Mybatis 学习笔记

一、简介

MyBatis是什么

  • MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  • MyBatis 可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO( Plain Old Java Objects,普通的Java对象)映射成数据库中的记录

Mabits官方文档

二、Mybatis配置

1、数据准备

在MySQL数据库创建一数据库实例learnmybatis,在其创建一张表

CREATE TABLE employee(
  id INT(11) PRIMARY KEY AUTO_INCREMENT,
  last_name VARCHAR(255),
  gender CHAR(1),
  email VARCHAR(255),
  did VARCHAR(255)
);

CREATE TABLE department(
  id INT(11) PRIMARY KEY AUTO_INCREMENT,
  department_name VARCHAR(255)
);

创建对应的JavaBean

public class Employee {
  
  private Integer id;
  private String lastName;
  private String email;
  private String gender;
  private Department department;
  
  //getter and setter and toString()
}


public class Department {
    private Integer id;
    private String departmentName;
    private List<Employee> emps;
    //getter and setter and toString()
}

pom.xml中引入依赖,注意引入最新版

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>

2、Mybatis全局配置

<?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>
  <settings>
      <!-- 使用驼峰命名法 -->
      <setting name="mapUnderscoreToCamelCase" value="true"/>
      <!-- 开启日志,这里也可以配置log4j来实现,不过需要配置-->
      <setting name="logImpl" value="STDOUT_LOGGING" />
      <!--二级缓存开启-->
      <setting name="cacheEnabled" value="true"/>
  </settings>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/learnmybatis?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai" />
        <property name="username" value="root" />
        <property name="password" value="root" />
      </dataSource>
    </environment>
  </environments>
  <!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
  <mappers>
     <!--这里填写resource下的路径-->
    <mapper resource="mappers/EmployeeMapper.xml" />
    <mapper resource="mappers/DepartmentMapper.xml" />
    <mapper resource="mappers/EmployeeMapper1.xml" />
    <!--也可以直接填写包名,有好几种写法-->
  </mappers>
</configuration>

MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息。文档的顶层结构如下:

  • configuration 配置
    • properties 属性
    • settings 设置
    • typeAliases 类型命名
    • typeHandlers 类型处理器
    • objectFactory 对象工厂
    • plugins 插件
    • environments 环境
      • environment 环境变量
        • transactionManager 事务管理器
        • dataSource 数据源
      • databaseIdProvider 数据库厂商标识
      • mappers 映射器

官方文档

3、全局配置文件-properties外部引入

<configuration>
  <!--
    1、mybatis可以使用properties来引入外部properties配置文件的内容;
    resource:引入类路径下的资源
    url:引入网络路径或者磁盘路径下的资源
    -->
  <properties resource ="dbconfig.properties"></properties>

在resource下中创建dbconfig.properties文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/learnmybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

4、配置文件常用配置

settings-运行时行为设置

<configuration>
  ...
  
  <!-- 
    2、settings包含很多重要的设置项
    setting:用来设置每一个设置项
      name:设置项名
      value:设置项取值
    这里设置驼峰命名法
   -->
  <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>

类型更多设置

类型处理器

typeAliases-别名

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。可以在xml配置<typeAliases>,也可以在类上加注解@Alias不过为了好排查,一般都写全类名。

enviroments-运行环境

  • id:指定当前环境的唯一标识
  • transactionManager、和dataSource都必须有
<environments default="dev_mysql">
  <environment id="dev_mysql">
    <transactionManager type="JDBC"></transactionManager>
    <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>
  ...

5、测试类

public class MybatisTest {

    public static void main(String[] args) throws IOException {
        // 加载mybatis框架主配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 读取解析配置文件内容,创建SqlSessionFacory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 获取sqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        // 执行数据库操作
        Employee empAndDepart = mapper.getEmpAndDepart(1);
        // List<Employee> list = mapper.getEmp1();
        System.out.println(empAndDepart);
        sqlSession.commit();
        sqlSession.close();
    }
}

三、Mybatis 基础操作

1、基础操作

  • cache –命名空间的二级缓存配置
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 自定义结果集映射
  • parameterMap – 已废弃!老式风格的参数映射
  • sql –抽取可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

EmployeeMapper.java

public interface EmployeeMapper {
  
    //也可以通过注解方式
    //@Select("select * from employee where id = #{id}")
    // @Results({
    //         @Result(id=true,column="id",property="id")
    // )}
    // public Employee getEmpById(Integer id);
    
  public Employee getEmpById(Integer id);

  public Long addEmp(Employee employee);

  public boolean updateEmp(Employee employee);

  public void deleteEmpById(Integer id);
  
}

EmployeeMapper.xml

<?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 -->
<mapper namespace="org.demo.mapper.EmployeeMapper">

   <select id="getEmpById" resultType="org.demo.po.Employee">
    select * from employee where id = #{id}
  </select>

  <!-- public void addEmp(Employee employee); 
    插入自增返回主键 -->
  <insert id="addEmp" parameterType="org.demo.po.Employee"
    useGeneratedKeys="true" keyProperty="id" >
    insert into employee(last_name,email,gender) 
    values(#{lastName},#{email},#{gender})
  </insert>
  
  <!-- public void updateEmp(Employee employee);  -->
  <update id="updateEmp">
    update employee 
    set last_name=#{lastName},email=#{email},gender=#{gender}
    where id=#{id}
  </update>
  
  <!-- public void deleteEmpById(Integer id); -->
  <delete id="deleteEmpById">
    delete from employee where id=#{id}
  </delete>
  
</mapper>
  • resultType:返回结果类型,可以做别名映射
  • parameterType:参数类型,可以省略,
  • 获取自增主键的值:
    • mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
    • useGeneratedKeys=“true”;使用自增主键获取主键值策略
    • keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性

2、映射文件-参数处理

4种映射文件,多个参数会被封装成 一个map

<mapper namespace="org.demo.mapper.EmployeeMapper">
  <!--在mybat配置文件配置好typeAliases后,resultType即可使用别名-->
  <!-- 多个参数,不能直写id或lastName,否则抛出 org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [1, 0, param1, param2]-->
   <select id="getEmpByIdAndLastName" resultType="org.demo.po.Employee">
     select * from tbl_employee where id = #{id} and last_name=#{lastName}
   </select>
   <select id="getEmpByIdAndLastName2" resultType="org.demo.po.Employee">
     select * from employee where id = #{0} and last_name=#{1}
   </select>
   <select id="getEmpByIdAndLastName3" resultType="org.demo.po.Employee">
     select * from employee where id = #{param1} and last_name=#{param2}
   </select>
   <select id="getEmpByIdAndLastName4" resultType="org.demo.po.Employee">
     select * from employee where id = #{id} and last_name=#{lastName}
   </select>
  ...
public interface EmployeeMapper {
    
  public Employee getEmpByIdAndLastName(Integer id, String name);
  public Employee getEmpByIdAndLastName2(Integer id, String name);
  public Employee getEmpByIdAndLastName3(Integer id, String name);
  public Employee getEmpByIdAndLastName4(@Param("id")Integer id,@Param("lastName")String name);
  ...

参数处理#{}与${}取值区别

  • #{} : 是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
  • ${} : 取出的值直接拼装在sql语句中;会有安全问题;

3、流式查询

3.1 简介

流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用。如果没有流式查询,我们想要从数据库取 1000 万条记录而又没有足够的内存时,就不得不分页查询,而分页查询效率取决于表设计,如果设计的不好,就无法执行高效的分页查询。

流式查询的过程当中,数据库连接是保持打开状态的,因此要注意的是:执行一个流式查询后,数据库访问框架就不负责关闭数据库连接了,需要应用在取完数据后自己关闭

3.2 流式查询接口

EmployeeMapper创建mapper方法,这里提供两种方式

// MySQL 默认情况下,创建 prepareStatement 时,就已经是 ResultSet.TYPE_FORWARD_ONLY 和 ResultSet.CONCUR_READ_ONLY ,所以这两个参数可加可不加
// @Select("select * from employee")
// @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
// @ResultType(Employee.class)
void getEmpList(ResultHandler<Employee> resultHandler);

基于xml的方式

<!--FORWORD_ONLY 结果集的游标只能向下滚动-->
<!--SCROLL_INSENSITIVE 结果集的游标可以上下移动,当数据库变化时,当前结果集不变-->
<!--SCROLL_SENSITIVE 返回可滚动的结果集,当数据库变化时,当前结果集同步改变-->
<!--fetchSize MySQL驱动需要这个数字来明确开启流处理方式-->
<select id="getEmpList" resultType="Employee" resultSetType="FORWARD_ONLY" fetchSize="-2147483648">
    select * from employee
</select>

测试用例

// 执行数据库操作
AtomicInteger num = new AtomicInteger();
mapper.getEmpList(resultContext -> {
    //逐条返回
    Employee employee = resultContext.getResultObject();
    /**
    * 业务代码
    */
    num.getAndIncrement();
    System.out.println("第" + num + "次查询," + employee.toString());
});

3.3 流式查询接口–Cursor

MyBatis 提供了一个叫 org.apache.ibatis.cursor.Cursor 的接口类用于流式查询,这个接口继承了 java.io.Closeablejava.lang.Iterable 接口,由此可知:

  • Cursor 是可关闭的,实际上当关闭 Cursor 时,也一并将数据库连接关闭了;

  • Cursor 是可遍历的

除此之外,Cursor 还提供了三个方法:

  • isOpen():用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据;
  • isConsumed():用于判断查询结果是否全部取完;
  • getCurrentIndex():返回已经获取了多少条数据。

因为 Cursor 实现了迭代器接口,因此在实际使用当中,从 Cursor 取数据非常简单:

try(Cursor cursor = mapper.querySomeData()) {
    cursor.forEach(rowObject -> {
        // ...
    });
}

使用 try-resource 方式可以令 Cursor 自动关闭。

3.4 流式查询接口–Cursor实现

首先定义好mapper方法

@Select("select * from employee limit #{limit}")
Cursor<Employee> scan(@Param("limit") int limit);

因为Cursor在取数据的过程中需要保持数据库连接,而 Mapper 方法通常在执行完后连接就关闭了,因此 Cusor 也一并关闭了,这里可能会报错,所以在spring整合的时候,有三种方法可以选择

  • SqlSessionFactory

    用 SqlSessionFactory 来手工打开数据库连接。

    @GetMapping("scan/1/{limit}")
    public void scanFoo1(@PathVariable("limit") int limit) throws Exception {
        try (
            SqlSession sqlSession = sqlSessionFactory.openSession();  // 1
            Cursor<Employee> cursor = 
                  sqlSession.getMapper(EmployeeMapper.class).scan(limit)   // 2
        ) {
            cursor.forEach(e -> { });
        }
    }
    

    上述代码中,1 处我们开启了一个 SqlSession (实际上也代表了一个数据库连接),并保证它最后能关闭;2 处我们使用 SqlSession 来获得 Mapper 对象。这样才能保证得到的 Cursor 对象是打开状态的

  • TransactionTemplate

    在 Spring 中,我们可以用 TransactionTemplate 来执行一个数据库事务,这个过程中数据库连接同样是打开的

    @GetMapping("scan/2/{limit}")
    public void scanFoo2(@PathVariable("limit") int limit) throws Exception {
        TransactionTemplate transactionTemplate = 
                new TransactionTemplate(transactionManager);  // 1
    
        transactionTemplate.execute(status -> {               // 2
            try (Cursor<Employee> cursor = EmployeeMapper.scan(limit)) {
                cursor.forEach(e -> { });
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        });
    }
    

    上面的代码中,1 处我们创建了一个 TransactionTemplate 对象(此处 transactionManager 是怎么来的不用多解释,本文假设读者对 Spring 数据库事务的使用比较熟悉了),2 处执行数据库事务,而数据库事务的内容则是调用 Mapper 对象的流式查询。注意这里的 Mapper 对象无需通过 SqlSession 创建。

  • @Transactional 注解

    @GetMapping("scan/3/{limit}")
    @Transactional
    public void scanFoo3(@PathVariable("limit") int limit) throws Exception {
        try (Cursor<Employee> cursor = EmployeeMapper.scan(limit)) {
            cursor.forEach(e -> { });
        }
    }
    

    本质上和方案二,只在外部调用时生效。在当前类中调用这个方法,依旧会报错。

四、Mybatis 高级操作(一对多、多对一)

1、封装映射

1.1 map(select-记录封装)

public interface EmployeeMapper {

  //多条记录封装一个map:Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javaBean
  //@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
  @MapKey("lastName")
  public Map<String, Employee> getEmpByLastNameLikeReturnMap(String lastName);
  
  //返回一条记录的map;key就是列名,值就是对应的值
  public Map<String, Object> getEmpByIdReturnMap(Integer id);
  ...

1.2 自定义结果映射规则(select-resultMap)

<mapper namespace="org.demo.mapper.EmployeeMapper">

  <!--自定义某个javaBean的封装规则
  type:自定义规则的Java类型
  id:唯一id方便引用
    -->
  <resultMap id="MySimpleEmp" type="org.demo.po.Employee">
    <!--指定主键列的封装规则
    id定义主键会底层有优化;
    column:指定哪一列
    property:指定对应的javaBean属性
      -->
    <id column="id" property="id"/>
    <!-- 定义普通列封装规则 -->
    <result column="last_name" property="lastName"/>
    <!-- 其他不指定的列会自动封装:我们只要写resultMap就把全部的映射规则都写上。 -->
    <result column="email" property="email"/>
    <result column="gender" property="gender"/>
  </resultMap>
  
  <!-- resultMap:自定义结果集映射规则;  -->
  <!-- public Employee getEmpById(Integer id); -->
  <select id="getEmpByIdWithResultMap"  resultMap="MySimpleEmp">
    select * from employee where id=#{id}
  </select>

2、关联查询

实体类和数据库如上述配置

2.1 多对一处理

创建EmployeeMapper接口文件

public interface EmployeeMapper {

    Employee getEmpAndDepart(Integer id);

    List<Employee> getEmpAndDepart1();

    List<Employee> getEmpAndDepart2();

    @Select("select * from employee where id = #{id}")
    @Options
    Employee getEmpById(@Param("id") Integer id);
}

mapper.xml文件(多对一或一对一)这里有三种查询方式

  • 联合查询
  • 指定联合的javaBean对象查询
  • 分步查询
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.EmployeeMapper">

    <!-- 联合查询:级联属性封装结果集-->
    <resultMap id="MyEmployee" type="org.demo.po.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="id" property="department.id"/>
        <result column="department_name" property="department.departmentName"/>
    </resultMap>

    <!--  association可以指定联合的javaBean对象 -->
    <resultMap id="MyEmployee1" type="org.demo.po.Employee">
        <result column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <!--  association可以指定联合的javaBean对象
        property="department":指定哪个属性是联合的对象
        javaType:指定这个属性对象的类型[不能省略]
        -->
        <association property="department" javaType="Department">
            <id column="id" property="id"/>
            <result column="department_name" property="departmentName"/>
        </association>
    </resultMap>

    <!-- association分步查询  -->
    <!-- 使用association进行分步查询:
      1、先按照员工id查询员工信息
      2、根据查询员工信息中的did值去部门表查出部门信息
      3、部门设置到员工中;
     -->
    <resultMap id="MyEmployee2" type="org.demo.po.Employee">
        <result column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <!-- association定义关联对象的封装规则
           select:表明当前属性是调用select指定的方法查出的结果
           column:指定将哪一列的值传给这个方法
           流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
        -->
        <association column="did" property="department" javaType="Department" select="getDepart">
            <id column="id" property="id"/>
            <result column="department_name" property="departmentName"/>
        </association>
    </resultMap>
    
    <!--  public Employee getEmpAndDept(Integer id);-->
    <select id="getEmpAndDepart" resultMap="MyEmployee">
        SELECT *
        FROM employee e, department d
        WHERE e.did=d.id AND e.id=#{id}
    </select>


    <select id="getEmpAndDepart1" resultMap="MyEmployee">
        select * from employee e, department d WHERE e.did =d.id
    </select>

    <select id="getEmpAndDepart2" resultMap="MyEmployee">
        select * from employee
    </select>

    <select id="getDepart" resultType="Department">
        select * from department where id = #{did}
    </select>
    
</mapper>

延迟加载

我们每次查询Employee对象的时候,都将一起查询出来。部门信息在我们使用的时候再去查询;分段查询的基础之上加上两个配置:在全局配置文件中配置,实现懒加载

<configuration>
  ...
  <settings>
    ...
    <!--显示的指定每个我们需要更改的配置的值,即使他是默认的。防止版本更新带来的问题  -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
  </settings>

2.2 一对多处理

创建DepartmentMapper.xml,一对多关联查询-collection,有两种

  • 联合查询
  • 分步查询
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.DepartmentMapper">

    <!--嵌套结果集的方式,使用collection标签定义关联的集合类型的属性封装规则  -->
    <resultMap type="org.demo.po.Department" id="MyDeptWithEmps">
        <id column="id" property="id"/>
        <result column="department_name" property="departmentName"/>
        <!--
          collection定义关联集合类型的属性的封装规则
          ofType:指定集合里面元素的类型
        -->
        <collection property="emps" ofType="org.demo.po.Employee" javaType="java.util.List">
            <!-- 定义这个集合中元素的封装规则 -->
            <id column="e_id" property="id"/>
            <result column="last_name" property="lastName"/>
            <result column="email" property="email"/>
            <result column="gender" property="gender"/>
        </collection>
    </resultMap>

    <!-- collection:分段查询 -->
    <resultMap type="org.demo.po.Department" id="MyDeptStep">
        <id column="id" property="id"/>
        <id column="department_name" property="departmentName"/>
        <!--多列传值column="{key1=column1,key2=column2}"-->
        <collection property="emps"
                    select="getEmpByDid"
                    column="id">
        </collection>
    </resultMap>

    <!-- Department MyDept(Integer id); -->
    <!--这里因为两个id重名了,Mybatis无法分辨,所以需要重命名一下-->
    <select id="MyDept" resultMap="MyDeptWithEmps">
        SELECT d.*, e.*,e.id as e_id
        FROM department d LEFT JOIN employee e ON d.id=e.did
        WHERE d.id=#{id}
    </select>

    <!--Department getDeptStep(Integer id);-->
    <select id="getDeptStep" resultMap="MyDeptStep">
        select * from department
        where id = #{id}
    </select>

    <select id="getEmpByDid" resultType="org.demo.po.Employee">
        select * from employee where did = #{id}
    </select>
</mapper>

3、鉴别器

discriminator鉴别器,创建EmployeeMapper1.xml

<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.EmployeeMapper1">

    <!-- =======================鉴别器============================ -->
    <!-- <discriminator javaType=""></discriminator>
      鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
      封装Employee:
        如果查出的是女生:就把部门信息查询出来,否则不查询;
        如果是男生,把last_name这一列的值赋值给email;
     -->
    <resultMap type="org.demo.po.Employee" id="MyEmpDis">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="email" property="email"/>
        <result column="gender" property="gender"/>
        <!--
          column:指定判定的列名
          javaType:列值对应的java类型,忽视大小写
          -->
        <discriminator javaType="string" column="gender">
            <!--女生  resultType:指定封装的结果类型;不能缺少。/resultMap-->
            <case value="0" resultType="org.demo.po.Employee">
                <association property="department"
                             select="getDeptById"
                             column="did" fetchType="eager" >
                </association>
            </case>
            <!--男生 ;如果是男生,把last_name这一列的值赋值给email; -->
            <case value="1" resultType="org.demo.po.Employee">
                <id column="id" property="id"/>
                <result column="last_name" property="lastName"/>
                <result column="last_name" property="email"/>
                <result column="gender" property="gender"/>
            </case>
        </discriminator>
    </resultMap>

    <!--  List<Employee> getEmpsWithDiscriminator();-->
    <select id="getEmpsWithDiscriminator" resultMap="MyEmpDis">
        select * from employee
    </select>

    <select id="getDeptById" resultType="org.demo.po.Department">
        select * from department where id = #{did}
    </select>
</mapper>

4、动态sql

4.1 简介

MyBatis采用功能强大的基于 OGNL 的表达式来简化操作。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

严格来说,在XML中只有”<”和”&”是非法的,需要转义,其中转义字符

&lt;<
&gt;>
&amp;&
&apos;
&quot;"

4.2 if判断

<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.DynamicSQLMapper">

    <!-- 查询员工,要求,携带了哪个字段查询条件就带上这个字段的值 -->
    <!-- public List<Employee> getEmpsByConditionIf(Employee employee); -->
    <select id="getEmpsByConditionIf" resultType="org.demo.po.Employee">
        select * from employee where 1=1
        <!-- test:判断表达式(OGNL)
        OGNL参照PPT或者官方文档。
             c:if  test
        从参数中取值进行判断
        遇见特殊符号应该去写转义字符:
        &&:
        -->
        <if test="id!=null">
            and id=#{id}
        </if>
        <if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
            and last_name like #{lastName}
        </if>
        <if test="email!=null and email.trim()!=&quot;&quot;">
            and email=#{email}
        </if>
        <!-- ognl会进行字符串与数字的转换判断  "0"==0 -->
        <if test="gender==0 or gender==1">
            and gender=#{gender}
        </if>
    </select>
    <!--
    查询的时候如果某些条件没带可能sql拼装会有问题
    1. 给where后面加上1=1,以后的条件都and xxx。
    2. mybatis使用where标签来将所有的查询条件包括在内。mybatis就会将where标签中拼装的sql,
    多出来的and或者or去掉(where只会去掉第一个多出来的and或者or,但最后一个多出来的and或者or则不会去掉)。
      -->
    <select id="getEmpsByConditionIfWithWhere" resultType="org.demo.po.Employee">
        select * from employee
        <where>
            <if test="id!=null">
                id=#{id}
            </if>
            <if test="lastName!=null and lastName!=&quot;&quot;">
                and last_name like #{lastName}
            </if>
            <if test="email!=null and email.trim()!=&quot;&quot;">
                and email=#{email}
            </if>
            <!-- ognl会进行字符串与数字的转换判断  "0"==0 -->
            <if test="gender==0 or gender==1">
                and gender=#{gender}
            </if>
        </where>
    </select>

    <!--Integer updateEmp(Employee employee);-->
    <update id="updateEmp">
        <!-- Set标签的使用 -->
        update employee
        <set>
            <if test="lastName!=null">
                last_name=#{lastName},
            </if>
            <if test="email!=null">
                email=#{email},
            </if>
            <if test="gender!=null">
                gender=#{gender}
            </if>
        </set>
        where id=#{id}
        <!--
          Trim:更新拼串
          update employee
          <trim prefix="set" suffixOverrides=",">
            <if test="lastName!=null">
              last_name=#{lastName},
            </if>
            <if test="email!=null">
              email=#{email},
            </if>
            <if test="gender!=null">
              gender=#{gender}
            </if>
          </trim>
          where id=#{id}  -->
    </update>

</mapper>

4.3 trim-自定义字符串截取

SQL语句前面或后面多出的and或者**or **where标签不能解决

  • prefix="":前缀:trim标签体中是整个字符串拼串后的结果。
    • prefix给拼串后的整个字符串加一个前缀
  • prefixOverrides=""
    • 前缀覆盖: 去掉整个字符串前面多余的字符
  • suffix="":后缀
    • suffix给拼串后的整个字符串加一个后缀
  • suffixOverrides=""
    • 后缀覆盖:去掉整个字符串后面多余的字符
<!--public List<Employee> getEmpsByConditionTrim(Employee employee);  -->
<select id="getEmpsByConditionTrim" resultType="org.demo.po.Employee">
    select * from employee
    <!-- 自定义字符串的截取规则 -->
    <trim prefix="where" suffixOverrides="and">
        <if test="id!=null">
            id=#{id} and
        </if>
        <if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
            last_name like #{lastName} and
        </if>
        <if test="email!=null and email.trim()!=&quot;&quot;">
            email=#{email} and
        </if>
        <!-- ognl会进行字符串与数字的转换判断  "0"==0
         最后的and会自动剪裁-->
        <if test="gender==0 or gender==1">
            gender=#{gender} and
        </if>
    </trim>
</select>

4.4 choose-分支选择

<mapper namespace="com.lun.c04.dynamicsql.DynamicSQLMapper">

<!-- public List<Employee> getEmpsByConditionChoose(Employee employee); -->
    <select id="getEmpsByConditionChoose" resultType="org.demo.po.Employee">
        select * from employee
        <where>
            <!-- 如果带了id就用id查,如果带了lastName就用lastName查;只会进入其中一个 -->
            <choose>
                <when test="id!=null">
                    id=#{id}
                </when>
                <when test="lastName!=null">
                    last_name like #{lastName}
                </when>
                <when test="email!=null">
                    email = #{email}
                </when>
                <otherwise>
                    gender = 0
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>

4.5 foreach-遍历集合

  • collection:指定要遍历的集合:
    • list类型的参数会特殊处理封装在map中,map的key就叫list
  • item:将当前遍历出的元素赋值给指定的变量
  • separator:每个元素之间的分隔符
  • open:遍历出所有结果拼接一个开始的字符
  • close:遍历出所有结果拼接一个结束的字符
  • index:索引。遍历list的时候是index就是索引,item就是当前值
    • 遍历map的时候index表示的就是map的key,item就是map的值
  • #{变量名}就能取出变量的值也就是当前遍历出的元素
<!--public List<Employee> getEmpsByConditionForeach(List<Integer> ids);  -->
<select id="getEmpsByConditionForeach" resultType="org.demo.po.Employee">
    select * from employee
    <foreach collection="ids" item="item_id" separator=","
             open="where id in(" close=")">
        #{item_id}
    </foreach>
</select>

<!--List<Employee> selectEmp(Map map)
map的结构是{"list",list},list里是id
-->
<select id="selectEmp" parameterType="map" resultType="org.demo.po.Employee">
    select * from employee
    <where>
        <foreach collection="list" item="id" open="(" separator="or" close=")">
            id = #{id}
        </foreach>
    </where>
</select>

<!--List<Employee> selectEmp1(Map map)
map的结构是{"list",list},list里是id
-->
<select id="selectEmp1" parameterType="map" resultType="org.demo.po.Employee">
    select * from employee
    <where>
        id in
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </where>
</select>

<!-- 批量保存 -->
<!--public void addEmps(@Param("emps")List<Employee> emps);  -->
<!--MySQL下批量保存:可以foreach遍历   mysql支持values(),(),()语法-->
<insert id="addEmps">
    insert into employee(last_name,email,gender,did)
    values
    <foreach collection="emps" item="emp" separator=",">
        (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
    </foreach>
</insert><!--   -->

<!-- 这种方式需要数据库连接属性allowMultiQueries=true;
  这种分号分隔多个sql可以用于其他的批量操作(删除,修改) -->
<insert id="addEmps2">
    <foreach collection="emps" item="emp" separator=";">
        insert into employee(last_name,email,gender,did)
        values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
    </foreach>
</insert>

4.6 sql抽取可重用的sql片段

抽取可重用的sql片段,方便后面引用:

  1. sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
  2. include来引用已经抽取的sql:
  3. include还可以自定义一些property,sql标签内部就能使用自定义的属性
    • include-property:取值的正确方式${prop},
    • 不能使用#{},而使用${}
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.DynamicSQLMapper">
    <sql id="employColumns">${alias}.id,${alias}.last_name,${alias}.gender,${alias}.email </sql>

    <select id="selectEmployeeBySql" resultType="org.demo.po.Employee">
        select
        <include refid="employColumns"><property name="alias" value="t1"/></include>
        from employee t1
    </select>

    <sql id="insertColumn">
        <if test="_databaseId=='oracle'">
            last_name,email,gender,did
        </if>
        <if test="_databaseId=='mysql'">
            last_name,email,gender,did
        </if>
    </sql>

    <insert id="addEmps3">
        insert into employee(
        <include refid="insertColumn"></include><!-- 使用地方 -->
        )
        values
        <foreach collection="emps" item="emp" separator=",">
            (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
        </foreach>
    </insert>
</mapper>

五、缓存

MyBatis官方文档

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

MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。

  1. 默认情况下,只有一级缓存( SqlSession级别的缓存,也称为本地缓存)开启。
  2. 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  3. 为了提高扩展性。 MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
  4. 对于Mabtis查询,查询过程默认是二级缓存->一级缓存->数据库

六、Mybatis逆向工程

1、逆向工程创建

pom.xml中引入逆向工程MBG依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.1</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.7</version>
    </dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.7</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.7</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>8.0.16</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

在项目的resources目录下创建generatorConfig.xml,修改部分内容,最后执行插件generate目标即可

<?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="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/learnmybatis?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="org.demo.pojo" 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="org.demo.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="department" domainObjectName="Department"/>
        <table tableName="employee" domainObjectName="Employee"/>
    </context>
</generatorConfiguration>

2、MBG相关类CURD

  • selectByExample

    按条件查询,需要传入一个example对象或者null;如果传入一个null,则表示没有条件,也就是查询所有数据

  • example.createCriteria().xxx

    创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系

  • example.or().xxx

    将之前添加的条件通过or拼接其他条件

  • updateByPrimaryKey

    通过主键进行数据修改,如果某一个值为null,也会将对应的字段改为null

  • updateByPrimaryKeySelective()

    通过主键进行选择性数据修改,如果某个值为null,则不修改这个字段

七、分页插件

1、插件配置

首先在pom.xml中引入相关依赖

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

mybatis-config.xml设置分页插件

<plugins>
	<!--设置分页插件-->
	<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

2、插件使用

2.1 直接输出

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

  • pageNum:当前页的页码
  • pageSize:每页显示的条数
//访问第一页,每页四条数据
Page<Object> page = PageHelper.startPage(1, 4);
List<Emp> emps = mapper.selectByExample(null);
//在查询到List集合后,打印分页数据
System.out.println(page);

2.2 使用PageInfo

在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, intnavigatePages)获取分页相关数据

  • list:分页之后的数据
  • navigatePages:导航分页的页码数
PageHelper.startPage(1, 4);
List<Emp> emps = mapper.selectByExample(null);
PageInfo<Emp> page = new PageInfo<>(emps,5);
System.out.println(page);

3、分页常用数据

  • pageNum:当前页的页码
  • pageSize:每页显示的条数
  • size:当前页显示的真实条数
  • total:总记录数
  • pages:总页数
  • prePage:上一页的页码
  • nextPage:下一页的页码
  • isFirstPage/isLastPage:是否为第一页/最后一页
  • hasPreviousPage/hasNextPage:是否存在上一页/下一页
  • navigatePages:导航分页的页码数
  • navigatepageNums:导航分页的页码,[1,2,3,4,5]

参考资料与其他文章

源码仓库

具体源码分析

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值