MyBatis

MyBatis:基本使用,动态SQL,逆向工程MBG

概述

  • 一个ORM(持久层)框架

    • JDBC(JavaDataBase Connectivity)是 Java 数据库连接——用 Java 操作数据库
    • Mybatis 针对 JDBC 中重复操作做了封装,扩展并优化部分功能
    • ORM:对象关系映射(Object Relational Mapping),通过描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中——ORM中间件能在任何一个应用的业务逻辑层和数据库层之间充当桥梁;ORM会牺牲程序的执行效率,因为ORM是面向对象的,并且应用于多层系统
  • 与其他ORM的对比:

    • JDBC:
      • SQL 夹杂在代码中,修改SQL需要重新编译 Java 类
      • 维护不易
      • 无连接池
    • Hibernate:
      • 内部自动生产的 SQL 不容易做特殊优化
      • 反射操作多
    • MyBatis:
      • SQL 和 Java 代码分开
  • 架构图:

    image-20220903124455760
    • MyBatis xml配置文件:
      • mybatis-config.xml是MyBatis的主配置文件,是全局的,保存数据库的连接信息、运行环境等
      • mapper.xml 是 sql 映射文件,记录对数据库进行增删改查的sql语句,映射文件需要在主配置文件mybatis-config.xml里显式的加载(见后文的xml配置)
    • SqlSessionFactory会话工厂:
      • SqlSessionFactory创建各种会话,实际上是一个接口,接口中定义了openSession的不同的加载方法
      • 一般用单例模式来管理SqlSessionFactory,便于重复使用
      • 工厂模式:如果创建某一个对象,使用的过程基本固定,可以把创建这个对象的相关代码封装到一个“工厂类”中,以后使用工厂类“生产”需要的对象
    • SqlSession会话:面向用户的,即一个用户对应一个会话,定义数据库的操作方法,类似于JDBC中的Connection对象
      • 每个线程都应有自己的SqlSession实例。SqlSession的实例不能共享使用,因为不是线程安全的
      • 打开了一个SqlSession,则在使用完毕后需要关闭它。通常sqlsession.close()关闭操作,放到finally{}块里,以确保每次都能正确关闭
    • executor执行器:MyBatis底层自定义了Execuor执行器接口操作数据库(执行SQL语句),包括2个执行器:基本执行器、缓存执行器
    • MappedStatement映射器:
      • MyBatis定义的一个底层封装对象,包装了xml配置信息、sql映射信息等
      • 在Mapper.xml里,一个sql语句对应一个MappedStatement映射器对象,sql的id也是MappedStatement映射器的id——接收输入映射(SQL语句中的参数),返回输出映射(即将SQL查询的结果映射成相应的结果)

范例

基本过程

  • 数据库:建立库testMyBatis,建立表Emp——表名和后面的类一致;表的列和类的属性一致

  • 环境

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <dependencies>
    <!-- Mybatis核心 -->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.3</version>
    </dependency>

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

    </dependencies>
  • 配置日志(src/main/resources/log4j.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
    <param name="Encoding" value="UTF-8" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
    </layout>
    </appender>
    <logger name="java.sql">
    <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
    <level value="info" />
    </logger>
    <root>
    <level value="debug" />
    <appender-ref ref="STDOUT" />
    </root>
    </log4j:configuration>
  • 配置文件(src/main/resources目录):习惯上命名为mybatis-config.xml,用于配置连接数据库的环境和MyBatis的全局配置信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>

    <!--设置连接数据库的环境-->
    <!--配置环境,可配置多个环境(比如:develop开发、test测试-->
    <environments default="development">
    <environment id="development">
    <!--1.1.配置事务管理方式:JDBC/MANAGED
    JDBC:将事务交给JDBC管理(推荐)
    MANAGED:自己管理事务-->
    <transactionManager type="JDBC"/>
    <!--1.2.配置数据源,即连接池 JNDI/POOLED/UNPOOLED
    JNDI:已过时
    POOLED:使用连接池(推荐)
    UNPOOLED:不使用连接池
    -->
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url"
    value="jdbc:mysql://localhost:3306/testMyBatis?characterEncoding=utf8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    </dataSource>
    </environment>
    </environments>

    <!--引入映射文件-->
    <!--如果mapper文件有多个,可以通过多个mapper标签导入-->
    <mappers>
    <mapper resource="mappers/EmpMapper.xml"/>
    </mappers>
    </configuration>
    • 对应数据库为testMyBatis,因此url中为testMyBatis
    • 由于数据库建立时,使用了utf-8编码,因此需要characterEncoding=utf8
    • mappers/EmpMapper.xml位于src/main/resources
  • 建立Emp类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package com.testMyBatis.tables;

    public class Emp {
    private Integer id;
    private String name;
    private String job;
    private Double salary;

    public Integer getId() {
    return id;
    }
    public String getName() {
    return name;
    }
    public String getJob() {
    return job;
    }
    public Double getSalary() {
    return salary;
    }
    public void setId(Integer id) {
    this.id = id;
    }
    public void setName(String name) {
    this.name = name;
    }
    public void setJob(String job) {
    this.job = job;
    }
    public void setSalary(Double salary) {
    this.salary = salary;
    }

    @Override
    public String toString() {
    return "Emp [id=" + id + ", name=" + name + ", job=" + job + ", salary=" + salary + "]";
    }
    }
    image-20220903150530077
  • 建立mapper接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.testMyBatis.mappers;

    import java.util.List;

    import com.testMyBatis.tables.Emp;

    public interface EmpMapper {
    // 查询所有的用户信息
    List<Emp> findAll();
    }
  • src/main/resources下创建mappers/EmpMapper.xml

    • 映射文件命名规则:

      • 表所对应的实体类的类名+Mapper.xml
      • 一个映射文件对应一个实体类,对应一张表的操作
      • MyBatis中可以面向接口操作数据,要保证:
        • mapper接口的全类名和映射文件的命名空间(namespace)保持一致,此时namespace应当也为com.test
        • mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
    • 映射文件内容:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <?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值应该保证唯一,在程序中通过[ namespace + id ]定位到要执行哪一条SQL语句-->

      <mapper namespace="com.testMyBatis.mappers.EmpMapper">
      <select id="findAll" resultType="com.testMyBatis.tables.Emp">
      select * from Emp
      </select>
      </mapper>
      • namespace不能重复
      • id不能重复
      • select必须设置属性设置resultType或resultMap,表明实体类和数据库表的映射关系
        • resultType属性:从这条SQL语句返回所期望类的完全限定名称(包名+类名)——如果返回一个集合,则为一个集合中元素的类型;用于属性名和表中字段名一致的情况
        • resultMap属性:复杂对象结构(例如多表关联查询等),用于一对多或多对一或字段名和属性名不一致的情况
  • 测试:通过代理模式创建EmpMapper接口的代理实现类对象,根据EmpMapper的全类名匹配映射文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package com.testMyBatis;

    import com.testMyBatis.mappers.EmpMapper;
    import com.testMyBatis.tables.Emp;
    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 org.junit.jupiter.api.Test;

    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;

    public class test {

    @Test
    public void findAll() throws IOException {
    // 读取mybatis的核心配置文件(mybatis-config.xml)
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    // 通过配置信息获取一个SqlSessionFactory工厂对象
    SqlSessionFactory fac = new SqlSessionFactoryBuilder().build(in);
    // 创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
    SqlSession session = fac.openSession();

    // List<Emp> list = session.selectList("com.testMyBatis.mappers.EmpMapper.findAll");
    // for (Emp e : list) {
    // System.out.println(e);
    //}

    // 通过代理模式创建EmpMapper接口的代理实现类对象
    EmpMapper empMapper = session.getMapper(EmpMapper.class);
    // 调用EmpMapper接口中的方法,就可以根据EmpMapper的全类名匹配映射文件,
    // 通过调用的方法名匹配映射文件中的SQL标签,并执行标签中的SQL语句
    List<Emp> result = empMapper.findAll();
    for (Emp e : result) {
    System.out.println(e);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    DEBUG 09-03 13:47:53,839 ==>  Preparing: select * from Emp (BaseJdbcLogger.java:137) 
    DEBUG 09-03 13:47:53,873 ==> Parameters: (BaseJdbcLogger.java:137)
    DEBUG 09-03 13:47:53,919 <== Total: 5 (BaseJdbcLogger.java:137)
    Emp [id=1, name=a, job=程序员, salary=3300.0]
    Emp [id=2, name=b, job=程序员, salary=2800.0]
    Emp [id=3, name=c, job=程序员, salary=2700.0]
    Emp [id=4, name=d, job=总监, salary=4200.0]
    Emp [id=5, name=e, job=程序员, salary=3000.0]
  • 综上:

    • 类对应数据库中的表格
    • mapper接口对应xml映射文件的命名空间,接口的函数名对应映射文件中的id,代理模式创建代理类对象,对象调用接口中的函数来执行映射文件中的sql语句
    • xml映射文件的名字在mybatis-config.xml中定义
    • mybatis-config.xml在初始化一个SqlSessionFactory工厂对象时传入

其他固定的CRUD

  • 增删改的标签不用指定resultType,因为返回值都是int,接口函数可以为void

  • 映射文件中添加增删改标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <update id="insert">
    insert into emp value(null, 'f', '保安', 6000)
    </update>

    <update id="update">
    update emp set job='门卫', salary=20000 where name='f'
    </update>

    <update id="delete">
    delete from emp where name='f'
    </update>
  • mapper接口声明增删改函数

    1
    2
    3
    4
    5
    6
    public interface EmpMapper {
    ...
    void insert();
    void update();
    void delete();
    }

占位符(带参数的CRUD)

  • #{} :本质是占位符赋值
    • 为SQL语句中的参数值进行占位
    • 大部分情况下都是使用#{}
    • 为字符串或者日期类型的值进行占位时,在参数值传过来替换占位符的同时,会进行转义处理(在字符串或日期类型的值的两边加上单引号)
  • ${}:本质是字符串拼接
    • 将传过来的SQL片段直接拼接在该占位符所在的位置,不会进行任何的转义处理,因此可能需要添加单引号
    • 使用该占位符时,即使只有一个占位符,也需要将参数先封装再传递
    • 例如:
      • 动态传递查询的列,若使用select #{columns} from emp,会被转译为select 'id,name,job' from emp,而使用select ${columns} from emp则直接拼接为select id,name,job from emp

不同类型的参数

  • mapper接口中方法的参数为一个:可以使用${}#{}以任意的名称获取参数的值

    • mapper接口:

      1
      2
      3
      4
      public interface EmpMapper {
      ...
      List<Emp> getDataByName(String name);
      }
    • 映射文件:注意#{}${}的差别

      1
      2
      3
      4
      <select id="getDataByName" resultType="com.testMyBatis.tables.Emp">
      <!--select * from emp where username = #{name}-->
      select * from emp where name='${name}'
      </select>
  • mapper接口中方法的参数为多个:

    • MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为key、以参数为value(#{},以param0,param1…为key、以参数为value(${}

      • mapper接口:

        1
        List<Emp> getDataByNameSalary(String name, double salary);
      • 映射文件:

        1
        2
        3
        4
        <select id="getDataByNameSalary" resultType="com.testMyBatis.tables.Emp">
        <!--select * from emp where name = #{arg0} and salary = #{arg1}-->
        select * from emp where name = '${param1}' and salary = ${param2}
        </select>
    • 可以手动创建map集合,通过${}#{}访问map集合的key获得对应的value——key即为#{}中的占位字符串

      • mapper接口:

        1
        List<Emp> getDataByNameSalaryMap(Map<String, Object> map);
      • 映射文件:

        1
        2
        3
        <select id="getDataByNameSalaryMap" resultType="com.testMyBatis.tables.Emp">
        select * from emp where name=#{name} and salary=#{salary}
        </select>
      • 测试:

        1
        2
        3
        4
        Map<String, Object> map = new HashMap<>();
        map.put("name", "张三");
        map.put("salary", 3300.0);
        List<Emp> result = empMapper.getDataByNameSalaryMap(map);
  • 使用实体类作为参数:访问实体类对象中的属性名获取属性值

    • mapper接口:

      1
      void insertUser(Emp emp);
    • 映射文件:

      1
      2
      3
      <insert id="insertUser">
      insert into emp values(null, #{name}, #{job}, #{salary})
      </insert>
  • 使用@Param标识参数:通过@Param注解标识mapper接口中的方法参数,这些参数放入map集合,以@Param注解的值为key、参数为value(#{}

    • mapper接口:

      1
      List<Emp> getDataByNameSalaryParam(@Param("name") String name, @Param("salary") double salary);
    • 映射文件:

      1
      2
      3
      <select id="getDataByNameSalaryParam" resultType="com.testMyBatis.tables.Emp">
      select * from emp where name=#{name} and salary=#{salary}
      </select>

MyBatis的查询

  • 上面给出了查询一个实体类对象、查询一个list集合实体对象的示例——一个对象在表格里是一行记录

  • MyBatis对于Java常用的类型都设置了类型别名,例如:java.lang.Integer–>int|integer、int–>_int|_integer、Map–>map、List–>list

  • 要求返回单个数据:

    • mapper接口:

      1
      int getCount();
    • 映射文件:

      1
      2
      3
      <select id="getCount" resultType="_integer">
      select count(id) from emp
      </select>
  • 要求返回一条数据,格式为map:输出的map中,key为表的字段(String类型),value为该数据在相应字段下的值(Object类型)

    • mapper接口:

      1
      Map<String, Object> getRecordToMap(@Param("id") int id);
    • 映射文件:

      1
      2
      3
      <select id="getRecordToMap" resultType="map">
      select * from emp where id=#{id}
      </select>
  • 要求返回多条数据,格式为map:

    • 方法一:返回map列表

      • mapper接口:

        1
        List<Map<String, Object>> getAllRecordToMap1();
      • 映射文件:

        1
        2
        3
        <select id="getAllRecordToMap1" resultType="map">
        select * from emp
        </select>
    • 方法二:返回map<map>:需要通过@MapKey注解设置map集合的key,value是每条数据所对应的map。下面的例子,key为每条数据的id

      • mapper接口:

        1
        2
        @MapKey("id")
        Map<String, Object> getAllRecordToMap2();
      • 映射文件:同上

        1
        2
        3
        4
        5
        6
        7
        {
        1={name=a, id=1, job=q, salary=3300.0},
        2={name=b, id=2, job=q, salary=2800.0},
        3={name=c, id=3, job=q, salary=2700.0},
        4={name=d, id=4, job=q, salary=4200.0},
        5={name=e, id=5, job=q, salary=3000.0}
        }

特殊SQL

  • 模糊查询

    1
    2
    3
    4
    5
    <select id="testMohu" resultType="com.testMyBatis.tables.Emp">
    <!--select * from t_user where username like '%${mohu}%'-->
    <!--select * from t_user where username like concat('%',#{mohu},'%')-->
    select * from emp where name like "%"#{mohu}"%"
    </select>
    • SQL模糊查询:SELECT 字段 FROM 表 WHERE 某字段 Like 条件
      • %:表示任意0个或多个字符。可匹配任意类型和长度的字符,例如SELECT * FROM user WHERE u_name LIKE '%三%'——SELECT * FROM user WHERE u_name LIKE '%三%' AND u_name LIKE '%猫%'又有“三”又有“猫”、SELECT * FROM user WHERE u_name LIKE '%三%猫%'“三”在“猫”前面
      • _:表示任意单个字符。匹配单个任意字符,例如SELECT * FROM user WHERE u_name LIKE '_三_',三个字且中间为“三”
      • [ ]:表示括号内所列字符中的一个(类似正则表达式),例如SELECT * FROM user WHERE u_name LIKE '老[1-9]'(找出“老1”等)、SELECT * FROM user WHERE u_name LIKE '[张李王]三'(找出“张三”、“李三”等)、SELECT * FROM user WHERE u_name LIKE '老[c-f]'(找出“老c”等)
      • [^ ] :表示不在括号所列之内的单个字符
  • 批量删除:foreach(见后文动态SQL)

  • 动态设置表名

    1
    2
    3
    <select id="getAllUser" resultType="com.testMyBatis.tables.Emp">
    select * from ${tableName}
    </select>

resultMap

  • 上面的例子中,表中字段名和表对应类的属性名是一致的。若字段名和实体类中的属性名不一致,则通过resultMap设置自定义映射
  • resultMap:设置自定义映射
    • 属性:
      • id:表示自定义映射的唯一标识
      • type:查询的数据要映射的实体类的类型
    • 子标签:
      • id:设置主键的映射关系
      • result:设置普通字段的映射关系
      • association:设置多对一的映射关系
      • collection:设置一对多的映射关系
      • 属性:
        • property:设置映射关系中实体类中的属性名
        • column:设置映射关系中表中的字段名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<resultMap id="唯一标识" type="映射的entity对象的绝对路径">
<id column="表主键字段" jdbcType="字段类型" property="映射entity对象的主键属性" />

<result column="表某个字段" jdbcType="字段类型" property="映射entity对象的某个属性"/>

<!-- 指的是entity对象中的对象属性 -->
<association property="entity中某个对象属性" javaType="这个对象的绝对路径">
<id column="这个对象属性对应的表的主键字段" jdbcType="字段类型" property="这个对象属性内的主键属性"/>
<result column="表某个字段" jdbcType="字段类型" property="这个对象属性内的某个属性"/>
</association>

<!-- 指的是entity对象中的集合属性 -->
<collection property="entity中的某个集合属性" ofType="这个集合泛型所存实体类的绝对路径">
<id column="这个集合属性中泛型所存实体类对象对应表的主键字段" jdbcType="字段类型"
property="这个集合属性中泛型所存实体类对象的主键属性"
/>
<result column="表某个字段" jdbcType="字段类型"
property="这个集合属性泛型所存实体类对象的属性"
/>
</collection>

<!-- 引用另一个resultMap (套娃) -->
<collection property="entity中的某个集合属性"
resultMap="这个引用的resultMap的type,就是这个集合属性泛型所存实体类的绝对路径"
/>
</resultMap>

一对一映射

1
2
3
4
5
6
<resultMap id="empMap" type="com.testMyBatis.tables.Emp">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="job" column="job"></result>
<result property="salary" column="salary"></result>
</resultMap>
  • 在MyBatis核心配置文件设置一个全局配置信息mapUnderscoreToCamelCase,在查询表中数据时自动将_类型的字段名转换为驼峰——user_name转换为userName

多对一映射

  • 例如,将两个表关联起来的查询语句,查询某个员工信息以及员工所对应的部门信息——其中Emp类的属性有dept对象,dept对象具有属性did、dname

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <resultMap id="empDeptMap" type="Emp">
    <id column="eid" property="eid"></id>
    <result column="ename" property="ename"></result>
    <result column="age" property="age"></result>
    <result column="sex" property="sex"></result>
    <association property="dept" javaType="Dept">
    <id column="did" property="did"></id>
    <result column="dname" property="dname"></result>
    </association>
    </resultMap>
    <!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
    <select id="getEmpAndDeptByEid" resultMap="empDeptMap">
    select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.eid = #{eid}
    </select>

一对多映射

  • 例如,根据部门id查询新的部门和部门中的员工信息——此时部门类具有属性List<Emp> emps

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <resultMap id="deptEmpMap" type="Dept">
    <id property="did" column="did"></id>
    <result property="dname" column="dname"></result>
    <!--
    ofType:设置collection标签所处理的集合属性中存储数据的类型
    -->
    <collection property="emps" ofType="Emp">
    <id property="eid" column="eid"></id>
    <result property="ename" column="ename"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    </collection>
    </resultMap>
    <!--Dept getDeptEmpByDid(@Param("did") int did);-->
    <select id="getDeptEmpByDid" resultMap="deptEmpMap">
    select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did = emp.did where dept.did = #{did}
    </select>
  • 分步查询(略)

动态SQL

  • 根据特定条件动态拼装SQL语句

  • sql片段:记录一段公共sql片段,在使用的地方通过include标签进行引入

    1
    2
    3
    4
    5
    6
    <sql id="empColumns">
    name, job, salary
    </sql>
    <select id="getEmpByConditionTrim" resultType="com.testMyBatis.tables.Emp">
    select <include refid="empColumns"></include> from emp
    </select>
  • If

    • 可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会拼接到sql语句

    • 表达式中的name为Emp类的属性。property != null适用于任何类型的字段判断属性值是否为空;property != ''仅适用于String类型字段判断是否为空字符串

    • mapper:

      1
      List<Emp> getEmpByConditionOne(Emp emp);
    • 映射文件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <select id="getEmpByConditionIf" resultType="com.testMyBatis.tables.Emp">
      select * from emp where 1=1
      <if test="name != null and name != ''">
      and name = #{name}
      </if>
      <if test="job != null and job != ''">
      and job = #{job}
      </if>
      <if test="salary != null and salary != 0">
      and salary = #{salary}
      </if>
      </select>
  • Where

    • where和if一般结合使用:

      • 若where标签中的if条件都不满足,则where标签没有任何功能
      • 若where标签中的if条件满足,则自动添加where关键字,并将条件最前方多余的 and去掉
    • mapper:

      1
      List<Emp> getEmpByConditionWhere(Emp emp);
    • 映射文件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <select id="getEmpByConditionWhere" resultType="com.testMyBatis.tables.Emp">
      select * from emp
      <where>
      <if test="name != null and name != ''">
      name = #{name}
      </if>
      <if test="job != null and job != ''">
      and job = #{job}
      </if>
      <if test="salary != null and salary != 0">
      and salary = #{salary}
      </if>
      </where>
      </select>
  • choose、when、otherwise:相当于if…else if..else

    • mapper:

      1
      List<Emp> getEmpByChoose(Emp emp);
    • 映射文件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <select id="getEmpByChoose" resultType="com.testMyBatis.tables.Emp">
      select * from emp
      <where>
      <choose>
      <when test="name != null and name != ''">
      name = #{name}
      </when>
      <when test="job != null and job != ''">
      job = #{job}
      </when>
      <when test="salary != null and salary != 0.0">
      salary = #{salary}
      </when>
      <otherwise>
      1 = 1
      </otherwise>
      </choose>
      </where>
      </select>
  • Trim:<trim prefix="", suffix="", prefixOverrides="", suffixOverrides=""> </trim>

    • prefix:给sql增加前缀

    • suffix:给sql增加后缀

    • prefixOverrides:去掉sql前面多余的关键字或者字符

    • suffixOverrides:去掉sql前面多余的关键字或者字符

    • 映射文件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <sql id="empColumns">
      name, job, salary
      </sql>
      <select id="getEmpByConditionTrim" resultType="com.testMyBatis.tables.Emp">
      select <include refid="empColumns"></include> from emp
      <trim prefix="where" suffixOverrides="and|or">
      <if test="name != null and name != ''">
      name = #{name} and
      </if>
      <if test="job != null and job != ''">
      job = #{job} or
      </if>
      <if test="salary != null and salary != 0.0">
      salary = #{salary}
      </if>
      </trim>
      </select>
      • sql语句为from emp where ...,如果job满足条件,salary不满足条件,则将最后的or删除形成最终的sql语句
  • Foreach

    • 输入参数为一个数组,或者集合

    • separator:设置循环体之间的分隔符

    • item:表示集合或数组中的每一个数据

    • collection:设置要循环的数组或集合

    • 例如:

      • 批量删除(通过数组)

        1
        int deleteMoreByArray(@Param("ids") int[] ids);
        1
        2
        3
        4
        5
        6
        <delete id="deleteMoreByArray">
        delete from emp where
        <foreach collection="ids" item="id" separator="or">
        id = #{id}
        </foreach>
        </delete>
        • 效果:delete from emp where id = ? or id = ? or id = ?

        • 注意,这里collection属性等于Param注解提供的值,通过它访问参数——建议除了实体对象和map以外,其他参数输入都用这个方法

        • 或者:

          1
          2
          3
          4
          5
          6
          <delete id="deleteMoreByArray">
          delete from emp where id in
          <foreach collection="ids" item="id" separator="," open="(" close=")">
          #{id}
          </foreach>
          </delete>

          效果:delete from emp where id in (?, ?, ?)

      • 批量添加(通过集合)

        1
        2
        3
        4
        5
        6
        <insert id="insertMoreEmp">
        insert into emp values
        <foreach collection="emps" item="emp" separator=",">
        (null,#{emp.name},#{emp.job},#{emp.salary})
        </foreach>
        </insert

缓存

缓存只对查询有用

一级缓存

  • 默认开启
  • SqlSession级别,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据会从缓存直接获取,不会重新访问数据库
  • 一级缓存失效:
    • 不同的SqlSession对应不同的一级缓存
    • 同一个SqlSession
      • 查询条件不同(即执行不同的SQL查询,新的mapper接口实例执行同样的查询不会失效)
      • 两次查询期间执行了一次增删改操作
      • 两次查询期间手动清空了缓存

二级缓存

  • 范围更大

  • SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取(SqlSessionFactory级别高于SqlSession)

  • 二级缓存开启的条件:

    • 在核心配置文件中,设置全局配置属性cacheEnabled=”true”,默认为true

    • 在映射文件中设置标签<cache/>

      1
      2
      <mapper namespace="com.testMyBatis.mappers.EmpMapper">
      <cache flushInterval=""/>
    • 二级缓存必须在SqlSession关闭或提交之后有效——即,两个session,第一个session查询后关闭,第二个session再次查询

    • 查询的数据所转换的实体类类型必须实现序列化的接口

  • 失效:两次查询之间执行增删改

  • 在mapper配置文件中添加的cache标签可以设置一些属性:

    • eviction属性:缓存回收策略
      • LRU(Least Recently Used):移除最长时间不被使用的对象
      • FIFO(First in First out)
      • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
      • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
      • 默认LRU
    • flushInterval属性:刷新间隔,单位毫秒(默认不设置,仅仅调用语句时刷新)
    • size属性:引用数目,正整数,代表缓存最多可以存储多少个对象
    • readOnly属性:只读,true/false
      • true:只读缓存;给所有调用者返回缓存对象的相同实例。这些对象不能被修改
      • false:读写缓存;通过序列化返回缓存对象的拷贝,此时修改这个对象不会改变缓存中的内容。慢一些,但安全
      • 默认false
  • 缓存查询顺序:

    • 先查询二级缓存——可能会有其他程序已经查出的数据
    • 二级缓存没有命中,再查询一级缓存
    • 一级缓存也没有命中,查询数据库
    • SqlSession关闭之后,一级缓存中的数据会写入二级缓存

三级缓存EHCache

逆向工程(MyBatis Generator)

  • 正向工程:先创建Java实体类,由框架根据实体类生成数据库表——Hibernate支持正向工程,目前MyBatis不能独立支持(以上的所有操作,都需要数据库中预先建立一个表结构)

  • 逆向工程:先创建数据库表,由框架根据数据库反向生成Java实体类、Mapper接口、Mapper映射文件——代码生成器

  • 此时可以通过任意字段,以任意的条件进行CRUD(QBC风格,即条件都是定义好的,只需要调用对应的方法,就可以生成标准的条件),但逆向工程生成的是单个表的CRUD,多表连接、 存储过程等这些复杂sql的定义需要手工编写

  • 创建一个新工程MyBatis_MBG

  • 依赖和插件(pom.xml):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>MyBatis_MBG</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!--打包结果为jar-->
    <packaging>jar</packaging>

    <!-- 依赖MyBatis核心包 -->
    <dependencies>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.3</version>
    </dependency>

    <!-- log4j日志 -->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>

    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
    </dependency>
    </dependencies>

    <!-- 控制Maven在构建过程中相关配置 -->
    <build>

    <!-- 构建过程中用到的插件 -->
    <plugins>

    <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
    <plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.0</version>

    <!-- 插件的依赖 -->
    <dependencies>

    <!-- 逆向工程的核心依赖 -->
    <dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.2</version>
    </dependency>

    <!-- 数据库连接池 -->
    <dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.2</version>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.8</version>
    </dependency>
    </dependencies>
    </plugin>
    </plugins>
    </build>

    </project>
  • mybatis_config.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?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>

    <properties resource="jdbc.properties"/>

    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </dataSource>
    </environment>
    </environments>

    <mappers>
    <package name="mappers"/>
    </mappers>
    </configuration>
  • jdbc.properties:

    1
    2
    3
    4
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/testMyBatis?characterEncoding=utf8
    jdbc.username=root
    jdbc.password=123456
  • 逆向工程的配置文件,必须为generatorConfig.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
    PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
    "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    <generatorConfiguration>
    <!--
    targetRuntime: 执行生成的逆向工程的版本
    MyBatis3Simple: 生成基本的CRUD
    MyBatis3: 生成带条件的CRUD
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
    <!-- 数据库的连接信息 -->
    <jdbcConnection driverClass="com.mysql.jdbc.Driver"
    connectionURL="jdbc:mysql://localhost:3306/testMyBatis?characterEncoding=utf8"
    userId="root"
    password="123456">
    </jdbcConnection>
    <!-- javaBean的生成策略-->
    <javaModelGenerator targetPackage="com.testMyBatis.tables" targetProject=".\src\main\java">
    <property name="enableSubPackages" value="true" />
    <property name="trimStrings" value="true" />
    </javaModelGenerator>
    <!-- SQL映射文件的生成策略 -->
    <sqlMapGenerator targetPackage="mappers" targetProject=".\src\main\resources">
    <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>
    <!-- Mapper接口的生成策略 -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.testMyBatis.mapper" targetProject=".\src\main\java">
    <property name="enableSubPackages" value="true" />
    </javaClientGenerator>

    <!-- 逆向分析的表 -->
    <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
    <!-- domainObjectName属性指定生成出来的实体类的类名 -->
    <table tableName="emp" domainObjectName="Emp"/>
    <!--<table tableName="t_dept" domainObjectName="Dept"/>-->
    </context>
    </generatorConfiguration>
    • enableSubPackages:为true,则每个“.”都是一层package
    • trimStrings:如果表格中的字段前后有空格,则将空格去掉,得到类的属性名
  • 运行:

    image-20220904165604594
  • 运行效果:

    image-20220904165628826 image-20220904165652794 * ByExample:条件查询 * Selective:选择性运行(例如,如果某个对象的一个属性为null,则插入数据时不会将相应字段填入记录)

分页插件

  • 依赖:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
    </dependency>
  • mybatis-config.xml中配置插件

    1
    2
    3
    4
    <plugins>
    <!--设置分页插件-->
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
  • 使用:

    • 查询之前PageHelper.startPage(int pageNum, int pageSize)开启分页功能

      • pageNum:当前页的页码
      • pageSize:每页显示的条数
    • 获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)获取分页相关数据

    • 分页相关数据:

      1
      2
      3
      4
      5
      6
      7
      PageInfo{
      pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
      list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30, pages=8, reasonable=false, pageSizeZero=false},
      prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
      hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
      navigatepageNums=[4, 5, 6, 7, 8]
      }
      • pageNum:当前页的页码
      • pageSize:每页显示的条数
      • size:当前页显示的真实条数
      • total:总记录数
      • pages:总页数
      • prePage:上一页的页码
      • nextPage:下一页的页码
      • isFirstPage/isLastPage:是否为第一页/最后一页
      • hasPreviousPage/hasNextPage:是否存在上一页/下一页
      • navigatePages:导航分页的页码数