Mybatis 详解

一、简介

1. MyBatis是什么

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

2. MyBatis历史

  • 原是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis ,代码于2013年11月迁移到Github(下载地址见后)。
  • iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

3. 为什么要使用MyBatis?

  • JDBC

    • SQL夹在Java代码块里,耦合度高导致硬编码内伤;
    • 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见。
  • Hibernate 和 JPA

    • 长难复杂SQL,对于Hibernate而言处理也不容易;
    • 内部自动生产的SQL,不容易做特殊优化;
    • 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难,导致数据库性能下降。
  • MyBatis是一个半自动化的持久化层框架

    • 对开发人员而言,核心sql还是需要自己优化;
    • sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据。

二、基本配置

1. 创建数据库表

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

2. 添加依赖

<dependency>
    <groupId>org.mybatisgroupId>
    <artifactId>mybatisartifactId>
    <version>3.4.1version>
dependency>

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>6.0.6version>
dependency>

3. 创建对应的JavaBean

public class Employee{

    private Integer id;
    private String lastName;
    private String email;
    private String gender;
	
    ......

}

4. 创建mybatis配置文件,sql映射文件

  • MyBatis 的全局配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息、如数据库连接池信息等。指导着MyBatis进行工作。我们可以参照官方文件的配置示例。
  • 映射文件的作用就相当于是定义Dao接口的实现类如何工作。这也是我们使用MyBatis时编写的最多的文件
1)mybatis-config.xml

DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="dev">
        <environment id="dev">
            <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>
        <mapper resource="mapper/EmployeeMapper.xml" />
    mappers>
configuration>

2)sql映射文件

DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="abc">
    
    <select id="getEmpById" resultType="com.example.mybatis.pojo.Employee">
        select id,last_name lastName,email,gender from employee where id = #{id}
    select>
mapper>

5. 测试

/**
 * 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 有数据源一些运行环境信息
 * 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。
 * 3、将sql映射文件注册在全局配置文件中
 * 4、写代码:
 * 1)、根据全局配置文件得到SqlSessionFactory;
 * 2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查一个sqlSession就是代表和数据库的一次会话,用完关闭
 * 3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
 *
 * @throws IOException
 */
@Test
public void test() throws IOException {

    // 2、获取sqlSession实例,能直接执行已经映射的sql语句
    // sql的唯一标识:statement Unique identifier matching the statement to use.
    // 执行sql要用的参数:parameter A parameter object to pass to the statement.
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

    SqlSession openSession = sqlSessionFactory.openSession();
    try {
        Employee employee = openSession.selectOne(
                "abc.getEmpById", 1);
        System.out.println(employee);
    } finally {
        openSession.close();
    }
}

三、Mybatis 全局配置文件

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

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

1. properties-引入外部配置文件

<configuration>

    <properties resource="application.properties"/>
configuration>

application.properties

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

如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:

  • 在 properties 元素体内指定的属性首先被读取。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根
    据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。

2. settings-运行时行为设置

这是 MyBatis 中极为重要的调整设置,它们会改变MyBatis 的运行时行为。

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    <setting name="cacheEnabled" value="true"/>
settings>
设置参数 描述 有效值 默认值
cacheEnabled 该配置影响的所有映射器中配置的缓存的全局开关。 true/false TRUE
lazyLoadingEnabled 延迟加载的全局开关。当开启时。所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 true/false FALSE
useColumnLabel 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 true/false TRUE
defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数。 Any positive integer Not Set (null)
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则( camel case )映射即从经典数据库列名A_ COLUMN到经典Java属性名aColumn的类似映射 true/false FALSE

3. typeAliases-别名

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

<configuration>
	...
	<typeAliases>
		<typeAlias alias="Author" type="domain.blog.Author"/>
		<typeAlias alias="Blog" type="domain.blog.Blog"/>
		<typeAlias alias="Comment" type="domain.blog.Comment"/>
		<typeAlias alias="Post" type="domain.blog.Post"/>
		<typeAlias alias="Section" type="domain.blog.Section"/>
		<typeAlias alias="Tag" type="domain.blog.Tag"/>
	typeAliases>
configuration>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<configuration>
	...
	<typeAliases>
		<package name="domain.blog"/>
	typeAliases>
configuration>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

值得注意的是, MyBatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。

4. typeHandlers-类型处理器

无论是 MyBatis 在预处理语句( PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERIC 或 LONG INTEGER
FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR

日期类型的处理

日期和时间的处理, JDK1.8以前一直是个头疼的问题。我们通常使用JSR310规范领导者Stephen Colebourne创建的Joda-Time来操作。 1.8已经实现全部的JSR310规范了。日期时间处理上,我们可以使用MyBatis基于JSR310( Date and Time API)编写的各种日期时间类型处理器。MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的

<typeHandlers>
	<typeHandler handler="org.apache.ibatis.type.InstantTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.LocalDateTimeTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.LocalDateTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.LocalTime TypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.0ffsetDateTimeTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.OffsetTimeTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.ZonedDateTimeTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.YearTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.MonthTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.YearMonthTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.JapaneseDateTypeHandler" />
typeHandlers>

自定义类型处理器步骤

  1. 实现org.apache.ibatis.type.TypeHandler接口或者继承org.apache.ibatis.type.BaseTypeHandler
  2. 指定其映射某个JDBC类型(可选操作)
  3. 在mybatis全局配置文件中注册

5. plugins-插件

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。 插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

6. enviroments-运行环境

MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。每种环境使用一个environment标签进行配置并指定唯一标识符。可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境。

  • environment-指定具体环境

    • id:指定当前环境的唯一标识
    • transactionManager、和dataSource都必须有
  • transactionManager

    • type: JDBC | MANAGED | 自定义
      • JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围。JdbcTransactionFactory
      • MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 ManagedTransactionFactory
      • 自定义:实现TransactionFactory接口, type=全类名/别名
  • dataSource
    • type: UNPOOLED | POOLED | JNDI | 自定义
      • UNPOOLED:不使用连接池,
        UnpooledDataSourceFactory
      • POOLED:使用连接池, PooledDataSourceFactory
      • JNDI: 在EJB 或应用服务器这类容器中查找指定的数据源
      • 自定义:实现DataSourceFactory接口,定义数据源的获取方式。
<environments default="dev">
    <environment id="dev">
        <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>
    <environment id="prod">
        <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>

7. databaseIdProvider-多数据库支持

MyBatis 可以根据不同的数据库厂商执行不同的语句。

  • Type: DB_VENDOR 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。
  • Property-name:数据库厂商标识
  • Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引
<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
  <property name="MySQL" value="mysql" />
databaseIdProvider>

8. mappers-sql映射注册

<mappers>
    <mapper resource="mapper/EmployeeMapper.xml" />
    <mapper resource="mapper/EmployeeMapper2.xml" />
    <mapper resource="mapper/DepartmentMapper.xml" />
    <mapper resource="mapper/DynamicSQLMapper.xml" />
mappers>

四、Insert

1. 获取自增主键的值

mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys()

  • useGeneratedKeys=“true”;使用自增主键获取主键值策略;
  • keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性。
<insert id="addEmp" parameterType="com.example.mybatis.pojo.Employee"
        useGeneratedKeys="true" keyProperty="id" >
    insert into employee(last_name,email,gender)
    values(#{lastName},#{email},#{gender})
insert>

2. 参数处理

1)单个参数

mybatis不会做特殊处理,#{参数名/任意名}:取出参数值。

2)多个参数

mybatis会做特殊处理。通常操作:

  • 方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
  • 取值:#{id},#{lastName}

上述操作会抛出异常:org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [1, 0, param1, param2]

解决方法


<select id="getEmpByIdAndLastName" resultType="com.example.mybatis.pojo.Employee">
    select * from employee where id = #{id} and last_name=#{lastName}
select>
<select id="getEmpByIdAndLastName2" resultType="com.example.mybatis.pojo.Employee">
    select * from employee where id = #{0} and last_name=#{1}
select>
<select id="getEmpByIdAndLastName3" resultType="com.example.mybatis.pojo.Employee">
    select * from employee where id = #{param1} and last_name=#{param2}
select>
<select id="getEmpByIdAndLastName4" resultType="com.example.mybatis.pojo.Employee">
    select * from employee where id = #{id} and last_name=#{lastName}
select>
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);
3)POJO&Map&TO

POJO:如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;

  • #{属性名}:取出传入的pojo的属性值

Map:如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map

  • #{key}:取出map中对应的值

<select id="getEmpByMap" resultType="com.example.mybatis.pojo.Employee">
    select * from employee where id = #{id} and last_name=#{lastName}
select>
public Employee getEmpByMap(Map<String, Object> map);

TO:如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象,如:

Page{
	int index;
	int size;
}

五、查询

1. 返回List



<select id="getEmpsByLastNameLike" resultType="com.example.mybatis.pojo.Employee">
    select * from employee where last_name like #{lastName}
select>
public List<Employee> getEmpsByLastNameLike(String str);

2. 记录封装map


<select id="getEmpByLastNameLikeReturnMap" resultType="com.example.mybatis.pojo.Employee">
    select * from employee where last_name like #{lastName}
select>


<select id="getEmpByIdReturnMap" resultType="map">
    select * from employee where id=#{id}
select>
//多条记录封装一个map:Map:键是这条记录的主键,值是记录封装后的javaBean
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("lastName")
public Map<String, Employee> getEmpByLastNameLikeReturnMap(String lastName);

//返回一条记录的map;key就是列名,值就是对应的值
public Map<String, Object> getEmpByIdReturnMap(Integer id);

3. 自定义结果映射规则


<resultMap type="com.example.mybatis.pojo.Employee" id="MySimpleEmp">
   
   <id column="id" property="id"/>
   
   <result column="last_name" property="lastName"/>
   
   <result column="email" property="email"/>
   <result column="gender" property="gender"/>
resultMap>



<select id="getEmpByIdWithResultMap"  resultMap="MySimpleEmp">
   select * from employee where id=#{id}
select>
//自定义结果映射规则
public Employee getEmpByIdWithResultMap(Integer id);

4. 关联查询

1)环境搭建

sql

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

INSERT INTO employee (id, last_name, gender, email) VALUES(0,'shiftycat', 0, 'shiftycat.163.com');
INSERT INTO employee (id, last_name, gender, email) VALUES(2,'shiftlesscat', 1, 'shiftlesscat.163.com');
CREATE TABLE department(
	id int(11) primary key auto_increment,
	department_name varchar(255)
);

ALTER TABLE employee ADD COLUMN department_id int(11);

ALTER TABLE employee ADD CONSTRAINT fk_employee_department 
FOREIGN KEY(department_id) REFERENCES department(id);

INSERT INTO department(department_name) values ('开发部');
INSERT INTO department(department_name) values ('测试部');

Employee类和Department类

public class Employee implements Serializable {

    private Integer id;
    private String lastName;
    private String email;
    private String gender;

    private Department department;
    
    ......
}
public class Department {

    private Integer id;
    private String departmentName;
    private List<Employee> emps;
    
    ......
}
2)级联属性封装结果
<resultMap type="com.example.mybatis.pojo.Employee" id="MyDifEmp">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <result column="department_id" property="department.id"/>
    <result column="department_name" property="department.departmentName"/>
resultMap>


<select id="getEmpAndDept" resultMap="MyDifEmp">
    SELECT
        e.id id,e.last_name last_name,e.gender gender,
        e.department_id department_id, d.department_name department_name
    FROM employee e, department d
    WHERE e.department_id=d.id AND e.id=#{id}
select>
public Employee getEmpAndDept(Integer id);
3)association
(1)定义关联对象

<resultMap type="com.example.mybatis.pojo.Employee" id="MyDifEmp2">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    
    <association property="department" javaType="com.example.mybatis.pojo.Department">
        <id column="department_id" property="id"/>
        <result column="department_name" property="departmentName"/>
    association>
resultMap>


<select id="getEmpAndDept2" resultMap="MyDifEmp2">
    SELECT
        e.id id,e.last_name last_name,e.gender gender,
        e.department_id department_id, d.department_name department_name
    FROM employee e, department d
    WHERE e.department_id=d.id AND e.id=#{id}
select>
public Employee getEmpAndDept2(Integer id);
(2)分步查询
  1. DepartmentMapper.xml

    
    <select id="getDeptById" resultType="com.example.mybatis.pojo.Department">
        select id,department_name departmentName from department where id=#{id}
    select>
    
  2. EmployeeMapper

    
    
    
    <resultMap type="com.example.mybatis.pojo.Employee" id="MyEmpByStep">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="email" property="email"/>
        <result column="gender" property="gender"/>
        
        <association property="department"
                     select="com.example.mybatis.mapper.DepartmentMapper.getDeptById"
                     column="department_id">
        association>
    resultMap>
    
    <select id="getEmpByIdStep" resultMap="MyEmpByStep">
        select * from employee where id=#{id}
    select>
    
(3)延迟加载

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

<settings>
    
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
settings>
4)collection
(1)定义关联集合

<resultMap type="com.example.mybatis.pojo.Department" id="MyDept">
   <id column="did" property="id"/>
   <result column="department_name" property="departmentName"/>
   
   <collection property="emps" ofType="com.example.mybatis.pojo.Employee">
       
       <id column="eid" property="id"/>
       <result column="last_name" property="lastName"/>
       <result column="email" property="email"/>
       <result column="gender" property="gender"/>
   collection>
resultMap>


<select id="getDeptByIdPlus" resultMap="MyDept">
   SELECT d.id did,d.department_name department_name,
          e.id eid,e.last_name last_name,
          e.email email,e.gender gender
   FROM department d LEFT JOIN employee e ON d.id=e.department_id
   WHERE d.id=#{id}
select>
(5)分步查询
  1. DepartmentMapper.xml

    
    <resultMap type="com.example.mybatis.pojo.Department" id="MyDeptStep">
        <id column="id" property="id"/>
        <id column="department_name" property="departmentName"/>
        <collection property="emps"
                    select="com.example.mybatis.mapper.EmployeeMapper.getEmpsByDeptId"
                    column="id">collection>
    resultMap>
    
    
    <select id="getDeptByIdStep" resultMap="MyDeptStep">
        select id,department_name from department where id=#{id}
    select>
    
  2. EmployeeMapper

    
    <select id="getEmpsByDeptId" resultType="com.example.mybatis.pojo.Employee">
        select * from employee where department_id=#{deptId}
    select>
    
(3)延迟加载

在全局配置文件中配置,实现懒加载

<settings>
    
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
settings>
5)分步查询传递多列值

需要将多列的值传递过去,则将多列的值封装map传递:column="{key1=column1,key2=column2}"

fetchType=lazy:表示使用延迟加载;

  • lazy:延迟
  • eager:立即
<resultMap type="com.example.mybatis.pojo.Department" id="MyDeptStep">
    <id column="id" property="id"/>
    <id column="department_name" property="departmentName"/>
    <collection property="emps"
                select="com.example.mybatis.mapper.EmployeeMapper.getEmpsByDeptId"
                column="{deptId=id}" fetchType="lazy">collection>
resultMap>

<select id="getDeptByIdStep" resultMap="MyDeptStep">
    select id,department_name from department where id=#{id}
select>
6)discriminator鉴别器


<resultMap type="com.example.mybatis.pojo.Employee" id="MyEmpDis">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="email" property="email"/>
    <result column="gender" property="gender"/>
    
    <discriminator javaType="string" column="gender">
        
        <case value="0" resultType="com.example.mybatis.pojo.Employee">
            <association property="department"
                         select="com.example.mybatis.mapper.DepartmentMapper.getDeptById"
                         column="department_id" fetchType="eager" >
            association>
        case>
        
        <case value="1" resultType="com.example.mybatis.pojo.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>


<select id="getEmpsWithDiscriminator" resultMap="MyEmpDis">
    select * from employee limit 10
select>

六、动态SQL

1. if 判断



<select id="getEmpsByConditionIf" resultType="com.example.mybatis.pojo.Employee">
    select * from employee where
    
    <if test="id!=null">
        id=#{id}
    if>
    <if test="lastName!=null && lastName!=""">
        and last_name like #{lastName}
    if>
    <if test="email!=null and email.trim()!=""">
        and email=#{email}
    if>
    
    <if test="gender==0 or gender==1">
        and gender=#{gender}
    if>
select>

2. where 条件查询

mybatis使用where标签来将所有的查询条件包括在内。mybatis就会将where标签中拼装的sql,多出来的and或者or去掉(where只会去掉第一个多出来的and或者or,但最后一个多出来的and或者or则不会去掉)。

<select id="getEmpsByConditionIfWithWhere" resultType="com.example.mybatis.pojo.Employee">
    select * from employee
    <where>
        <if test="id!=null">
            id=#{id}
        if>
        <if test="lastName!=null && lastName!=""">
            and last_name like #{lastName}
        if>
        <if test="email!=null and email.trim()!=""">
            and email=#{email}
        if>
        
        <if test="gender==0 or gender==1">
            and gender=#{gender}
        if>
    where>
select>

3. trim 自定义字符串截取

后面多出的and或者or where标签不能解决时:

  • prefix="":给拼串后的整个字符串加一个前缀;
  • prefixOverrides="":前缀覆盖: 去掉整个字符串前面多余的字符;
  • suffix="":给拼串后的整个字符串加一个后缀;
  • suffixOverrides="":后缀覆盖:去掉整个字符串后面多余的字符。

<select id="getEmpsByConditionTrim" resultType="com.example.mybatis.pojo.Employee">
    select * from employee
    
    <trim prefix="where" suffixOverrides="and">
        <if test="id!=null">
            id=#{id} and
        if>
        <if test="lastName!=null && lastName!=""">
            last_name like #{lastName} and
        if>
        <if test="email!=null and email.trim()!=""">
            email=#{email} and
        if>
        
        <if test="gender==0 or gender==1">
            gender=#{gender}
        if>
    trim>
select>

4. choose 分支选择


<select id="getEmpsByConditionChoose" resultType="com.example.mybatis.pojo.Employee">
    select * from employee
    <where>
        
        <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>

5. set 更新


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

6. foreach 遍历集合

  • collection:指定要遍历的集合:
    • list类型的参数会特殊处理封装在map中,map的key就叫list
  • item:将当前遍历出的元素赋值给指定的变量
  • separator:每个元素之间的分隔符
  • open:遍历出所有结果拼接一个开始的字符
  • close:遍历出所有结果拼接一个结束的字符
  • index:索引。遍历list的时候是index就是索引,item就是当前值
    • 遍历map的时候index表示的就是map的key,item就是map的值
  • #{变量名}:就能取出变量的值也就是当前遍历出的元素

<select id="getEmpsByConditionForeach" resultType="com.example.mybatis.pojo.Employee">
    select * from employee
    <foreach collection="ids" item="item_id" separator=","
             open="where id in(" close=")">
        #{item_id}
    foreach>
select>

7. mysql下foreach批量插入的两种方式




<insert id="addEmps">
    insert into employee(last_name,email,gender,department_id)
    values
    <foreach collection="emps" item="emp" separator=",">
        (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
    foreach>
insert>


<insert id="addEmps2">
    <foreach collection="emps" item="emp" separator=";">
        insert into employee(last_name,email,gender,department_id) values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
    foreach>
insert>

8. _parameter & _databaseId

mybatis默认还有两个内置参数

  1. _parameter

    :代表整个参数

    • 单个参数:_parameter就是这个参数
    • 多个参数:参数会被封装为一个map;_parameter就是代表这个map
  2. _databaseId

    :如果配置了databaseIdProvider标签。

    • _databaseId就是代表当前数据库的别名oracle

9. bind 绑定


<bind name="lastName" value="'%'+lastName+'%'"/>

10. 抽取可重用的sql片段

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

  1. sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用;
  2. include来引用已经抽取的sql;
  3. include还可以自定义一些property,sql标签内部就能使用自定义的属性;
    • include-property:取值的正确方式${prop},
    • 不能使用#{},而使用${}
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password sql>

<select id="selectUsers" resultType="map">
	select
		<include refid="userColumns"><property name="alias" value="t1"/>include>,
		<include refid="userColumns"><property name="alias" value="t2"/>include>
	from some_table t1
		cross join some_table t2
select>
<sql id="insertColumn">
	<if test="_databaseId=='oracle'">
		employee_id,last_name,email
	if>
	<if test="_databaseId=='mysql'">
		last_name,email,gender,d_id
	if>
sql>

<insert id="addEmps">
	insert into tbl_employee(
		<include refid="insertColumn">include>
	) 
	values
	<foreach collection="emps" item="emp" separator=",">
		(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
	foreach>
 insert>

七、缓存

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。

  • 默认情况下,只有一级缓存( SqlSession级别的缓存,也称为本地缓存)开启。
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。为了提高扩展性。 MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

1. 一级缓存

一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域。在mybatis3.1之后, 可以配置本地缓存的作用域. 在 mybatis.xml 中配置。

一级缓存失效的四种情况

  1. 不同的SqlSession对应不同的一级缓存;
  2. 同一个SqlSession但是查询条件不同;
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作;
  4. 同一个SqlSession两次查询期间手动清空了缓存。

2. 二级缓存

二级缓存(second level cache),全局作用域缓存。二级缓存默认不开启,需要手动配置。MyBatis提供二级缓存的接口以及实现,缓存实现要求 POJO实现Serializable接口。二级缓存在 SqlSession 关闭或提交之后才会生效。

cache标签的属性

  • eviction:缓存的回收策略:

    • LRU – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

    默认的是 LRU。

  • flushInterval:缓存刷新间隔

    • 缓存多长时间清空一次,默认不清空,设置一个毫秒值
  • readOnly:是否只读:

    • true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
    • false:非只读:mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
  • size:缓存存放多少元素;

  • type=“”:指定自定义缓存的全类名;

    • 实现Cache接口即可;

使用步骤

  1. 全局配置文件中开启二级缓存:

    <settings>
        <setting name="cacheEnabled" value="true"/>
    settings>
    
  2. 需要使用二级缓存的映射文件处使用cache配置缓存:

    <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024">cache>
    
  3. Pojo类实现序列化

    public class Employee implements Serializable {
        private static final long serialVersionUID = -7390587151857533202L;
    
        private Integer id;
        private String lastName;
        private String email;
        private String gender;
        ......
    }
    

3. 缓存有关的设置以及属性

  1. 全局setting的cacheEnable:配置二级缓存的开关,一级缓存一直是打开的。
  2. select标签的useCache属性:配置这个select是否使用二级缓存,一级缓存一直是使用的。
  3. 每个增删改标签的flushCache属性:增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false
  4. sqlSession.clearCache():只是用来清除一级缓存。
  5. 全局setting的localCacheScope本地缓存作用域:(一级缓存SESSION),当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存。

4. 第三方缓存整合

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。MyBatis定义了Cache接口方便我们进行自定义扩展。

package com.example.mybatis.pojo;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {
    String getId();
    void putObject(Object key, Object value);
    Object getObject(Object key);
    Object removeObject(Object key);
    void clear();
    int getSize();
    ReadWriteLock getReadWriteLock();

}

步骤:

  1. 添加依赖:mybatis-ehcache

    <dependency>
        <groupId>org.mybatis.cachesgroupId>
        <artifactId>mybatis-ehcacheartifactId>
        <version>1.2.1version>
    dependency>
    
  2. 编写ehcache.xml配置文件

    
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
        
        <diskStore path="./ehcache" />
    
        <defaultCache
                maxElementsInMemory="10000"
                maxElementsOnDisk="10000000"
                eternal="false"
                overflowToDisk="true"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
        defaultCache>
    
    ehcache>
    
    
        
    
  3. DepartmentMapper.xml

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.mybatis.mapper.DepartmentMapper">
        <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    

此外

若想在命名空间中共享相同的缓存配置和实例,可以使用 cache-ref 元素来引用另外一个缓存。

<mapper namespace="com.example.mybatis.dao.DepartmentMapper">
	
	<cache-ref namespace="com.example.mybatis.dao.EmployeeMapper"/>

巨輪-MyBatis学习笔记:https://blog.csdn.net/u011863024/article/details/107854866

你可能感兴趣的:(Mybatis,mybatis)