天天看点

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

不定期补充、修正、更新;欢迎大家讨论和指正

目录

  • 传统的JDBC
  • MyBatis
    • 基本使用
      • 传参问题
    • 全局配置文件
    • 结果映射
      • 多表查询
    • 动态SQL
      • if标签
      • choose标签
      • foreach标签
    • 缓存
      • 一级缓存
      • 二级缓存
      • 自定义缓存
    • 源码分析
  • 整合Spring
  • MyBatisPlus

传统的JDBC

回顾以前数据库的连接和操作,我们使用的是原生的Java AP来实现,大致的步骤为

  1. 配置账号、密码、Driver、url成功连接数据库
  2. 通过链接Connection获取PreparedStatement类
  3. 编写SQL语句,将SQL语句放入PreparedStatement中预编译
  4. 填充占位符
  5. 执行SQL语句,如果是查询SQL,还需要用ResultSet类来接收结果集
  6. 关闭资源等收尾工作

以下表为例

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

插入

@Override
    public void insert(Jobs jobs) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = JdbcUtils.getConnection();//JdbcUtils是自己封装的工具类,里面包含获取连接,关闭连接的功能
            								//就是上一篇博客中CURD->insert->封装 的getconnection()和closeResource()方法
            String sql = "INSERT INTO `jobs`(`job_id`,`job_title`,`min_salary`,`max_salary`)VALUE(?,?,?,?);";
			
            ps = conn.prepareStatement(sql);//预编译

            ps.setString(1,jobs.getJob_id());//填充占位符
            ps.setString(2,jobs.getJob_title());
            ps.setInt(3,jobs.getMin_salary());
            ps.setInt(4,jobs.getMax_salary());

            ps.execute();//执行

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.closeResource(conn,ps);//关闭连接
        }

    }
           

删除

@Override
    public void deleteByID(String ID) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = JdbcUtils.getConnection();
            String sql = "DELETE FROM `jobs` WHERE `job_id`=?;";
            ps = conn.prepareStatement(sql);
            ps.setString(1,ID);

            ps.execute();

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.closeResource(conn,ps);
        }
    }
           

查询

@Override
    public Jobs getByID(String ID) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            String sql = "SELECT * FROM `jobs` WHERE `job_id` = ?;";

            ps = conn.prepareStatement(sql);

            ps.setString(1,ID);


            rs = ps.executeQuery();
            if (rs.next()) {
                Jobs jobs = new Jobs();
                jobs.setJob_id(rs.getString(1));
                jobs.setJob_title(rs.getString(2));
                jobs.setMin_salary(rs.getInt(3));
                jobs.setMax_salary(rs.getInt(4));
                return jobs;
            }


        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.closeResource(conn,ps,rs);
        }
        return null;
           

在使用传统的JDBC中,我们可以发现诸多不便

  • CURD很多步骤相同,连接数据库,获取PreparedStatement等。

    解决方法:提出公共部分作于模板

  • 将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。

    解决方法:利用反射从外部文件获取SQL语句。

  • 对于查询语句,接收结果集的过程十分繁琐
  • 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能

    解决方法:利用数据库连接池

尽管有一些简化的工具比如Dbutils和JDBCTemplate等,但能做的也很有限。

随后市面上就出现了许多持久层框架来简化开发和操作,如MyBatis、Hibernate、TopLink、Guzz、jOOQ、Spring Data。如今MyBatis占主流,同时也是主流MVC框架——SSM(Spring + SpringMVC + MyBatis)重要的一环。

MyBatis

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

什么是 MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

–摘自官网

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)

–摘自百度百科

MyBatis官方帮助文档

基本使用

导入Mybatis

jar包:mybatis

Maven依赖

<!-- Maven -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.5</version>
  <!-- 之前用的3.4.6执行后会抛出一堆Warning,查了之后说要降级jdk或者升级mybatis的版本 3.5.5亲测没有这些警告-->
</dependency>
           

mysql驱动

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
           

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。

而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

根据官方所说,SqlSessionFactory是整个MyBatis的核心,SqlSessionFactory通过SqlSessionFactoryBuilder获取,而SqlSessionFactoryBuilder可以通过XML配置文件获取,以下为官方给出的XML配置文件模板(配置文件名根据规范一般为mybatis-config.xml)。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED"><!--是否使用连接池-->
       <!--连接数据库的基本四个信息-->
        <property name="driver" value="${driver}"/> 
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
  <!--将mapper注册到mybatis中,后面会讲,很重要的一步,先注释掉-->
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>
           
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

接下来就可以获取SqlSessionFactory实例了,通过SqlSessionFactory可以获取SqlSession实例,而SqlSession才是真正与数据库交互的东西。

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

为了后续方便使用,可以写成工具类

package com.jojo.mybatis;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;
    
    static {
    
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }

}
           

在测试前先把主配置文件中的mapper映射注释掉,因为这是官方给的例子,我们并没有该mapper需要映射,否则会报错

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

成功获取SqlSession,但不代表能成功连接到数据库,就算连接数据库的账号密码是错误的,在这时也不会提示

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

利用SqlSession向数据库做CURD操作,仍以下表为例

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

ORM(对象关系映射),创建实体类,用于后面接收结果集

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

按照原生JDBC,接下来就是写SQL语句了,MyBatis提供在XML中编写SQL语句的形式

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  
<mapper namespace="org.mybatis.example.BlogMapper">
	
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
  
</mapper>
           
  • namespece:命名空间。在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。命名空间在之前版本的MyBatis中是可选的,这样容易引起混淆因此毫无益处。现在命名空间则是必须的,且易于简单地用更长的完完全限定名来隔离语句。
  • id:全局唯一不解释
  • resultType:接收结果集的类
  • #{id}:占位符以该形式编写

命名空间是为了防止id冲突,这里简单使用完全不怕,但既然是必须的,就先创建一个接口

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

接下来很重要的一步,将做CURD的mapper在主配置文件中注册

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

在这里很容易遇到找不到该mapper路径的异常,因为Maven一般只读取resource下的xml文件,而mapper一般是放在src目录下

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

因此需要在pom.xml添加以下配置

<build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
           

接下来就可以通过sqlSession向数据库进行CURD操作了

因为getById标签目前全局唯一,直接获取即可

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

为了避免标签冲突,安全的做法是先获取命名空间所绑定的接口

这样不仅更安全,可读性好、传参也方便

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

经过简单的使用,可以发现MyBatis很好地解决了原生JDBC的弊端:SQL语句在外部文件中编写,降低耦合性、结果集的接收MyBatis自动完成,只需要指定接收结果集的类即可,使得我们可以专注于SQL语句的构造。

接下来把剩下的CURD写完,不过在实现之前导入log4j日志功能,我们只能通过SqlSession是否操作数据库成功,但是如果出错,仅仅通过SqlSession很难进行排错。

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

导入依赖

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>

           

创建log4j的配置文件(log4j.properties,最好放在resource下)

这里是从Log4j的配置与使用详解直接扒下来的

# Global logging configuration
# 设置日志输出级别以及输出目的地,可以设置多个输出目的地,开发环境下,日志级别要设置成DEBUG或者ERROR
# 前面写日志级别,逗号后面写输出目的地:我自己下面设置的目的地相对应,以逗号分开
# log4j.rootLogger = [level],appenderName1,appenderName2,…
log4j.rootLogger=DEBUG,CONSOLE,LOGFILE

#### 控制台输出 ####
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 输出到控制台
log4j.appender.CONSOLE.Target = System.out
# 指定控制台输出日志级别
log4j.appender.CONSOLE.Threshold = DEBUG
# 默认值是 true, 表示是否立即输出
log4j.appender.CONSOLE.ImmediateFlush = true
# 设置编码方式
log4j.appender.CONSOLE.Encoding = UTF-8
# 日志输出布局
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 如果日志输出布局为PatternLayout 自定义级别,需要使用ConversionPattern指定输出格式
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p (%c:%L) - %m%n



#### 输出错误信息到文件 ####
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
# 指定输出文件路径
#log4j.appender.LOGFILE.File =F://Intellij idea/logs/error.log 
log4j.appender.LOGFILE.File =./logs/error.log 

#日志输出到文件,默认为true
log4j.appender.LOGFILE.Append = true
# 指定输出日志级别
log4j.appender.LOGFILE.Threshold = ERROR
# 是否立即输出,默认值是 true,
log4j.appender.LOGFILE.ImmediateFlush = true
# 设置编码方式
log4j.appender.LOGFILE.Encoding = UTF-8
# 日志输出布局
log4j.appender.LOGFILE.layout = org.apache.log4j.PatternLayout
# 如果日志输出布局为PatternLayout 自定义级别,需要使用ConversionPattern指定输出格式
log4j.appender.LOGFILE.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

           

有了日志功能,可以了解到SQL完整的执行过程,方便调试

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

在接口制定CURD方法

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

插入

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

对于增删改操作,SqlSession默认是不会自动提交事务的,需要手动提交

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

更改

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

删除

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

查询全部

查询多个结果,虽然返回值是集合,但在SQL语句返回值设置为集合中元素的类型就行

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

传参问题

细心的朋友可以发现在以上CURD传参的形式都有所不同

在MyBatis中,当传参只有一个时,在SQL语句写的参数名可以随意

比如删除操作,因为不会产生歧义,名字怎么取都没关系

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

当传的参数有多个时,可以传map,SQL语句的#{}中的变量名需要和map中的key相同

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

也可以通过Java Bean传递多个参数,SQL语句的#{}中的变量名和实体类的属性相同

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

还有是使用@param注解的形式,这里不进行演示

MyBatis(四)SQL语句中参数传递的五种方法

还有需要注意的是 ${},#{}的区别

在很多地方,${} 的作用是直接取值,这种方式容易产生SQL注入的危险(以预编译的方式将参数设置到sql中)

而#{} 运行结果会是一个?占位符 ,和使用PreparedStatement目的一致

当然${} 并不是一无是处,可以用于分库分表排序等原生jdbc不支持占位符的地方,例如

select * from ${month}_salary order by ${name}

全局配置文件

全局配置文件的属性设置,完全可以看官方文档

mybatis-配置

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

这里使用几个常用的

属性(properties)

主要用来获取外部文件的内容,比如连接数据库的信息可以单独放到另一个文件,然后通过properties引入

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

也可以直接在标签内设置,但是优先级还是文件中的高

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

比如之前mapper用到类时写的是全路径比较长

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

取别名

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

当需要某个包下有很多类需要取别名时,可以以包为单位为其下的所有类取别名

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

类的别名为类名(无视大小写)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

环境配置(environments)

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境,所以要在< environments default=“development” >标签内选择具体的环境

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

每套环境内都要设置transactionManager和dataSource,具体内容自己看

设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。自行了解,后续使用时会提到。

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

结果映射

在前面select语句中,对于复杂的结果集我们常用JavaBean 或 POJO来接收,也就是resultType=‘JavaBean’,虽然看似是ResultMap完成了结果集的映射,但实际上是ResultMap完成了这些事。MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。

什么时候用ResultMap呢,当数据库的字段和javaBean属性字段不一样时就可以使用(数据库字段命名一般为job_id,而Java是驼峰式命名)

之前属性名和数据库字段是一一对应的,所以直接可以用resultType接收

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

假如改成驼峰式命名,我们来看看还能接收到结果集吗

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

可以看到能查询到结果,但是没接收到结果集

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

这种情况下容易解决,我们可以在全局配置文件中的setting标签内开启驼峰命名自动映射

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

也可以利用SQL语法中的as取别名(别忘了关掉驼峰命名自动映射,不然不知道有没有成功)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

还有一种方法就是用resultMap作映射了

resultMap标签有三个属性

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

这里使用两个子标签就足够了(id和result的作用一样)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

在数据库字段和属性名一一对应或者驼峰转换后对应,直接用resultType就行,用resultMap就多次一举了

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

但是对于多表查询就不得不用resultMap了,在此之前先了解resultMap标签内所有的属性

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

多表查询

在先前我们已经有了一张job表

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

现在根据job_id与另一张employee表进行多表查询(为了方便点,这里只考虑以下四个属性,以及job表的job_title属性)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

而这两张表结合呈现一对多的关系(以job表为主表,一个job可以有多个employee),而反过来就是多对一的关系(以employee为主表),因此使用resultMap时要对两种关系分别进行实现。

构造实体类

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

因为job表可以查出多个employees,所以要为其添加一个列表类型的属性

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

重写下Employees的toString方法,方便后面查看

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

多对一

接下来先以简单的多对一关系进行实现,以下面查询结果为例

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

映射文件,在主配置文件注册这些常规流程就不说了

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

先构造select语句

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

再构造resultMap,因为查询的主表是Employees,所以type为Employees

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

这段没什么好说的,都是Employees的属性,按照普通的列名-属性名进行映射就行

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

重点是association标签,关联(association)元素处理“有一个”类型的关系

association标签下又包含四个属性,不过只用关心property和javaType

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

简单来说

property:你想将另一张表查询的结果集放到哪个属性下,还记得在Employees表下的Jobs jobs属性吗,所以这里property=’jobs’

javaType:接收另一张表结果集的JavaBean

接下来只要完成另一张表自己的列名-属性名的映射就行了

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

test

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

查询成功

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

在该例中,我们完全可以将多表查询等价为嵌套子查询,即先查询jobs表得到结果,在此结果上再查询employees表

因此我们可以修改成另一种形式

实现查询jobs表和employees的SQL语句,jobs直接返回结果,employees表还需要进行映射处理

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

同样重点在于association标签

property和javaType和上面一样

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

column和select的功能如下

column:连接键的列名,这里column是必选项,不像前面可以略去,因为要知道通过哪个共同的键进行查询

select:也是必选,不然怎么获得嵌套查询的结果

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

同样成功查询

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

一对多

当jobs作为主表时,通过job_id查询employees,显然构成了一对多的关系,但是大部分操作都相似,连SQL语句都是一样的

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

接下来是关键点resultMap,association标签用于处理“有一个”类型的关联,而处理“有很多个”类型的关联就需要用到collection集合标签了,collection标签的属性和association标签大部分相同,需要注意的是collection返回的类型,因为返回的是List,所以javaType得是ArrayList类型(可忽略),而ofType填才是List中元素的类型

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

在jobs表中实现打印方法(本来不用这么麻烦,但是只取了几个字段,其他字段就不打印了)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

成功获取

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

关于多表查询的结果映射还是比较令人头大,可以参考以下视频,以及强烈建议参考官方文档

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

动态SQL

​ 动态sql是指在进行sql操作的时候,传入的参数对象或者参数值,根据匹配的条件,有可能需要动态的去判断是否为空,循环,拼接等情况,是比较常用的功能。

比如在一个页面中的表单有几个传输参数(学到MyBatis应该都学过JaveWeb了)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

根据传输的参数动态地构造SQL语句

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

执行SQL语句

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

原文:JDBC实现动态查询

可以发现使用原生的JDBC十分的繁琐,而且还容易漏掉空格逗号造成错误,因此MyBatis提供了强大的动态SQL,让我们摆脱这种麻烦。

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

if标签

if标签是比较常用的功能,最常见情景是根据条件包含 where 子句的一部分

比如想查询工资大于minSalary,小于maxSalary范围的字段

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

仅传入minSalary

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

根据日志信息可以看到成功拼接了SQL语句(maxSalary为空)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

在此基础上查询工资低于20000的

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

其实这里写的拼接语句有很大的漏洞,假如minSalary为空但maxSalary不为空

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

就会拼接成错误的SQL语句导致报错

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

一种解决方法是在主SQL中添加where 1=1(保证后续的语句肯定执行),拼接SQL统一用逻辑连接符开头

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

这样SQL语句就不会出错了

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

另一种方法是使用trim标签,这个就自己去官网看。

choose标签

choose标签和swith-case语句类似,只会进入一个分支

比如我们可以根据job_id或job_title查询字段,如果一个属性存在,另一个就属性就不使用

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

为了规范点,以后查询条件都放在where标签内

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

只用job_title查询(when标签)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

同时用两个属性查询,可以看到当排在前面的分支符合后,就算后面有其他分支符合条件也不会进入

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

默认情况(otherwise标签)

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

foreach标签

foreach常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)

foreach使用难点比if和choose复杂些,主要在于参数的选择,foreach标签内有以下几个参数

  • item:表示集合中每一个元素进行迭代时的别名。(必选)
  • index:指定一个名字,表示在迭代过程中每次迭代到的位置。(可选)
  • open:表示该语句以什么开始(如果是 in 条件语句,以’('开始 )(可选)
  • separator:表示在每次进行迭代之间以什么符号作为分隔符(如果是 in 条件语句,以’,'作为分隔符)(可选)
  • close:表示该语句以什么结束(如果 in 条件语句,以’)'开始 )(可选)

使用 foreach 标签时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:

  • 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
  • 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
  • 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
  • 摘自MyBatis foreach标签

比如传入List,通过多个job_id查询多个字段

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

另外提一下,我们知道形参不同就可以函数重载,但在mybatis这是不允许的,因为标签id必须得唯一

Mybatis的XxxMapper.xml中能否配置重载方法

Mybatis的mapper接口函数重载问题

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率

MyBatis默认定义了两级缓存:一级缓存和二级缓存

一级缓存

一级缓存是默认开启的(SqlSession级别的缓存,也称为本地缓存)

如下,当我们先后分别查询同一字段,因为第一次查询的结构仍在缓存中,所以并不需要再次从数据库查询,大大增加了高并发的效率,因为我们知道数据从内存中读取远比从磁盘IO读取来的快

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

演示需要用到日志功能,这里不使用log4j,使用MyBatis自带的日志功能换换口味,在主配置文件的setting标签开启就行

<settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
           
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

一级缓存在几种情况下会失效,官方的给的文档说到select语句的结果会被缓存,但是insert、update、delete语句会刷新缓存

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

如下,在两次查询同一字段中,我们还更新了字段,尽管更新的字段和要查询的字段无关,MyBatis为了保证ACID原则也会再次从数据库查询

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

对此我们可以看各个标签内的属性,对于select语句,默认使用缓存

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

对于增删改操作,没有useCache属性,并且flushCache默认为true,所以一执行就会刷新缓存

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

二级缓存

二级缓存也叫全局缓存,是基于namespace级别的缓存,一个命名空间对应一个缓存(二级缓存并不是来解决一级缓存失效的问题的)

一个会话查询一个缓存,查询的数据会存放在当前会话的一级缓存中,如果该会话关闭,其缓存也会消除。

但我们可以使其保存在二级缓存中,新的会话查询就可以从二级缓存读取,不同的mapper查出的数据会放在自己对应的缓存中

要启用全局的二级缓存,需要现在主配置文件开启。

因为二级缓存的作用域是namespace,也就是mapper映射文件,所以哪个映射文件需要二级缓存,在该mapper映射文件中添加< cache >即可

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

该标签下有五个属性

  • eviction(清除策略):缓存清空策略,默认为LRU
    1. LRU – 最近最少使用:移除最长时间不被使用的对象。
    2. FIFO – 先进先出:即队列,按对象进入缓存的顺序来移除它们。
    3. SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    4. WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
  • flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  • size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
  • readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
  • type(自定义缓存)除了上述自定义缓存的方式,也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

创建两个会话,在第一次会话关闭后,仍然查询相同的字段,很明显可以看到两次查询的对象是相同的。

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus
Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

关闭二级缓存后

Java学习_持久层_MyBatis传统的JDBCMyBatis整合SpringMyBatisPlus

自定义缓存

MyBatis毕竟不是专门做缓存的,所以在一些复杂的情况下就需要用其他更强大的缓存,比如EhCache、Redis。自行了解

MyBatis自定义缓存——Redis实现

MyBatis自定义缓存——EhCache实现

源码分析

MyBatis底层源码解析 (详细)

整合Spring

目前来说,Mybatis常与Spring、SpringMVC一起组成SSM框架,也是目前主流的MVC框架来完成一个JavaWeb项目的开发,因此学习整合Mybatis和Spring(SpringMVC是Spring下的模块,整合Spring就行)是十分必要的。

使用 Spring IoC 可以有效的管理各类的 Java 资源,达到即插即拔的功能;通过 Spring AOP 框架,数据库事务可以委托给 Spring 管理,消除很大一部分的事务代码,配合 MyBatis 的高灵活、可配置、可优化 SQL 等特性,完全可以构建高性能的大型网站。

毫无疑问,MyBatis 和 Spring 两大框架已经成了 Java 互联网技术主流框架组合,它们经受住了大数据量和大批量请求的考验,在互联网系统中得到了广泛的应用。使用 MyBatis-Spring 使得业务层和模型层得到了更好的分离,与此同时,在 Spring 环境中使用 MyBatis 也更加简单,节省了不少代码,甚至可以不用 SqlSessionFactory、 SqlSession 等对象,因为 MyBatis-Spring 为我们封装了它们。

–《Java EE 互联网轻量级框架整合开发》

篇幅原因这里不进行实现

Spring:

Java学习_Spring_IoC

Java学习_Spring_AOP

MyBatis-Spring

MyBatis 与 Spring 整合

MyBatisPlus

  • 占坑