MyBatis学习笔记

廉价感情. 提交于 2020-02-06 08:47:34

mybatis简介

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

HelloWorld

基本流程

1、创建全局配置文件,配置mybatis的一些操作

2、创建sql映射文件,该文件中配置了每个sql,以及sql的封装规则

3、将该sql映射文件注册到全局配置文件中

4、根据全局配置文件创建SqlSessionFactory对象,并且获取SqlSession对象

5、使用SqlSession对象执行写好的sql操作

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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!--将sql映射文件注册到全局配置文件中-->
    <mappers>
        <mapper resource="conf/employeeMapper.xml"/>
    </mappers>
</configuration>

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="cn.jason.mybatis.employeeMapping">
    <!--namespace 名称空间
    id : 唯一标识这条sql语句
    resultType : 将查询到的信息映射成什么类型进行返回
    #{id} :从传递过来的参数中取出id值作为sql的参数
    -->
    <select id="selectEmp" resultType="cn.jason.domain.Employee">
        select * from tbl_employee where id = #{id}
    </select>
</mapper>

每一个mybatis的应用都是以一个SqlSessionFactory的实例为核心

获取SqlSession对象执行操作

public void test01() throws IOException {
    String resource = "conf/mybatisConfig.xml" ;
    InputStream inputStream = 
        Resources.getResourceAsStream(resource) ;
    SqlSessionFactory  sqlSessionFactory = new 
        SqlSessionFactoryBuilder().build(inputStream);
    //sqlSession代表和数据库的一次会话
    SqlSession sqlSession = null ;
    Employee employee = null ;
    try{
        //可以执行已经映射的sql语句
        sqlSession = sqlSessionFactory.openSession() ;
        //第一个参数传一个已经映射的sql的唯一标识
        //第二个参数传递的是该sql语句所要使用的参数
        employee = sqlSession.selectOne("cn.jason.mybatis.employeeMapping.selectEmp", 1);
    }finally {
        if(sqlSession != null)
            sqlSession.close();
    }
    System.out.println(employee);
}

接口式编程

好处

1、使用接口的方法拥有更强的类型检查

2、把DAO层与实现分离

sql映射文件中namespace指定为接口的全类名,使该映射文件与接口进行绑定,其中每个sql语句的id指定为该接口中的某个方法,使该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="cn.jason.dao.EmployeeMapper">

    <!--Employee getEmpById(Integer id) ;
        id 指定为 方法名
    -->
    <select id="getEmpById" resultType="cn.jason.domain.Employee">
        select * from tbl_employee where id = #{id}
    </select>
</mapper>

cn.jason.dao.EmployeeMapper接口

package cn.jason.dao;
import cn.jason.domain.Employee;
public interface EmployeeMapper {
    Employee getEmpById(Integer id) ;
}

先从SqlSession对象中获取该接口的实现类(代理对象),然后调用该接口中的方法即可

public void test02(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession();
    //获取映射接口的实现类对象(代理对象)
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    //调用该接口的方法
    Employee emp = mapper.getEmpById(1);
    session.close();
    System.out.println(emp);
}

一个xxx接口对应一个xxxMapper.xml 文件,在调用动态代理时,该代理对象执行xml中的sql并且传入参数和封装结果,我们只要专注于sql的编写即可。

全局配置文件

properties标签

mybatis可以使用properties标签引入properties配置文件的内容

用${} 取出properties中的值

<!--
     resource 引入类路径下的资源
     url 引入网络资源的内容
-->
<properties resource="conf/dbconfig.properties"/>
<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${mysql.driver}"/>
            <property name="url" value="${mysql.url}"/>
            <property name="username" value="${mysql.username}"/>
            <property name="password" value="${mysql.password}"/>
        </dataSource>
    </environment>
</environments>

properties配置文件

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=root

settings标签

settings标签包含很多设置项,很直接影响到mybatis的行为

1、mapUnderscoreToCamelCase 将数据库下划线字段映射成经典的Java属性名,例如 a_column -> aColumn 默认是false

typeAliases标签给类型起别名

typeAlias 为某个类起别名

<typeAliases>
    <typeAlias type="cn.jason.domain.Employee" alias="emp"/>
</typeAliases>

然后在sql映射文件中只需要使用较短的别名即可

<select id="getEmpById" resultType="emp">
    select * from tbl_employee where id = #{id}
</select>

package 为某个包下的所有类以及子包中的类起默认别名,默认别名为类名小写,若包中有Employee类且子包中也有Employee的话,都被映射成employee 所以会产生类型冲突

<typeAliases>
    <!--为某个包下的所有类以及子包起默认别名,默认别名为类名小写-->
    <package name="cn.jason.domain"/>
</typeAliases>
<select id="getEmpById" resultType="employee">
    select * from tbl_employee where id = #{id}
</select>

environments标签 配置多种环境 default 标识所要引用的环境

每一个environment标签配置具体的环境信息,id代表当前环境的唯一标识

<environments default="development">
    <environment id="development">
        <!-- 指定事物管理器,默认提供JDBC和MANAGED两种。如果要自定义事物管理器,实现transactionFactory接口,type指定为全类名 		
-->
        <transactionManager type="JDBC"/>
        <!-- 数据源,默认有UNPOOLED、POOLED和JNDI。如果需要自定义连接池,实现DataSourceFactory,type指定为全类名。例如我们想使用druid连接池,那么就实现实现DataSourceFactory接口-->
        <dataSource type="POOLED">
        </dataSource>
    </environment>
</environments>

databaseIdProvider标签 用于支持多数据库厂商

mappers标签 将sql映射文件注册到全局配置中

<!--将sql映射文件注册到全局配置文件中-->
<mappers>
    <!--resource引用类路径下的映射文件
    url引用网络路径或者磁盘路径下的映射文件
    -->
    <mapper resource="conf/employeeMapper.xml"/>
    <mapper resource="conf/employeeMapper.xml"/>
        <!--批量注册 name写接口文件所在的的包名
            前提条件是接口文件与映射文件在同一包名下,否则无法找到映射文件-->
        <package name="cn.jason.dao.EmployeeMapper"/>
</mappers>

映射文件

简单的增删改

接口文件

package cn.jason.dao;
import cn.jason.domain.Employee;
public interface EmployeeMapper {
    // mybatis允许怎增删改直接定义以下类型返回值
    // Integer、Long和Boolean以及它们对应的基本类型
    // 增删改返回的是影响了多少行,如果是Boolean类型,影响行数大于0返回true,否则返回false
    Employee getEmpById(Integer id) ;
    Boolean addEmp(Employee employee) ;
    void updateEmp(Employee employee) ;
    void deleteEmpById(Integer id) ;
}

sql映射文件

<mapper namespace="cn.jason.dao.EmployeeMapper">
    <select id="getEmpById" resultType="employee">
        select * from tbl_employee where id = #{id}
    </select>
    <!--void addEmp(Employee employee) ;-->
    <insert id="addEmp" parameterType="employee">
        insert into tbl_employee(last_name,email,gender)
        values(#{lastName},#{email},#{gender})
    </insert>
    <!--void updateEmp(Employee employee) ;-->
    <update id="updateEmp" >
        update tbl_employee
        set last_name = #{lastName},email=#{email},gender=#{gender}
        where id = #{id}
    </update>
    <!--void deleteEmpById(Integer id) ;-->
    <delete id="deleteEmpById">
      delete from tbl_employee
      where id = #{id}
    </delete>
</mapper>

测试代码

public void test(){
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //参数为true代表自动提交事务
        SqlSession session = sqlSessionFactory.openSession(true);
        EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
        //测试添加
//        mapper.addEmp(new Employee(null,"jerry","jerry@qq.com","1"));
        //测试修改
//        mapper.updateEmp(new Employee(7,"Mio","Mio@qq.com","1"));
        //测试删除
        mapper.deleteEmpById(7);
        session.close();
    }

添加完员工信息后获取该员工的自增主键值并进行设置

<!--当添加操作完成后获取主键并且赋值给传递进来的javaBean对象的主键属性
    useGeneratedKeys="true" 获取自增主键
    keyProperty : 指定对应的主键属性,mybatis获取到主键值后,将这个值封装给javaBean对象
    这里是添加完成后将主键id获取然后赋值给传递进入来employee对象
-->
<insert id="addEmp" parameterType="employee"
        useGeneratedKeys="true" keyProperty="id">
    insert into tbl_employee(last_name,email,gender)
    values(#{lastName},#{email},#{gender})
</insert>
EmployeeMapper mapper = 
    session.getMapper(EmployeeMapper.class);
//测试添加
Employee employee = new 
    Employee(null,"jerry","jerry@qq.com","1") ;
mapper.addEmp(employee);
//Employee{id=9, lastName='jerry', emial='jerry@qq.com', gender='1'}
System.out.println(employee);

参数处理

传递单个参数

​ 如果传递单个参数mybatis不会做特殊的处理

​ #{参数名},取出参数值

传递多个参数

总结

​ 传递多个参数的话,mybatis会封装成一个Map

​ 有@Param注解指定Key的名称可以使用#{注解名称}来获取值或者#{paramN}来获取

​ 没有使用注解的话只能使用#{paramN}来获取了

​ 操作

<!--Employee getEmpByIdAndLastName(Integer id,String lastName) ; -->
<select id="getEmpByIdAndLastName">
    select * from tbl_employee
    where id = #{id}
    and last_name = #{lastName}
</select>

​ 异常

Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]

当传递多个参数时,mybatis会做特殊的处理,多个参数会被封装成一个map,而#{}就是从这个map中取出指定的key的值。

这个map的key是:param1…paramN或者参数的索引(0,1…N)

命名参数

更好的做法是使用命名参数,在封装map时明确指定key是什么

Employee getEmpByIdAndLastName(@Param("id") Integer id,@Param("lastName") String lastName) ;

通过这种方式明确指定封装map时key 的名称,那么在取值时就可以#{参数名}

POJO

取出对象中的属性值 #{属性值}

Map的方式

可以将数据打包成一个Map进行传递,取出时#{Key}

TO

如果多个参数不是业务模型中的数据,但是要经常使用,可以创建一个TO(Transfer Object)来传递数据

Employee getEmp(@Param("id")Integer id,String lastName) ; 
取值id ===> #{id}  lastName ===> #{param2}

Employee getEmp(Integer id,Employee emp) ; 

取值 id ===> #{param1}  lastName===>#{param2.lastName}

传递Collection(List、Set)类型或者数组

如果传递的是Collection(List、Set)类型或者数组,mybatis会做特殊处理

把它们封装到map中。

对应的Key:

​ Collection(collection) ,如果是List那么Key就是list

​ 数组的key是array

例如

	Employee getEmpById(List<Integer> ids) ; 
	取值就是:#{list[0]/list[1]/.../last[n]}

#{}与${}取值的区别

​ #{}:是以预编译的形式,将参数设置到sql语句中,PreparedStatement (预编译的方式)

​ ${}:取出的值直接封装到sql语句中, Statement (拼装sql语句)

​ 原生jdbc不支持占位符的地方我们就可以使用${}进行取值,例如动态分表查询 select * from ,{} , {}、排序… ,因为表名那不能使用占位符,所以只能使用拼装sql的方式。

​ 大部分情况下还是应该使用#{}取值

select 元素

在这里插入图片描述

返回的是List

<!--List<Employee> getEmpsByLastNameLike(String lastName) ;如果返回的是一个集合,要写集合中的元素类型
mybatis会把每一条数据封装成一个元素对象并且把它们放到集合中
resultType 指定为你要把一条记录封装成什么类型的对象
-->
<select id="getEmpsByLastNameLike" resultType="employee">
    select * from tbl_empployee
    WHERE  last_name
    LIKE  #{lastName}
</select>

返回的是Map

<!--Map<String,Object> getEmpInfoById(Integer id) ;
	封装成一个map进行放回
	查询到的一行数据中的列名作为key,对应列的值作为value 
-->
<select  id="getEmpInfoById" resultType="map">
    select * from tbl_employee
    where id = #{id}
</select>
public void test02(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    Map<String,Object> empInfoById = mapper.getEmpInfoById(1);
 //结果:[gender=0, d_id=1, last_name=Harry1, id=1, email=Harry@qq.com]
    System.out.println(empInfoById.entrySet());
    session.close();
}

多条记录封装成一个map

DAO

//多条记录封装成一个map
//Map<Integer,Employee> Key是这条记录的主键,值是经过封装后的JavaBean对象
@MapKey("id") //告诉mybatis封装这个map的时候使用那个属性作为主键
Map<Integer,Employee> getEmpsByLastNameLikeReturnMap(String lastName) ;

Mapper

<!--多条记录封装成一个map Map<Integer,Employee> Key是这条记录的主键,值是经过封装后的JavaBean对象
Map<Integer,Employee> getEmpsByLastNameLikeReturnMap(String lastName) ;
resultType 指定为你要把一条记录封装成什么类型的对象
-->
<select id="getEmpsByLastNameLikeReturnMap" resultType="Employee">
     select * from tbl_employee
     WHERE  last_name
     LIKE  #{lastName}
</select>

Test

public void test03(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    //参数为true代表自动提交事务
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    Map<Integer,Employee> empInfoById = mapper.getEmpsByLastNameLikeReturnMap("%rr%");
    System.out.println(empInfoById.entrySet());
    session.close();
}

结果

[1=Employee{id=1, lastName='Harry1', emial='Harry@qq.com', gender='0'},
9=Employee{id=9, lastName='jerry', emial='jerry@qq.com', gender='1'}]

resultMap自定义结果集

实体集

public class Employee {
    private Integer id ;
    private String lastName ;
    private String email  ;
    private String gender ;
}
<!--resultMap 自定义封装结果集的规则
type:指定自定义映射规则的类型,例如这里要为Employee类型自定义规则
id:该映射规则的唯一标识
-->
<resultMap id="myEmp" type="employee">
    <!--指定主键列的封装规则
    id定义主键底层会有优化
        column:指定哪一列
        property:指定对应的javaBean属性
    -->
    <id column="id" property="id"/>
    <!--result 定义普通列的映射规则-->
    <result column="last_name" property="lastName"/>
    <!--其他不指定的字段则自动封装(字段与属性名对应)-->
</resultMap>
<!--Employee getEmpById(Integer id) ; -->
<select id="getEmpById" resultMap="myEmp">
    select * from tbl_employee
    WHERE  id = #{id}
</select>

级联查询

场景:查询员工信息的同时查出对应的部门信息

实体类

public class Employee {
    private Integer id ;
    private String lastName ;
    private String email  ;
    private String gender ;
    private Department dept ;
}
public class Department {
    private Integer id ;
    private String departmentName ;
}

DAO

public interface EmployeeMapper {
    Employee getEmpAndDeptById(Integer id) ;
}

实现

<!--
    查询employee的同时查出对应的部门
    Employee getEmpAndDeptById(Integer id) ;
        id    last_name   gender    d_id    did   dept_name
        1      Harry1        0        1       1     开发部
-->
<!-- 使用级联属性封装结果集 -->
<resultMap id="MyEmp" type="employee">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <result column="did" property="dept.id"/>
    <result column="dept_name" property="dept.departmentName"/>
</resultMap>

<select id="getEmpAndDeptById" resultMap="MyEmp">
  SELECT e.id id,e.`last_name` last_name ,e.`gender` gender , e.`d_id` d_id,
  d.id did,d.dept_name dept_name  FROM tbl_employee e,tbl_dept d
  WHERE e.`d_id` = d.id AND e.id = #{id}
</select>

结果

Employee{id=1, lastName='Harry1', email='null', gender='0', dept=Department{id=1, departmentName='开发部'}}

association(联合的方式)

实现

<!--

    查询employee的同时查出对应的部门
    Employee getEmpAndDeptById(Integer id) ;
        id    last_name   gender    d_id    did   dept_name
        1      Harry1        0        1       1     开发部
-->
<resultMap id="MyEmp" type="employee">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <!--association定义单个属性对象的封装规则-->
    <!--property 指定employee中哪个属性是联合对象
        javaType 指定联合对象的类型(全类名或者别名)
        然后在association里面定义department的封装规则
    -->
    <association property="dept" javaType="department">
        <id column="did" property="id"/>
        <result column="dept_name" property="departmentName"/>
    </association>
</resultMap>
<select id="getEmpAndDeptById" resultMap="MyEmp">
  SELECT e.id id,e.`last_name` last_name ,e.`gender` gender , e.`d_id` d_id,
  d.id did,d.dept_name dept_name  FROM tbl_employee e,tbl_dept d
  WHERE e.`d_id` = d.id AND e.id = #{id}
</select>

使用association进行分步查询

组合已有的方法来完成复杂功能

<mapper namespace="cn.jason.dao.DepartmentMapper">
    <!--Department getDeptById(Integer id) ;-->
    <select id="getDeptById" resultType="department">
        select id ,dept_name as departmentName  from tbl_dept
        where id = #{id}
    </select>
</mapper>
<mapper namespace="cn.jason.dao.EmployeeMapper">
    <!--
        查询employee的同时查出对应的部门
        Employee getEmpAndDeptById(Integer id) ;
            id    last_name   gender    d_id    did   dept_name
            1      Harry1        0        1       1     开发部

        使用association进行分部查询
        1、先按照员工id查询员工信息
        2、根据员工信息中的d_id去部门表查出部门信息
        3、部门信息设置到员工中
    -->
    <resultMap id="MyEmp" type="employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <!--association定义单个属性对象的封装规则
        select属性 表明当前dept属性是通过调用其指定的方法进行查询和封装的
        select ==> 调用cn.jason.dao.DepartmentMapper中的getDeptById方法
        column 指定将哪一列的值传递给select指定的方法

        流程:使用select指定的方法,传入column指定列的值,查出对象,并封装给property指定的属性
        -->
        <association property="dept" select="cn.jason.dao.DepartmentMapper.getDeptById"
        column="d_id">

        </association>
    </resultMap>
    <select id="getEmpAndDeptById" resultMap="MyEmp">
      select * from tbl_employee
      where id = #{id}
    </select>
</mapper>

从日志可以看到发送了两条查询语句

DEBUG 2020-01-08 14:31:3393 ==>  Preparing: select * from tbl_employee where id = ? 
DEBUG 2020-01-08 14:31:33130 ==> Parameters: 1(Integer)
DEBUG 2020-01-08 14:31:33146 ====>  Preparing: select id ,dept_name as departmentName from tbl_dept where id = ? 
DEBUG 2020-01-08 14:31:33146 ====>  Parameters: 1(Integer)
DEBUG 2020-01-08 14:31:33146 <====      Total: 1
DEBUG 2020-01-08 14:31:33146 <====      Total: 1

使用延迟加载

​ 需求:部门信息在我们使用的时候再去查询

​ 实现:在分部查询的基础之上加上两个配置

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载-->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

Test

public void test(){
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession(true);
        EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
        Employee emp = mapper.getEmpAndDeptById(1);
    	//class :class cn.jason.domain.Employee_$$_jvst272_0 
    	//返回给我们employee的代理对象
    	System.out.println("class :"+emp.getClass());
        System.out.println("id : "+emp.getId());
        System.out.println("lastName :"+emp.getLastName());
        System.out.println("dept :"+emp.getDept());
        session.close();
    }

结果以及日志信息(在使用dept这个关联对象时再去查询)

DEBUG 2020-01-08 14:50:18624 ==>  Preparing: select * from tbl_employee where id = ? 
DEBUG 2020-01-08 14:50:18707 ==> Parameters: 1(Integer)
DEBUG 2020-01-08 14:50:18879 <==      Total: 1
id : 1
lastName :Harry1
DEBUG 2020-01-08 14:50:18880 ==>  Preparing: select id ,dept_name as departmentName from tbl_dept where id = ? 
DEBUG 2020-01-08 14:50:18882 ==> Parameters: 1(Integer)
DEBUG 2020-01-08 14:50:18892 <==      Total: 1
dept :Department{id=1, departmentName='开发部'}

原理是动态代理,mybatis返回给我们的不是JavaBean对象,而是JavaBean的代理对象

这个代理对象在我们需要返回改数据的时候再帮我们去数据库查询数据(懒加载)

代理对象会给我们提供对应的服务,但是其实现方式是隐蔽的,客户代码不知道也不需要知道它是通过哪种手段去完成任务,只知道它能确确实实的完成指定的任务)

collection定义集合类型的属性的封装规则

需求:查询部门的时候将部门对应的所有员工信息也查询出来(嵌套结果集的方式 )

实现:使用collection定义关联的集合类型的属性封装规则

实体类

public class Department {

    private Integer id ;
    private String departmentName ;
    private List<Employee>  emps ;
}
public class Employee {
    private Integer id ;
    private String lastName ;
    private String email  ;
    private String gender ;
}

DAO

public interface DepartmentMapper {
    Department getDeptByIdPlus(Integer id) ;
}

实现

<!--   did  dept_name     eid  lastName     email         gender
        1  开发部           1    Harry1    Harry@qq.com      0
        1  开发部           10     Tom       Tom@qq.com      1
 -->
<resultMap id="myDept" type="department">
    <id column="did" property="id"/>
    <result column="dept_name" property="departmentName"/>
    <!--collection定义集合类型的属性的封装规则
        property 指定要封装集合的具体属性
        ofType   指定集合里面的元素类型
    -->
    <collection property="emps" ofType="employee">
        <!--定义集合中元素的封装规则-->
        <id column="eid" property="id"/>
        <result column="lastName" property="lastName"/>
        <result column="email" property="email"/>
        <result column="gender" property="gender"/>
    </collection>

</resultMap>
<!--Department getDeptByIdPlus(Integer id) ;-->
<select id="getDeptByIdPlus" resultMap="myDept">
    SELECT d.`id` did,d.`dept_name` dept_name,
    e.id eid, e.last_name lastName,e.`email` email , e.gender gender
    FROM tbl_dept d
    LEFT JOIN tbl_employee e
    ON d.id = e.`d_id`
    WHERE d.`id`=#{id}
</select>

使用collection进行分部查询

和association的使用方法差不多,包括延迟加载

<mapper namespace="cn.jason.dao.EmployeeMapper">
    <!--List<Employee> getEmpsByDeptId(Integer deptId) ;-->
    <select id="getEmpsByDeptId" resultType="employee">
        select id,last_name lastName ,email,gender from tbl_employee
        where d_id = #{id}
    </select>
</mapper>
<mapper namespace="cn.jason.dao.DepartmentMapper">
    <!--Department getDeptById(Integer id) ;-->
    <select id="getDeptById" resultType="department">
        select id ,dept_name as departmentName  from tbl_dept
        where id = #{id}
    </select>

    <resultMap id="myDept" type="department">
        <id column="did" property="id"/>
        <result column="dept_name" property="departmentName"/>
        <collection property="emps" ofType="employee" select="cn.jason.dao.EmployeeMapper.getEmpsByDeptId" column="id">
        </collection>

    </resultMap>
    <!--Department getDeptByIdPlus(Integer id) ;-->
    <select id="getDeptByIdPlus" resultMap="myDept">
        select id,dept_name departmentName from tbl_dept
        where id = #{id}
    </select>
</mapper>
public void test01(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    DepartmentMapper mapper = session.getMapper(DepartmentMapper.class);
    Department deptByIdPlus = mapper.getDeptByIdPlus(1);
    System.out.println(deptByIdPlus);
    session.close();
}

结果以及日志(先查部门信息,再查员工信息)

DEBUG 2020-01-08 18:15:40570 ==>  Preparing: select id,dept_name departmentName from tbl_dept where id = ? 
DEBUG 2020-01-08 18:15:40600 ==> Parameters: 1(Integer)
DEBUG 2020-01-08 18:15:40661 <==      Total: 1
DEBUG 2020-01-08 18:15:40662 ==>  Preparing: select id,last_name lastName ,email,gender from tbl_employee where d_id = ? 
DEBUG 2020-01-08 18:15:40662 ==> Parameters: 1(Integer)
DEBUG 2020-01-08 18:15:40675 <==      Total: 2
Department{id=null, departmentName='开发部', emps=[Employee{id=1, lastName='Harry1', email='Harry@qq.com', gender='0', dept=null}, Employee{id=10, lastName='Tom', email='Tom@qq.com', gender='1', dept=null}]}

在做分步查询的时候,如果查询关联对象需要传递多个值,可以将多个参数封装成map进行传递

<collection property="emps" ofType="employee" 
            select="cn.jason.dao.EmployeeMapper.getEmpsByDeptId" 
            column="{key1=column1,key2=column2,...,keyN=columnN}">
</collection>

discriminator鉴别器

mybatis可以使用鉴别器判断某列的值,然后根据某列的值改变封装行为

<!--
mybatis可以使用鉴别器判断某列的值,然后根据某列的值改变封装行为
例子,封装Employee
    如果查出的是女生,就把部门信息查询出来,否则不查询
    如果是男生,把lastName的值赋值给email
-->
<resultMap id="MyEmpDis" type="employee">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <result column="email" property="email"/>
    <!--column 	指定进行判断的列名
        javaType 列值对应的java类型
    -->
    <discriminator javaType="string" column="gender">
        <!--女生  resultType 指定封装的结果类型-->
        <case value="1" resultType="employee">
            <association property="dept" select="cn.jason.dao.DepartmentMapper.getDeptById"
                         column="d_id">
            </association>
        </case>
        <!--男生-->
        <case value="0" resultType="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>
<!--     id  last_name  gender  email   d_id   -->
<select id="getEmpsByDeptId" resultMap="MyEmpDis">
    select * from tbl_employee
    where d_id = #{d_id}
</select>

动态SQL

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

MyBatis 采用功能强大的基于 OGNL 的表达式

  • if 判断
  • choose (when, otherwise) 分支选择
  • trim (where(封装查询条件), set(封装修改条件)) 自字符串截取
  • foreach

在这里插入图片描述

if

public interface EmployeeMapperDynamicSQL {
    List<Employee> getEmpsByConditionIF(Employee employee);
}

第一种方法,给where后面加上1=1,以后的条件用and连接

<!--if-->
    <!--List<Employee> getEmpsByConditionIF(Employee employee);-->
    <select id="getEmpsByConditionIF" resultType="employee">
        select * from tbl_employee
        where 1=1
            <!--test 判断表达式(OGNL) 从参数中取值进行判断
                遇见特殊符号应该去写转义字符
                例如 && ===> &amp;&amp;
            -->
        <if test="id!=null">and id = #{id}</if>
        <if test="lastName!=null and lastName!=''">
            and last_name like #{lastName}
        </if>
        <if test="email !=null and email.trim()!=''">
            and email = #{email}
        </if>
        <!--ognl会进行字符串与数字的转换判断-->
        <if test="gender==0 or gender==1">
            and gender= #{gender}
        </if>
    </select>
public void test(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
    Employee employee = new Employee() ;
    employee.setLastName("%rr%");
    List<Employee> empsByConditionIF = mapper.getEmpsByConditionIF(employee);
    for (int i = 0; i < empsByConditionIF.size(); i++) {
        System.out.println(empsByConditionIF.get(i));
    }
    session.close();
}

发出的SQL语句(select * from tbl_employee where last_name like ? )

DEBUG 2020-01-09 12:56:01992 ==>  Preparing: select * from tbl_employee where 1=1 and last_name like ? 
DEBUG 2020-01-09 12:56:0216 ==> Parameters: %rr%(String)
DEBUG 2020-01-09 12:56:0256 <==      Total: 2
Employee{id=1, lastName='Harry1', email='Harry@qq.com', gender='0', dept=null}
Employee{id=9, lastName='jerry', email='jerry@qq.com', gender='1', dept=null}

第二种方法

mybatis使用where标签将所有的查询条件包括在内,将前面多出来的and或or去掉

<select id="getEmpsByConditionIF" resultType="employee">
    select * from tbl_employee
    <where>
        <if test="id!=null">id = #{id}</if>
        <if test="lastName!=null and lastName!=''">
            and last_name like #{lastName}
        </if>
        <if test="email !=null and email.trim()!=''">
            and email = #{email}
        </if>
        <!--ognl会进行字符串与数字的转换判断-->
        <if test="gender==0 or gender==1">
            and gender= #{gender}
        </if>
    </where>
</select>

第三种方式

<!--List<Employee> getEmpsByConditionIF(Employee employee);-->
<!--
    prefix 前缀 prefix 给拼接后的字符串加一个前缀
    prefixOverrides 前缀覆盖 去掉整个字符串前面多余的字符
    suffix 后缀 给拼接后的整个字符串加后缀
    suffixOverrides 后缀覆盖 去掉整个字符串后面多余的字符
-->
<select id="getEmpsByConditionIF" resultType="employee">
    select * from tbl_employee
    <trim prefix="where" prefixOverrides="and" suffix="" suffixOverrides="">
        <if test="id!=null">and id = #{id}</if>
        <if test="lastName!=null and lastName!=''">
            and last_name like #{lastName}
        </if>
        <if test="email !=null and email.trim()!=''">
            and email = #{email}
        </if>
        <!--ognl会进行字符串与数字的转换判断-->
        <if test="gender==0 or gender==1">
            and gender= #{gender}
        </if>
    </trim>
</select>
Preparing: select * from tbl_employee where id = ? and last_name like ? 

choose

<select id="getEmpsByCondition" resultType="employee">
    select * from tbl_employee
    <where>
        <choose>
            <when test="id!=null">
                id = #{id}
            </when>
            <when test="lastName!=null">
                and last_name like #{lastName}
            </when>
            <when test="email!=null">
                and email=#{email}
            </when>
            <otherwise>
                and 1=1
            </otherwise>
        </choose>
    </where>
</select>
public void test(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
    Employee employee = new Employee() ;
    employee.setId(1);
    employee.setLastName("%rr%");
    List<Employee> empsByConditionIF = mapper.getEmpsByCondition(employee);
    for (int i = 0; i < empsByConditionIF.size(); i++) {
        System.out.println(empsByConditionIF.get(i));
    }
    session.close();
}

发出的SQL语句

DEBUG 2020-01-09 13:36:2740 ==>  Preparing: select * from tbl_employee WHERE last_name like ? 
DEBUG 2020-01-09 13:36:2770 ==> Parameters: %rr%(String)

set封装修改方法

set标签将条件后面多余的“,”去掉

<update id="updateEmp">
    UPDATE tbl_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}
</update>
public void test(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
    Employee employee = new Employee() ;
    employee.setId(1);
    employee.setLastName("Harry");
    employee.setEmail("Harry@qq.com");
    boolean b = mapper.updateEmp(employee);
    System.out.println(b);
    session.close();
}
DEBUG 2020-01-09 13:48:32946 ==>  Preparing: UPDATE tbl_employee SET last_name=?, email=? WHERE id = ? 

也可以使用trim

<update id="updateEmp">
    UPDATE tbl_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>

foreach

动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。

<!--List<Employee> getEmpsbyConditionForeach(List<Integer> list) ;
       collection 指定要遍历的集合
            list类型的参数会特殊处理封装在map中,map的key就叫list
        item 将当前遍历的元素赋值给指定的变量
            #{item} 能取出当前元素
        separator 指定元素之间的分隔符
        open 指定以什么字符开始
        close 指定以什么字符结束
        index 索引
            遍历list的时候就是索引
            遍历map的时候index就是key,item对应key的就是值
-->
    <select id="getEmpsbyConditionForeach" resultType="employee">
      select * from tbl_employee where id in
      <foreach collection="list" item="item_id" separator="," open="(" close=")">
          #{item_id}
      </foreach>
    </select>
public void test(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);

    List<Integer> list = new ArrayList<Integer>() ;
    list.add(1) ;
    list.add(2) ;
    list.add(9) ;
    List<Employee> empsbyConditionForeach = mapper.getEmpsbyConditionForeach(list);
    for (int i = 0; i < empsbyConditionForeach.size(); i++) {
        System.out.println(empsbyConditionForeach.get(i));
    }
    session.close();
}

结果以及日志信息

DEBUG 2020-01-09 14:17:32601 ==>  Preparing: select * from tbl_employee where id in ( ? , ? , ? ) 
DEBUG 2020-01-09 14:17:32649 ==> Parameters: 1(Integer), 2(Integer), 9(Integer)
DEBUG 2020-01-09 14:17:32673 <==      Total: 2
Employee{id=1, lastName='Harry', email='Harry@qq.com', gender='0', dept=null}
Employee{id=9, lastName='jerry', email='jerry@qq.com', gender='1', dept=null}

批量添加数据

<!--批量插入
例如,插入两个员工的信息
INSERT INTO tbl_employee(last_name,email,gender,d_id)
VALUES("Tom","Tom@qq.com","1",1),("Frank","Frank@qq.com","0",2)
-->
<!--void addEmps(List<Employee> emps) ;-->
<insert id="addEmps">
    INSERT INTO tbl_employee(last_name,email,gender,d_id)
     VALUES
      <foreach collection="list" item="emp" separator=",">
          (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
      </foreach>
</insert>
public void test(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);

    List<Employee> list = new ArrayList<Employee>() ;
    list.add(new Employee(null,"Tom","Tom@qq.com","0",new Department(1,null,null))) ;
    list.add(new Employee(null,"Mio","Mio@qq.com","1",new Department(2,null,null))) ;

    boolean result = mapper.addEmps(list);
    System.out.println(result);
    session.close();
}
DEBUG 2020-01-09 15:09:46295 ==>  Preparing: INSERT INTO tbl_employee(last_name,email,gender,d_id) VALUES (?,?,?,?) , (?,?,?,?) 
DEBUG 2020-01-09 15:09:46441 ==> Parameters: Tom(String), Tom@qq.com(String), 0(String), 1(Integer), Mio(String), Mio@qq.com(String), 1(String), 2(Integer)
DEBUG 2020-01-09 15:09:46655 <==    Updates: 2
true

两个内置参数

<!--
两个内置参数
不只是方法传递过来的参数可以被用来判断,取值...
mybatis默认还有两个内置参数
    _parameter:代表整个参数
        传递单个参数:_parameter就是这个参数
        传递多个参数===>封装成一个map:_parameter代表这个map

    _databaseId:如果配置了全局databaseIdProvider标签
        _databaseId就是代表当前数据库的别名

-->
<!--List<Employee> getEmpsTestInnerParamter(Employee employee) ; -->
<select id="getEmpsTestInnerParamter" resultType="employee">
      <if test="_databaseId=='mysql'">
          select * from tbl_employee
          <if test="_parameter!=null">
              where last_name = #{_parameter.lastName}
          </if>
      </if>
      <if test="_databaseId=='oracle'">
            select * from employess
      </if>
</select>

bind标签

可以将OGNL表达式的值绑定到一个变量中,方便后面引用这个变量的值

<select id="getEmpsTestInnerParameter" resultType="employee">
    <bind name="_lastName" value="'%'+lastName+'%'"></bind>
    select * from tbl_employee
    <if test="_parameter!=null">
        where last_name like #{_lastName}
    </if>
</select>
public void test(){
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
    Employee employee = new Employee() ;
    employee.setLastName("rr");
    List<Employee> empsTestInnerParameter = mapper.getEmpsTestInnerParameter(employee);
    for (int i = 0; i < empsTestInnerParameter.size(); i++) {
        System.out.println(empsTestInnerParameter.get(i));
    }
    session.close();
}
DEBUG 2020-01-09 15:59:09544 ==>  Preparing: select * from tbl_employee where last_name like ? 
DEBUG 2020-01-09 15:59:09575 ==> Parameters: %rr%(String)
DEBUG 2020-01-09 15:59:09590 <==      Total: 2

sql标签

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

使用include标签引用外部定义的sql

<sql id="insertColumn">
  last_name,email,gender,d_id
</sql>


<insert id="addEmps">
        INSERT INTO tbl_employee(
    
          <include refid="insertColumn"/>
        )
         VALUES
          <foreach collection="list" item="emp" separator=",">
              (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
          </foreach>
    </insert>


<!--Employee getEmpById(Integer id) ; -->
    <select id="getEmpById" resultType="employee">
        select <include refid="selectColumn"/>
        from tbl_employee
        where id = #{id}
    </select>

<sql id="selectColumn">
        id,last_name lastName,email,gender
</sql>

​ include还可以定义一些property,sql标签内部就能使用自定义的属性

​ ${prop}

<sql id="selectColumn">
    id,last_name lastName,email,gender,${testColumn}
</sql>

<!--Employee getEmpById(Integer id) ; -->
    <select id="getEmpById" resultType="employee">
        select
          <include refid="selectColumn">
            <property name="testColumn" value="abc"/>
          </include>
        from tbl_employee
        where id = #{id}
    </select>

结果
### SQL: select id,last_name lastName,email,gender,abc               from tbl_employee         where id = ?

缓存机制

mybatis系统中定义了两级缓存,一级缓存和二级缓存

public void test(){
    /*
    * 两级缓存
    * 一级缓存(本地缓存) sqlSession级别的缓存,一级缓存是一直开启	  * 的,没法关闭
    *   与数据库头一次会话期间查询到的数据会放在本地缓存中
    *   如果需要再获取相同的数据,直接再内存中取,没必要再去查询数据库
    * 二级缓存(全局缓存)
    *
    * */
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession session = sqlSessionFactory.openSession(true);
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);

    //查询一号员工
    Employee emp01 = mapper.getEmpById(1);
    System.out.println(emp01);

    //xxx其他业务逻辑
    //再次查询一号员工
    Employee emp02 = mapper.getEmpById(1);
    System.out.println(emp02);
    System.out.println(emp01==emp02);
}

输出与日志

DEBUG 2020-01-09 19:32:0758 ==>  Preparing: select * from tbl_employee where id = ? 
DEBUG 2020-01-09 19:32:0789 ==> Parameters: 1(Integer)
DEBUG 2020-01-09 19:32:07111 <==      Total: 1
Employee{id=1, lastName='Harry', email='Harry@qq.com', gender='0', dept=null}
Employee{id=1, lastName='Harry', email='Harry@qq.com', gender='0', dept=null}
true

可以看到只发送了一次sql请求,并且第二次查询获得的员工对象,它就是第一次查询得到的对象

第二次查询并没有发送sql也没有创建对象,而是将第一次查询封装的结果赋值给第二个引用

一级缓存失效情况(没有使用到当前一级缓存的情况,效果是还需要向数据库发出查询)

  • sqlSession对象不同
  • sqlSession相同,查询条件不同
  • sqlSession相同,两次查询期间执行了增删改
  • sqlSession相同,但是手动清除了一级缓存(sqlSession对象.clearCache())

二级缓存

二级缓存(全局缓存):基于namespace级别的缓存,一个名称空间对应一个二级缓存

工作机制

  • 一个会话:查询一条数据,这个数据就会被放在当前会话的一级缓存中

  • 如果会话关闭,一级缓存的数据会被保存到二级缓存中,新的会话查询信息就可以参照二级缓存中的内容

  • sqlSession既有EmployeeMapper查询的Employee 也有 DepartmentMapper查的Department

  • 那么不同namespace查出的数据会放在自己对应的缓存中

    使用:

  • 开启全局二级缓存配置 setting cacheEnabled

  • 到mapper.xml配置使用二级缓存,添加cache标签

<mapper namespace="cn.jason.dao.EmployeeMapper">
    <cache eviction="" flushInterval="" size="" readOnly="" type="" blocking="" >
    </cache>
</mapper>

在这里插入图片描述

注意,查出的数据默认会被放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

和缓存有关的设置/属性:

  1. cacheEnabled=true/false 打开/关闭二级缓存
  2. 每个select标签都有useCache的属性打开/关闭二级缓存,该查询方法是否使用缓存
  3. 每个增删改标签都有一个flushCache属性,增删改之后是否清除缓存(两级缓存都会被清空)
  4. 每个select标签也有flushCache属性,但是默认为false
  5. sqlSession对象.clearCache() 只是清除该对象的缓存(一级缓存)

如果要关闭一级缓存,那么设置localCacheScope(作用域)为statement

localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。

新会话要查找数据先去查看二级缓存中是否有对应的数据

在这里插入图片描述

在这里插入图片描述

mybatis与第三方缓存工具的整合,mybatis提供了cache接口,只要实现这个接口即可

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    ReadWriteLock getReadWriteLock();
}

与第三方缓存工具的整合

  1. 导入第三方缓存包

  2. 导入第三方缓存整合的适配包,mybatis官方有

  3. 到mybatis官网查看与第三方工具的整合方式

  4. 在mapper.xml中使用第三方缓存

    <cache type="org.mybatis.caches.ehcache.EhcacheCache">
    

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!