MyBatis

MyBatis

简介

JDBC:

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

Hibernate和JPA:

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

对开发人员而言核心SQL还是需要自己优化

SQL和Java编码分开,功能边界清晰,一个专注业务一个专注数据

MyBatis是一个半自动化的持久化层框架:

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

HelloWorld

SqlSession代表和数据库的一次会话;用完必须关闭

SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象

mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象(将接口和xml进行绑定)

两个重要的配置文件:

  • mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等系统运行环境信息
  • sql映射文件:保存了每一个sql语句的映射信息,将sql抽取出来。

配置式编程

操作数据库:

  1. 创建MyBatis全局配置文件:MyBatis的全局配置文件包含了影响MyBatis行为甚深的设置(settings)和属性(properties)信息、如数据库连接池信息等。指导着MyBatis进行工作。我们可以参照官方文件的配置示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?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.cj.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映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中-->
    <mappers>
    <mapper resource="EmployeeMapper.xml"/>
    </mappers>
    </configuration>
  2. 创建SQL映射文件:映射文件的作用就相当于是定义Dao接口的实现类如何 工作。这也是我们使用MyBatis时编写的最多的文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?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="cc.mousse.dao.EmployeeMapper">
    <!--
    namespace:名称空间;指定为接口的全类名
    id:唯一标识
    resultType:返回值类型
    #{id}:从传递过来的参数中取出id值

    public Employee getEmpById(Integer id);
    -->
    <select id="getEmpById" resultType="cc.mousse.bean.Employee">
    select id, last_name lastName, email, gender
    from t_employee
    where id = #{id}
    </select>
    </mapper>

测试:

  1. 根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象,有数据源一些运行环境信息
  2. sql映射文件;配置了每一个sql,以及sql的封装规则等
  3. 将sql映射文件注册在全局配置文件中
  4. 写代码:
    1. 根据全局配置文件得到SqlSessionFactory
    2. 使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
    3. 一个sqlSession就是代表和数据库的一次会话,用完关闭
    4. 使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test() throws IOException {
// 根据全局配置文件,利用SqlSessionFactoryBuilder创建SqlSessionFactory
String resource = "mybatis-3-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 使用SqlSessionFactory获取sqlSession对象,能直接执行已经映射的SQL语句
// 一个SqlSession对象代表和数据库的一次会话
try (SqlSession openSession = factory.openSession()) {
Employee employee = openSession.selectOne("cc.mousse.dao.EmployeeMapper.getEmpById", 1);
System.out.println(employee);
}
}

接口式编程

常用

创建一个Dao接口:

1
2
3
4
5
public interface EmployeeMapper {

Employee getEmpById(Integer id);

}

创建Mapper文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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="cc.mousse.dao.EmployeeMapper">
<!--
namespace:名称空间;指定为接口的全类名
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值

public Employee getEmpById(Integer id);
-->
<select id="getEmpById" resultType="cc.mousse.bean.Employee">
select id, last_name lastName, email, gender
from t_employee
where id = #{id}
</select>
</mapper>

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 1、接口式编程
* 原生: Dao ====> DaoImpl
* mybatis: Mapper ====> xxMapper.xml
*
* 2、SqlSession代表和数据库的一次会话;用完必须关闭;
* 3、SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象。
* 4、mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。
* (将接口和xml进行绑定)
* EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
* 5、两个重要的配置文件:
* mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等...系统运行环境信息
* sql映射文件:保存了每一个sql语句的映射信息:
* 将sql抽取出来。
*/
public void test2() throws IOException {
// 获取sqlSessionFactory对象
String resource = "mybatis-3-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession对象
try (SqlSession openSession = factory.openSession()) {
// 获取接口的实现类对象
// 会为接口自动创建代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);
System.out.println(employee);
}
}

全局配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>

configuration

配置:子标签有先后顺序

properties

属性:可以使用properties标签引入外部properties配置文件的内容

  • resource:引入类路径下的资源
  • url:引入网络或磁盘路径下的资源
1
<properties resource="dbconfig.properties"/>

settings

设置:极为重要的调整设置,它们会改变MyBatis的运行时行为

  • settings:用来设置每一个设置项

    • name:设置项名

      • cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存
      • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态
      • useColumnLabel:使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察
      • defaultStatementTimeout:设置超时时间,它决定数据库驱动等待数据库响应的秒数
      • mapUnderscoreToCamelCase:是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN映射到经典Java属性名aColumn
      • ……
    • value:设置项取值

1
2
3
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

typeAliases

类型命名:类型别名可为Java类型设置一个缩写名字,可以方便引用某个类,别名不区分大小写

  • typeAlias:为某个类起别名
    • type:类型全类名,默认类名小写
    • alias:指定新别名
  • package:为某个包下的类批量起别名,若包和子包中的类名一样则会报错
  • name:为当前包和所有后代包的每个类都起个别名
    • @Alias:在类上加此注解为其制定别名
1
2
3
4
<typeAliases>
<typeAlias type="cc.mousse.bean.Employee" alias="employee"/>
<package name="cc.mousse.bean"/>
</typeAliases>
1
2
@Alias("emp")
public class Employee { }

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

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

typeHandlers

类型处理器:SQL类型与Java类型的映射

类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERICBYTE
ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERICSMALLINT
IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERICINTEGER
LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERICBIGINT
FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERICFLOAT
DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERICDECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] 数据库兼容的字节流类型
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER 或未指定类型
EnumTypeHandler Enumeration Type VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERICDOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandler java.lang.String SQLXML
InstantTypeHandler java.time.Instant TIMESTAMP
LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
LocalDateTypeHandler java.time.LocalDate DATE
LocalTimeTypeHandler java.time.LocalTime TIME
OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
OffsetTimeTypeHandler java.time.OffsetTime TIME
ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
YearTypeHandler java.time.Year INTEGER
MonthTypeHandler java.time.Month INTEGER
YearMonthTypeHandler java.time.YearMonth VARCHARLONGVARCHAR
JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

objectFactory

对象工厂

plugins

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

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

environments

环境:可以配置多种环境,比如开发、测试和生产环境需要有不同的配置

  • default:指定使用某种环境,从而达到快速切换
1
<environments default="development">
environment

环境变量:配置一个具体的环境信息,必须有两个transactionManager和dataSource

  • id:代表当前环境的唯一标识
1
<environment id="test">
transactionManager

事务管理器

  • type:事务管理器的类型
    • JDBC:直接使用JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域,JdbcTransactionFactory
    • MANAGED:让容器来管理事务的整个生命周期(比如JEE应用服务器的上下文),ManagedTransactionFactory
    • 自定义事务管理器:实现TransactionFactory接口,type=全类名/别名
dataSource

数据源:

  • type: 数据源类型
    • UNPOOLED:不使用连接池, UnpooledDataSourceFactory
    • POOLED:使用连接池, PooledDataSourceFactory
    • JNDI:在EJB或应用服务器这类容器中查找指定的数据源
    • 自定义:实现DataSourceFactory接口,定义数据源的获取方式
  • 实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置
databaseIdProvider

数据库厂商标识:可以根据不同的数据库厂商执行不同的语句

  • Type:
    • DB_VENDOR:使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识
      • Property-name:数据库厂商标识
      • Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用
    • 自定义:实现DatabaseIdProvider接口
1
2
3
4
5
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
1
2
3
4
5
<select id="getEmpById" resultType="emp" databaseId="mysql">
select *
from t_employee
where id = #{id}
</select>

匹配规则:

  1. 如果没有配置databaseIdProvider标签,那么databaseId=null
  2. 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null
  3. 如果databaseId不为null,他只会找到配置databaseId的sql语句
  4. MyBatis会加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有语句。如果同时找到带有databaseId和不带databaseId的相同语句,则后者会被舍弃
mappers

映射器:将SQL映射注册到全局配置中

  • mapper:注册一个映射
    • resource:引用类路径下的文件
    • url:引用网络或磁盘路径下的文件
    • class:引用注册接口
      • 方式1:SQL映射名必须与接口同名同目录
      • 方式2:SQL利用注解写在接口上
      • 可以按需混合使用
  • package:批量注册,SQL映射文件名必须和接口名相同并且在同一目录下
1
2
3
4
5
6
<mappers>
<mapper resource="EmployeeMapper.xml"/>
<mapper url="/Users/mousse/IdeaProjects/MyBatis/Settings/src/main/resources/EmployeeMapper.xml"/>
<mapper class="cc.mousse.dao.EmployeeMapperAnnotation"/>
<package name="cc.mousse.dao"/>
</mappers>

映射文件

映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义

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

增删改元素

  • id命名空间中的唯一标识符
  • parameterType:传入语句参数的完全限定类名或别名,MyBatis可通过TypeHandler推断出具体传入语句的参数类型
    • 默认:unset
  • flushCache:任何时候语句被调用时都会导致蹦迪缓存和二级缓存被清空
    • 默认:true(增删改时)
  • timeout:抛出异常前驱动程序等待数据库返回请求结果的秒数
    • 默认:unset(依赖驱动)
  • statementType:让MyBatis分别使用
    • STATMENT:Statement
    • PREPARED:PreparedStatement
    • CALLABLE:CallableStatement
    • 默认:PREPARED
  • userGeneratedKeysMyBatis利用statement.getGeneratedKeys()获取主键值策略(增改时)
    • 默认:false
  • keyProperty:指定对应的主键属性,获取到主键值后将此值封装给JavaBean的哪个属性值
    • 默认:unset
  • keyColumn:通过生成的键值设置表中的列名(增改时),仅在某些数据库中是必须的
  • databaseId若配置的databaseIdProvider,MyBatis会加载所有不带databaseId或匹配当前databaseId的语句
    • 若带或不带的语句都有则不带的会被忽略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<mapper namespace="cc.mousse.dao.EmployeeMapper">

<!--void addEmp(Employee employee);-->
<!--MyBatis利用statement.getGeneratedKeys()获取自增主键的值-->
<!--useGeneratedKeys(true):使用自增主键获取主键值策略,默认为false
keyProperty:指定对应的主键属性,获取到主键值后将此值封装给JavaBean的哪个属性值
-->
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
insert into t_employee(last_name, email, gender)
values (#{lastName}, #{email}, #{gender})
</insert>

<!--void deleteEmpById(Integer id);-->
<delete id="deleteEmpById">
delete
from t_employee
where id = #{id}
</delete>

<!--void updateEmp(Employee employee);-->
<update id="updateEmp">
update t_employee
set last_name=#{lastName},
email=#{email},
gender=#{gender}
where id = #{id}
</update>

<!--Employee getEmpById(Integer id);-->
<select id="getEmpById" resultType="cc.mousse.bean.Employee">
select *
from t_employee
where id = #{id}
</select>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private SqlSessionFactory getFactory() {
String config = "mybatis-3-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(config);
} catch (IOException ignored) {
}
return new SqlSessionFactoryBuilder().build(inputStream);
}

/**
* 测试增删改
* 允许定义返回值类型:Integer,Long,Boolea,void
*/
@Test
public void crudTest() {
// openSession()形参里为true:获取到的SqlSession会自动提交,反之不会自动提交
try (SqlSession openSession = getFactory().openSession()) {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
mapper.addEmp(new Employee(null, "Marry", 'f', "Merry@mousse.cc"));
mapper.updateEmp(new Employee(1, "Marry", 'f', "Merry@mousse.cc"));
mapper.deleteEmpById(1);
System.out.println(mapper.getEmpById(1));
// 手动提交事务
openSession.commit();
}
}

主键生成

数据库支持:如MySQL/SQL Server等,设置useGeneratedKeys=”true”,然后再把keyProperty设置到目标属性上

1
2
3
4
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
insert into t_employee(last_name, email, gender)
values (#{lastName}, #{email}, #{gender})
</insert>

数据库不支持:如Oracle,使用selectKey子元素

  • selectKey元素将会首先运行,id会被设置,然后插入语句会被调用
    • keyProperty查出的主键值封装给JavaBean的哪个属性
    • order:当前SQL运行顺序
      • BEFORE
      • AFTER
    • resultType查出数据的返回值类型
    • keyColumn:匹配属性的返回结果集中的列名称
    • statementType:与之前相同
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--Oracle不支持自增,而是使用序列模拟自增-->
<insert id="addEmp" databaseId="oracle">
# keyProperty:查出的主键值封装给JavaBean的哪个属性
# order=BEFORE:当前SQL在插入SQL之前运行
# resultType:查出数据的返回值类型
<selectKey order="BEFORE" keyProperty="id" resultType="_id">
# 查询主键的SQL语句
select EMPLOYEES_SEQ.nextval from dual
</selectKey>
# 插入时的主键从序列中拿
insert into t_employee(EMPLOYEE_ID, last_name, email, gender)
values (#{id} , #{lastName}, #{email}, #{gender})
</insert>

参数传递

单个参数:

  • #{参数名}:取出参数值
  • 可接受基本类型,对象类型,集合类型的值
  • MyBatis可直接使用这个参数,不需要经过任何处理

多个参数:特殊处理

  • 任意多个参数都会被MyBatis重新包装成一个Map传入

    • Map的key:

      • param1……paramN

        1
        2
        3
        4
        5
        6
        <select id="getEmpByIdAndLastName" resultType="cc.mousse.bean.Employee">
        select *
        from t_employee
        where id = #{param1}
        and last_name = #{param2}
        </select>
      • 参数的索引

      • 命名参数:明确指定封装参数时map的key

        • 接口方法的形参上添加@Param注解

          1
          2
          3
          4
          5
          public interface EmployeeMapper {

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

          }
        • #{指定的key}取出对应参数值

          1
          2
          3
          4
          5
          6
          <select id="getEmpByIdAndLastName" resultType="cc.mousse.bean.Employee">
          select *
          from t_employee
          where id = #{id}
          and last_name = #{lastName}
          </select>
    • Map的value:传入参数值

命名参数:为参数使用@Param起一个名字,MyBatis就会将这些参数封装进map中,key就是我们自己指定的名字

POJO:当这些参数属于我们业务POJO时,我们直接传递POJO

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

Map:我们也可以封装多个参数为map,直接传递

  • #{key}:取出传入的Map中对应的值

    1
    2
    3
    4
    5
    public interface EmployeeMapper {

    Employee getEmpByMap(Map<String, Object> map);

    }
    1
    2
    3
    4
    5
    6
    <select id="getEmpByMap" resultType="cc.mousse.bean.Employee">
    select *
    from t_employee
    where id = #{id}
    and last_name = #{lastName}
    </select>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void mapTest() {
    try(SqlSession openSession = getFactory().openSession(true)) {
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("id", 2);
    map.put("lastName", "Jerry");
    Employee employee = mapper.getEmpByMap(map);
    System.out.println(employee);
    }
    }

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

参数处理

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Employee getEmp(@Param("id")Integer id,String lastName);
// 取值:id==>#{id/param1} lastName==>#{param2}

public Employee getEmp(Integer id,@Param("e")Employee emp);
// 取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}

/**特别注意:如果是Collection(List、Set)类型或者是数组,
也会特殊处理。也是把传入的list或者数组封装在map中。
key:Collection(collection),如果是List还可以使用这个key(list)
数组(array)
*/
public Employee getEmpById(List<Integer> ids);
// 取值:取出第一个id的值: #{list[0]}

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;
#{key}就可以取出map中的值;

(@Param("id")Integer id,@Param("lastName")String lastName);
// ParamNameResolver解析参数封装map的;
/*
1、names:{0=id, 1=lastName};构造器的时候就确定好了
确定流程:
1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;
2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
name的值:
标注了param注解:注解的值
没有标注:
1.全局配置:useActualParamName(jdk1.8):name=参数名
2.name=map.size();相当于当前元素的索引
{0=id, 1=lastName,2=2}
*/


args【1"Tom",'hello'】:

public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
//1、参数为null直接返回
if (args == null || paramCount == 0) {
return null;

//2、如果只有一个元素,并且没有Param注解;args[0]:单个参数直接返回
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];

//3、多个元素或者有Param标注
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;

//4、遍历names集合;{0=id, 1=lastName,2=2}
for (Map.Entry<Integer, String> entry : names.entrySet()) {

//names集合的value作为key; names集合的key又作为取值的参考args[0]:args【1,"Tom"】:
//eg:{id=args[0]:1,lastName=args[1]:Tom,2=args[2]}
param.put(entry.getValue(), args[entry.getKey()]);


// add generic param names (param1, param2, ...)param
//额外的将每一个参数也保存到map中,使用新的key:param1...paramN
//效果:有Param注解可以#{指定的key},或者#{param1}
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}

参数值获取:

1
2
select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing: select * from tbl_employee where id=2 and last_name=?
  • #{}:可以获取map中的值或者pojo对象属性的值;

    • 以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入

    • 大多情况下使用此方法

    • 更丰富的用法:

      • 规定参数的一些规则:

        • javaType

        • jdbcType:通常需要在某种特定的条件下被设置

          • 数据为null的时,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错)

          • OTHER:无效的类型

            • mybatis对所有的null都映射的是原生Jdbc的OTHER类型,Oracle不能,由于oracle不支持全局配置中的jdbcTypeForNull=OTHER。有两种办法:

              • #{email,jdbcType=NULL};

              • jdbcTypeForNull=NULL

                1
                <setting name="jdbcTypeForNull" value="NULL"/>
        • mode:存储过程

        • numericScale

        • resultMap

        • typeHandler

        • jdbcTypeName

        • expression:未来准备支持的功能:

  • ${}:可以获取map中的值或者pojo对象属性的值;

    • 取出的值直接拼装在sql语句中;会有安全问题

    • 原生jdbc不支持占位符的地方我们就可以使用${}进行取值:

      • 分表、排序。。。;按照年份分表拆分

        1
        2
        3
        4
        5
        6
        <select id="getEmpByMap" resultType="cc.mousse.bean.Employee">
        select *
        from ${tableName}
        where id = #{id}
        and last_name = #{lastName}
        </select>
        1
        2
        select * from ${year}_salary where xxx;
        select * from tbl_employee order by ${f_name} ${order}

select元素

定义查询操作

id:唯一标识符。

  • 用来引用这条语句,需要和接口的方法名一致

parameterType:参数类型

  • 可以不传,MyBatis会根据TypeHandler自动推断

resultType:返回值类型

  • 与自动封装有关

  • 别名或者全类名

  • 返回集合:定义集合中元素的类型,不能和resultMap同时使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    List<Employee> getEmpsByLastNameLike(String lastName);

    // 返回一条Map:Key为列名
    Map<String, Object> getEmpByIdReturnMap(Integer id);

    // 多条记录封装为一个Map,Map<Integer, Employee>
    // 告诉MyBatis封装Map时使用哪个属性作为Key
    @MapKey("id")
    Map<Integer, Employee> getEmpsByLastNameLikeReturnMap(String lastName);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!--resultType=定义集合中元素的类型-->
    <!--List<Employee> getEmpsByLastNameLike(String lastName);-->
    <select id="getEmpsByLastNameLike" resultType="cc.mousse.bean.Employee">
    select *
    from t_employee
    where last_name like #{lastName}
    </select>

    <!--MyBatis已为许多常见的Map类型内建了相应的类型别名-->
    <!--Map<String, Object> getEmpByIdReturnMap(Integer id);-->
    <select id="getEmpByIdReturnMap" resultType="map">
    select *
    from t_employee
    where id = #{id}
    </select>

    <!--Map<Integer, Employee> getEmpsByLastNameLikeReturnMap(String lastName);-->
    <select id="getEmpsByLastNameLikeReturnMap" resultType="cc.mousse.bean.Employee">
    select *
    from t_employee
    where last_name like #{lastName}
    </select>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Test
    public void listResultTest() {
    try (SqlSession openSession = getFactory().openSession(true)) {
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    List<Employee> employees = mapper.getEmpsByLastNameLike("M%");
    employees.forEach(System.out::println);
    }
    }

    @Test
    public void getEmpByIdReturnMapTest() {
    try (SqlSession openSession = getFactory().openSession(true)) {
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    Map<String, Object> map = mapper.getEmpByIdReturnMap(2);
    System.out.println(map);
    }
    }

    @Test
    public void getEmpsByLastNameLikeReturnMapTest() {
    try (SqlSession openSession = getFactory().openSession(true)) {
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    Map<Integer, Employee> empsByLastNameLikeReturnMap = mapper.getEmpsByLastNameLikeReturnMap("M%");
    empsByLastNameLikeReturnMap.forEach((k, v) -> System.out.println(k + ": " + v));
    }
    }

resultMap:对外部resultMap的命名引用。resultType和resultMap之间只能同时使用一个

  • 自定义结果集

flushCache:将其设置为true后,只要语句被调用都会导致本地缓存和二级缓存被清空

  • 默认值:false

useCache:将其设置为true后,将会导致本条语句的结果被二级缓存缓存起来

  • 默认值:对select元素为true

timeout:这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数

  • 默认值:unset(依赖数据库驱动)

fetchSize:让驱动程序每次批量返回的结果行数等于这个设置值

  • 默认值:unset(依赖数据库驱动)

statementType:同之前

resultSetType:FORWARD_ONLY/SCROLL_SENSITIVE/SCROLL_INSENSITIVE/DEFAULT(等价于 unset) 中的一个

  • 默认值: unset (依赖数据库驱动)

databaseId

  • 若配置了数据库厂商标识(databaseIdProvider),MyBatis会加载所有不带 databaseId 或匹配当前 databaseId 的语句
  • 若果带和不带的语句都有,则不带的会被忽略

resultOrdered:这个设置仅针对嵌套结果select语句

  • 若为true会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用
  • 默认值:false。

resultSets:这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔

自动映射

全局setting设置

  • autoMappingBehavior默认是PARTIAL,开启自动映射
    的功能。唯一的要求是列名和javaBean属性名一致
  • 如果autoMappingBehavior设置为null则会取消自动映射
  • 数据库字段命名规范,POJO属性符合驼峰命名法,如 A_COLUMNaColumn,我们可以开启自动驼峰命名规 则映射功能,mapUnderscoreToCamelCase=true

自定义resultMap

实现高级结果集映射

resultMap id:唯一ID方便引用

resultMap type:自定义Java类型

id:指定主键列的封装规则,定义主键底层有优化

  • column:指定哪一列
  • property:指定对应JavaBean属性

resoult:定义普通列封装规则

  • 参数同上

其它不指定的列:自动封装

  • 只要写resoultMap就把所有的列全部写上映射规则
1
2
3
4
5
6
7
8
9
10
11
12
<resultMap id="MyEmp" type="cc.mousse.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>

<select id="getEmpById" resultMap="MyEmp">
select *
from t_employee
where id = #{id}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--封装规则1:联合查询,级联封装结果集-->
<resultMap id="EmpAndDept1" type="cc.mousse.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<result column="dept_id" property="department.id"/>
<result column="dept_name" property="department.departmentName"/>
</resultMap>
<select id="getEmpAndDeptById" resultMap="EmpAndDept1">
select e.id as id, last_name, gender, email, d.id as dept_id, d.dept_name as dept_name
from t_employee e,
t_department d
where e.dept_id = d.id
and e.id = #{id}
</select>

association:一个复杂的类型关联,许多结果将包成这种类型

  • 嵌入结果映射:结果映射自身的关联或者参考一个

    • property:指定哪个实行是联合的对象
    • javaType:指定联合的类型,不能省略
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!--封装规则2:association指定联合的JavaBean对象-->
    <resultMap id="EmpAndDept2" type="cc.mousse.bean.Employee">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="email" property="email"/>
    <result column="gender" property="gender"/>
    <association property="department" javaType="cc.mousse.bean.Department">
    <id column="dept_id" property="id"/>
    <result column="dept_name" property="departmentName"/>
    </association>
    </resultMap>
    <select id="getEmpAndDeptById" resultMap="EmpAndDept2">
    select e.id as id, last_name, gender, email, d.id as dept_id, d.dept_name as dept_name
    from t_employee e,
    t_department d
    where e.dept_id = d.id
    and e.id = #{id}
    </select>
  • 分段查询:

    • select:调用目标的方法查询当前属性的值
    • column:将指定列的值传入目标方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!--使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性-->
    <resultMap id="EmpAndDeptStep" type="cc.mousse.bean.Employee">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="email" property="email"/>
    <result column="gender" property="gender"/>
    <!--association定义关联对象的封装规则-->
    <association property="department" select="cc.mousse.dao.DepartmentMapper.getDeptById" column="dept_id"/>
    </resultMap>
    <select id="getEmpAndDeptByIdStep" resultMap="EmpAndDeptStep">
    select *
    from t_employee
    where id = #{id}
    </select>
    1
    2
    3
    4
    5
    <select id="getDeptById" resultType="cc.mousse.bean.Department">
    select id, dept_name as departmentName
    from t_department
    where id = #{id}
    </select>
  • 分段查询&延迟加载:开启延迟加载和属性按需加载(需要的时候才查询而不是每次都会查询)

    1
    2
    3
    4
    5
    <!--显示的指定每个需要更改配置的值,即使它是默认的,防止更新带来的问题-->
    <settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

collection:复杂类型的集

  • 嵌入结果映射:结果映射自身的集或者参考一个

    • ofType:指定集合里元素的类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <resultMap id="DeptAndEmp" type="cc.mousse.bean.Department">
    <id column="dept_id" property="id"/>
    <result column="dept_name" property="departmentName"/>
    <collection property="employees" ofType="cc.mousse.bean.Employee">
    <!--定义集合中元素的封装规则-->
    <id column="emp_id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <result column="email" property="email"/>
    </collection>
    </resultMap>
    <select id="getDeptAndEmpById" resultMap="DeptAndEmp">
    select d.id as dept_id,
    d.dept_name as dept_name,
    e.id as emp_id,
    e.last_name as last_name,
    e.gender as gender,
    e.email as email
    from t_department d,
    t_employee e
    where d.id = e.dept_id
    and d.id = #{id}
    </select>
  • 集合类型&嵌套结果集:

    1
    2
    3
    4
    5
    <select id="getEmpsByDeptId" resultType="cc.mousse.bean.Employee">
    select *
    from t_employee
    where dept_id = #{deptId}
    </select>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <resultMap id="DeptAndEmpStep" type="cc.mousse.bean.Department">
    <id column="id" property="id"/>
    <result column="dept_name" property="departmentName"/>
    <collection property="employees" select="cc.mousse.dao.EmployeeResultMapMapper.getEmpsByDeptId" column="id"/>
    </resultMap>
    <select id="getDeptAndEmpByIdStep" resultMap="DeptAndEmpStep">
    select *
    from t_department
    where id = #{id}
    </select>
  • 分段查询&延迟加载:

    1
    2
    3
    4
    5
    <!--显示的指定每个需要更改配置的值,即使它是默认的,防止更新带来的问题-->
    <settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  • 多列值封装Map传递:

    • 分步查询的时候通过column指定,将对应的列的数据传递过去
    • 使用column=”{key1=column1,key2=column2…}”的形式
    1
    <collection property="employees" select="cc.mousse.dao.EmployeeResultMapMapper.getEmpsByDeptId" column="{deptId=id}"/>

association/collection标签的etchType=eager/lazy可以覆盖全局的延迟加载策略, 指定立即加载(eager)或延迟加载(lazy)

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

  • column:指定判定的列
  • javaType:列值对应的Java类型
  • case:基于某些值的结果映射
    • 嵌入结果映射:这种情形结果也映射它本身,因此可以包含很多相同的元 素或者它可以参照一个外部的结果映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<resultMap id="EmpByIdDiscriminator" type="cc.mousse.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<discriminator javaType="string" column="gender">
<case value="m" resultType="cc.mousse.bean.Employee">
<result column="last_name" property="email"/>
</case>
<case value="f" resultType="cc.mousse.bean.Employee">
<result column="email" property="email"/>
<association property="department" select="cc.mousse.dao.DepartmentMapper.getDeptById"
column="dept_id"/>
</case>
</discriminator>
</resultMap>
<select id="getEmpByIdDiscriminator" resultMap="EmpByIdDiscriminator">
select *
from t_employee
where id = #{id}
</select>

动态SQL

MyBatis强大特性之一,极大的简化我们拼装SQL的操作

动态SQL元素和使用JSTL或其他类似基于XML的文本处理器相似

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

ONGL

Object Graph Navigation Language对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。 类似于EL,SpEL

访问对象属性:person.name

调用方法:person.getName()

调用静态属性/方法:

  • @java.lang.Math@PI
  • @java.util.UUID@randomUUID()

调用构造方法:new com.atguigu.bean.Person(‘admin’).name

运算符:+,-*,/,%

逻辑运算符:in,not in,>,>=,<,<=,==,!=

  • xml中特殊符号如”,>,<等这些都需要使用转义字符

访问集合伪属性:

类型 伪属性 伪属性对应的Java方法
List,Set,Map size,isEmpty List/Set/Map.size(),List/Set/Map.isEmpty()
List,Set iterator List.iterator(),Set.iterator()
Map keys,values Map.keySet(),Map.values()
Iterator next,hasNext Iterator.next(),Iterator.hasNext()

if

判断

test:判断表达式(OGNL)

  • 遇见特殊符号应该去写转义字符:&&
  • 从参数中取值进行判断
  • OGNL会进行字符串与数字的转换判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--查询员工,要求,携带了哪个字段查询条件就带上这个字段的值-->
<select id="getEmpsByConditionId" resultType="cc.mousse.bean.Employee">
select *
from t_employee
<where>
# 从参数中取值进行判断
<if test="id!=null">
id = #{id}
</if>
<if test="lastName!=null and lastName!=''">
last_name like #{lastName}
</if>
<if test="email!=null and email.trim()!=''">
email = #{email}
</if>
<if test="gender=='m' or gender=='f'">
gender = #{gender}
</if>
</where>
</select>

choose

分支选择

when, otherwise,类似于带了break的switch-case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--如果带了id就用id查,如果带了lastName就用lastName查;只会进入其中一个-->
<select id="getEmpsByConditionChoose" resultType="cc.mousse.bean.Employee">
select *
from t_employee
<where>
<choose>
<when test="id!=null">
id = #{id}
</when>
<when test="lastName!=null">
last_name = #{lastName}
</when>
<when test="email!=null">
email = #{email}
</when>
<otherwise>
1 = 1
</otherwise>
</choose>
</where>
</select>

trim

字符串截取

where标签不能解决后面多出的and或者or,需要使用trim解决

trim标签体中是整个字符串拼串后的结果

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

where:封装查询条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--查询员工,要求,携带了哪个字段查询条件就带上这个字段的值-->
<select id="getEmpsByConditionTrim" resultType="cc.mousse.bean.Employee">
select *
from t_employee
<trim prefix="where" suffixOverrides="and">
# 从参数中取值进行判断
<if test="id!=null">
id = #{id} and
</if>
<if test="lastName!=null and lastName!=''">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=''">
email = #{email} and
</if>
<if test="gender=='m' or gender=='f'">
gender = #{gender}
</if>
</trim>
</select>

set:封装修改条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!--使用set标签-->
<update id="updateEmp">
update t_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>

<!--使用trim标签-->
<update id="updateEmp">
update t_employee
<trim prefix="set" prefixOverrides=",">
<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

遍历

  • collection:指定要遍历的集合
    • list类型的参数会特殊处理封装在map中,map的key就叫list
  • item:将当前遍历出的元素赋值给指定的变量
  • separator:每个元素之间的分隔符
  • open:遍历出所有结果拼接一个开始的字符
  • close:遍历出所有结果拼接一个结束的字符
  • index;索引
    • 遍历list的时候是index就是索引,item就是当前值
    • 遍历map的时候index表示的就是map的key,item就是map的值
  • #{变量名}就能取出变量的值也就是当前遍历出的元素
1
List<Employee> getEmpsByConditionForeach(@Param("ids")List<Integer> ids);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--批量查询-->
<select id="getEmpsByConditionForeach" resultType="cc.mousse.bean.Employee">
select *
from t_employee
<foreach collection="ids" item="item_id" separator="," open="where id in(" close=")">
#{item_id}
</foreach>
</select>

<!--批量保存-->
<insert id="addEmps">
insert into t_employee(last_name, gender, email, dept_id)
values
<foreach collection="employees" item="employee" separator=",">
(#{employee.lastName}, #{employee.gender}, #{employee.email}, #{employee.department.id})
</foreach>
</insert>

内置参数

不只是方法传递过来的参数可以被用来判断,mybatis默认还有两个内置参数

_parameter:代表整个参数

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

_databaseId:如果配置了databaseIdProvider标签

  • _databaseId就是代表当前数据库的别名
1
2
3
4
5
6
7
8
9
10
11
<select id="getEmpsTestInnerParameter" resultType="cc.mousse.bean.Employee">
<if test="_databaseId=='mysql'">
select * from mysql_employee
<if test="_parameter!=null">
where last_name = #{_parameter.lastName}
</if>
</if>
<if test="_databaseId=='oracle'">
select * from oracle_employee
</if>
</select>

bind

可以从OGNL表达式中创建一个变量并将其绑定到上下文

1
2
3
4
5
6
<select id="getEmpsByConditionBind" resultType="cc.mousse.bean.Employee">
<bind name="_lastName" value="'%' + lastName + '%'"/>
select *
from t_employee
where last_name like #{_lastName}
</select>

sql

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

sql:经常将要查询的列名,或者插入用的列名抽取出来方便引用

include:引用已经抽取的sql

  • include可以自定义property,sql标签内部就能使用自定义的属性
  • include-property:取值的正确方式${prop},#{不能使用这种方式}
1
2
3
4
5
6
7
8
<sql id="insertColumn">
last_name, gender, email
</sql>
<insert id="addEmp">
insert into t_employee(
<include refid="insertColumn"/>
) values (#{last_name}, #{gender}, #{email})
</insert>

Multi-db vendor support

在mybatis配置文件中配置了databaseIdProvider , 则可以使用“_databaseId”变量,这样就可以根据不同的数据库 厂商构建特定的语句

缓存机制

可以非常方便地配置和定制,缓存可以极大的提升查询效率

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

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

查询顺序:

  1. 二级缓存
  2. 一级缓存
  3. 数据库

一级缓存

local cache/本地缓存,作用域默认:sqlSession

一直开启的SqlSession级别的一个Map

当Session flush或close后,该Session中的所有Cache将被清空

本地缓存不能被关闭,但可以调用clearCache()来清空本地缓存或改变缓存的作用域

在mybatis3.1之后可以配置本地缓存的作用域,在mybatis.xml中配置

设置名 描述 有效值 默认值
localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为SESSION,会缓存一个会话中执行的所有查询。 若设置值为STATEMENT,本地缓存将仅用于执行语句,对相同SqlSession的不同查询将不会进行缓存。 SESSION | STATEMENT SESSION

同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中:

  • key:hashCode+查询的SqlId+编写的sql查询语句+参数

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

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

二级缓存

secondlevelcache/全局作用域缓存

默认不开启,需要手动配置

MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口

基于namespace级别的缓存:一个namespace对应一个二级缓存

二级缓存在SqlSession关闭或提交之后才会生效

工作机制:

  1. 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
  2. 如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息就可以参照二级缓存中的内容
  3. 不同namespace查出的数据会放在自己对应的缓存中(map)
    1. 数据会从二级缓存中获取
    2. 查出的数据都会被默认先放在一级缓存中
    3. 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

使用步骤:

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

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

    • 属性:
      • eviction=“FIFO”:缓存回收策略

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

        • 缓存多长时间清空一次,默认不清空
      • size:引用数目,正整数

        • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
      • readOnly:只读,true/false

        • true:只读
          • mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据
          • mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。
          • 不安全,速度快
        • false:非只读
          • mybatis觉得获取的数据可能会被修改
          • mybatis会利用序列化&反序列的技术克隆一份新的数据给你
          • 安全,速度慢
      • type:指定自定义缓存的全类名,实现Cache接口即可

    1
    <cache eviction="FIFO" flushInterval="300" readOnly="true"/>
  3. <cache/>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    4. **注意:POJO需要实现Serializable接口**

    ```java
    public class Employee implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    }

有关设置

全局setting的cacheEnable:配置二级缓存的开关,一级缓存一直是打开的

select标签的useCache属性:配置这个select是否使用二级缓存,一级缓存一直是使用的

sql标签的flushCache属性:sql执行以后会同时清空一级和二级缓存

  • 查询默认flushCache=false
  • 增删改默认flushCache=true

sqlSession.clearCache():只是用来清除一级缓存

localCacheScope:本地缓存作用域(一级缓存SESSION),当前会话的所有数据保存在会话缓存中
* STATEMENT:可以禁用一级缓存

当在某一个作用域(一级缓存Session/二级缓存Namespaces)进行了增删改操作后,默认该作用域下所有select中的缓存将被clear

第三方缓存整合

EhCache是一个纯Java的进程内缓存框架,具有快速精干等特点,是Hibernate中默认的CacheProvider

MyBatis定义了Cache接口方便我们进行自定义扩展,步骤:

  1. 导入ehcache包以及整合包,日志包ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar

  2. 编写ehcache.xml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="D:\44\ehcache" />

    <defaultCache
    maxElementsInMemory="10000"
    maxElementsOnDisk="10000000"
    eternal="false"
    overflowToDisk="true"
    timeToIdleSeconds="120"
    timeToLiveSeconds="120"
    diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU">
    </defaultCache>
    </ehcache>

    <!--
    属性说明:
    l diskStore:指定数据在磁盘中的存储位置。
    l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

    以下属性是必须的:
    l maxElementsInMemory - 在内存中缓存的element的最大数目
    l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
    l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
    l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

    以下属性是可选的:
    l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
    l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
    diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
    l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
    l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
    l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
    -->
  3. 配置cache标签

    1
    2
    3
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
    <!--引用缓存-->
    <cache-ref namespace="cc.mousse.dao.EmployeeMapper"/>

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

Spring整合

查看不同MyBatis版本整合Spring时使用的适配包:http://www.mybatis.org/spring/

下载整合适配包:https://github.com/mybatis/spring/releases

官方整合示例,jpetstore:https://github.com/mybatis/jpetstore-6

整合MyBatis目的:

  • Spring管理所有组件,可直接使用@Autowired自动注入Mapper
  • String管理事务,Spring式声明事务

Maven

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<!--依赖日志-->
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

<!--AOP组件-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.9</version>
</dependency>

<!--IOC组件-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.9</version>
</dependency>

<!--JDBC组件-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.9</version>
</dependency>

<!--WEB组件-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>

<!--JSTL-->
<!-- https://mvnrepository.com/artifact/org.apache.taglibs/taglibs-standard-impl -->
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
<scope>runtime</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.taglibs/taglibs-standard-spec -->
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-spec</artifactId>
<version>1.2.5</version>
</dependency>

<!--MyBatis组件-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>

<!--MySQL驱动-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>

web

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!--Spring配置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:contextConfigLocation.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<!--不加报错-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

Spring

Spring管理所有的业务逻辑组件:数据源,事务控制,AOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mybatis-scan="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

<!--Spring管理所有的业务逻辑组件-->
<context:component-scan base-package="cc.mousse">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!--引入数据库配置文件-->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</bean>

<!--Spring事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启基于注解的事务-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

<!--整合mybatis-->
<!--创建出SqlSessionFactory对象-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--指定配置全局文件的位置-->
<property name="configLocation" value="classpath:mybatis-3-config.xml"/>
<!--指定Mapper文件的位置-->
<property name="mapperLocations" value="classpath:mybatis.mapper/*.xml"/>
</bean>

<!--扫描所有Mapper接口的实现使其能够自动注入-->
<mybatis-scan:scan base-package="cc.mousse"/>

</beans>

SpringMVC

SpringMVC控制网站跳转逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--SpringMVC控制网站跳转逻辑-->
<context:component-scan base-package="cc.mousse" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<mvc:default-servlet-handler/>

<mvc:annotation-driven/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>

</beans>

MyBatis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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"/>
<setting name="jdbcTypeForNull" value="NULL"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

</configuration>

逆向工程

MyBatisGenerator,简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件、接口以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、 存储过程等这些复杂sql的定义需要我们手工编写

官方文档地址 http://www.mybatis.org/generator/

官方工程地址 https://github.com/mybatis/generator/releases

使用步骤:

  1. 编写MBG的配置文件:重要几处配置

    1. jdbcConnection配置数据库连接信息
    2. javaModelGenerator配置javaBean的生成策略
    3. sqlMapGenerator配置sql映射文件生成策略
    4. javaClientGenerator配置Mapper接口的生成策略
    5. table 配置要逆向解析的数据表
      • tableName:表名
      • domainObjectName:对应的javaBean名
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <?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>
    <!--
    MyBatis3Simple:简单版的CRUD
    MyBatis3:豪华版
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">

    <!--指定如何连接到目标数据库-->
    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
    connectionURL="jdbc:mysql://localhost:3306/mybatis"
    userId="root"
    password="root">
    </jdbcConnection>

    <!--类型解析器设置-->
    <javaTypeResolver>
    <property name="forceBigDecimals" value="false"/>
    </javaTypeResolver>

    <!--指定JavaBean的生成策略-->
    <javaModelGenerator targetPackage="cc.mousse.bean" targetProject="./src/main/java">
    <property name="enableSubPackages" value="true"/>
    <property name="trimStrings" value="true"/>
    </javaModelGenerator>

    <!--SQL映射生成策略-->
    <sqlMapGenerator targetPackage="mapper.xml" targetProject="./src/main/resources">
    <property name="enableSubPackages" value="true"/>
    </sqlMapGenerator>

    <!--指定Mapper接口所在位置-->
    <javaClientGenerator type="XMLMAPPER" targetPackage="cc.mousse.dao" targetProject="./src/main/java">
    <property name="enableSubPackages" value="true"/>
    </javaClientGenerator>

    <!--指定需要逆向分析哪些表,根据表创建JavaBean-->
    <table tableName="t_employee" domainObjectName="Employee"/>
    <table tableName="t_department" domainObjectName="Department"/>

    </context>
    </generatorConfiguration>
  2. 运行代码生成器生成代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Test
    public void generatorTest() throws Exception {
    List<String> warnings = new ArrayList<>();
    boolean overwrite = true;
    File configFile = new File("src/main/resources/generatorConfig.xml");
    ConfigurationParser cp = new ConfigurationParser(warnings);
    Configuration config = cp.parseConfiguration(configFile);
    DefaultShellCallback callback = new DefaultShellCallback(overwrite);
    MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
    myBatisGenerator.generate(null);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    @Test
    public void testMyBatis3() {
    try (SqlSession openSession = getFactory().openSession(true)) {
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    // 查询所有员工
    List<Employee> employees1= mapper.selectByExample(null);

    // 查询名字含有字母e且性别为m的员工
    // 封装员工查询条件的example
    EmployeeExample example = new EmployeeExample();
    // Criteria即为拼装条件查询
    EmployeeExample.Criteria criteria = example.createCriteria();
    criteria.andLastNameLike("%e%");
    criteria.andGenderEqualTo("m");

    // 查询(名字含有字母e且性别为m的)邮件含有字母e或员工
    EmployeeExample example1 = new EmployeeExample();
    EmployeeExample.Criteria criteria1 = example.createCriteria();
    criteria1.andLastNameLike("%e%");
    criteria1.andGenderEqualTo("m");
    EmployeeExample example2 = new EmployeeExample();
    EmployeeExample.Criteria criteria2 = example2.createCriteria();
    criteria2.andEmailLike("%e%");
    example1.or(criteria2);

    List<Employee> employees = mapper.selectByExample(example1);
    employees.forEach(System.out::println);
    }
    }

注意Context标签

  • targetRuntime=“MyBatis3“可以生成带条件的增删改查
  • targetRuntime=“MyBatis3Simple“可以生成基本的增删改查
  • 如果再次生成,建议将之前生成的数据删除,避免xml向后追加内容出现的问题

工作原理

运行顺序:

  1. 根据配置文件创建SQLSessionFactory

    1. SqlSessionFactoryBuilder
      1. 创建SqlSessionFactoryBuilder对象
      2. build(inputStream)
    2. XmlConfigBuilder
      1. 创建解析器parser
      2. 解析每一个标签并把详细信息保存在Configuration中
      3. 解析mapper.xml
    3. Configutation
      1. 返回Configuration
    4. XmlConfigBuilder
      1. build(Configuration)
    5. DefaultSqlSessionFactory
      1. new DefaultSqlSessionFactory()
      2. 返回创建的DefaultSqlSessionFactory,包含了保存全局配置信息的Configuration
    6. SqlSessionFactoryBuilder
  2. 返回SqlSession的实现类DefaultSession对象

    1. DefaultSqlSessionFactory
      1. openSessionFromDataSource()
    2. Configuration
      1. 获取信息创建tx
      2. new Executor()
      3. 根据Executor在全局配置中的类型创建SimpleExecutor/ReuseExecutor/BatchExecitor
    3. Executor
      1. 若开启二级缓存,创建CachingExecutor(executor)
      2. executor = (Executor) interceptorChain.pluginAll(executor),使用每一个拦截器重新包装executor并返回
      3. 创建DefaultSqlSession,包含Configuration和Executor
    4. DefaultSqlSession:SqlSessioon
      1. 返回DefaultSqlSession
    5. DefaultSqlSessionFactory
  3. getMapper返回接口的代理对象

    1. DefaultSqlSession:SqlSession
      1. getMapper(type)
    2. Configuration
      1. getMapper(type)
    3. MapperRegistry
      1. getMapper()
      2. 根据接口类型说去MapperProxyFactory
    4. MapperProxyFactory
      1. newInstance(sqlSession)
      2. 创建MapperProxy,是一个InvocationHandler
      3. 创建MapperProxy的代理对象
      4. 返回MapperProxy的代理对象
    5. DefaultSqlSession:SqlSession
    1. MapperProxy
      1. invoke()
    2. MapperMethod
      1. 判断增删改查类型
      2. 包装参数为一个Map或直接返回
      3. sqlSession.selectOne()
    3. DefaultSqlSession:SqlSession
      1. selectList()
      2. 获取MappedStatement
      3. Executor.query(ms, xxx, x)
    4. Executor
      1. 获取BoundSql,它代表SQL语句的详细信息
      2. executor.query
    5. SimpleExecutor
      1. 查看本地缓存是否有数据,没有就调用queryFromDatabase,查出以后会保存在本地缓存
      2. doQuery()
    6. BaseExecutor
      1. 创建StatementHandler对象,PreparedStatementHandler
    7. StatementHandler(处理SQL语句预编译,设置参数等工作,可以创建出Statement对象)
      1. (StatementHandler)interceptorChain.pluginAll(statementHandler)
      2. 创建ParameterHandler(设置参数),interceptorChain.pluginAll(parameterHandler)
      3. 创建ResultSetHandler(处理结果),interceptorChain.pluginAll(resultSetHandler)
      4. 预编译SQL产生PreparedStatement对象
      5. 调用ParamentHandler设置参数
      6. 调用TypeHandler(在整个过程中进行数据库类型和JavaBean类型的映射)给SQL预编译设置参数
      7. 查出数据使用ResultSetHandler处理结果,使用TypeHanler获取value值
      8. 后续的连接关闭
    8. DefaultSqlSession:SqlSession
      1. 返回list第一个
    9. MapperMethod
  4. 获取sqlSessionFactory对象:

    • 解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession
    • MappedStatement代表一个增删改查的详细信息
  5. 获取sqlSession对象:

    • 返回一个DefaultSqlSession对象,包含Executor和Configuration
    • 这一步会创建Executor对象
  6. 获取接口的代理对象:MapperProxy

    • getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
    • 代理对象里面包含了,DefaultSqlSession(Executor)
  7. 执行增删改查方法

总结:

  1. 根据配置文件(全局,SQL映射)初始化出Configuration对象
  2. 创建一个DefaultSqlSession对象,他里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
  3. DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy
  4. MapperProxy里面有(DefaultSqlSession)
  5. 执行增删改查方法:
    1. 调用DefaultSqlSession的增删改查(Executor)
    2. 会创建一个StatementHandler对象(同时也会创建出ParameterHandler和ResultSetHandler)
    3. 调用StatementHandler预编译参数以及设置参数值(使用ParameterHandler来给sql设置参数)
    4. 调用StatementHandler的增删改查方法
    5. ResultSetHandler封装结果

四大对象:

  1. Executor
  2. StatementHander:处理SQL语句预编译,设置参数等相关工作
  3. ParameterHandler和ResultHandler
  • ParameterHandler:设置预编译参数用
  • ResultHandler:处理结果集
  1. TypeHandler:在整个过程中进行数据库类型和JavaBean类型的映射

四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
									  
MyBatis配置
SqlMapConfig.xml
运行环境(数据库连接池,数据事务管理等)
|----------| |----------| |----------|
|mapper.xml| |mapper.xml| |mapper.xml|
|----------| |----------| |----------|

SqlSessionFactory

SQL输出映射结果

|-----------------| SqlSession |-----------------|
|SQL输入映射参数 ||SQL输入映射参数 |
|HashMap | Executor |HashMap |
|包装类/基本数据类型 ||包装类/基本数据类型 |
|Pojo |→→→→→→→→→→→→→→→Mapped Statement→→→→→→→→→→→→→→→|Pojo |
|-----------------||-----------------|
数据库

插件开发

MyBatis在四大对象的创建过程中都会有插件进行介入,插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果

MyBatis允许在已映射语句执行过程中的某一点进行拦截调用

默认情况下,MyBatis允许使用插件来拦截的方法调用包括:

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

原理:

  • 在四大对象创建时:

    1. 每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler)

    2. 获取到所有的Interceptor(拦截器)(插件需要实现的接口)调用interceptor.plugin(target)返回target包装后的对象

    3. 插件机制:

      • 可以使用插件为目标对象创建一个代理对象:AOP(面向切面)
      • 我们的插件可以为四大对象创建出代理对象
      • 代理对象就可以拦截到四大对象的每一个执行

开发步骤:

  1. 编写插件实现Interceptor接口,并使用**@Intercepts**注解完成插件签名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 完成插件签名:告诉MyBatis当前插件用来拦截哪个对象的哪个方法
    @Intercepts({@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)})
    public class MyFirstPlugin implements Interceptor {

    // 拦截目标对象的目标方法的执行
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    // 执行方法
    Object proceed = invocation.proceed();
    // 返回执行后的返回值
    return proceed;
    }

    // 包装目标对象:为目标对象创建一个代理对象
    @Override
    public Object plugin(Object target) {
    // 可以借助Plugin的wrap方法使用当前拦截器包装目标对象
    Object wrap = Plugin.wrap(target, this);
    // 返回当前创建的动态代理
    return wrap;
    }

    // 将插件注册时的property属性设置进来
    @Override
    public void setProperties(Properties properties) {
    System.out.println("插件配置信息:" + properties);
    }

    }
  2. 在全局配置文件中注册插件

    1
    2
    3
    4
    5
    6
    <plugins>
    <plugin interceptor="MyFirstPlugin">
    <property name="username" value="admin"/>
    <property name="password" value="12345"/>
    </plugin>
    </plugins>

插件原理:

  1. 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
  2. 多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹,形成代理链
  3. 目标方法执行时依次从外到内执行插件的intercept方法
  4. 多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获 取最后一层的h以及target属性的值

Interceptor

Intercept:拦截目标方法执行

plugin:生成动态代理对象,可以使用MyBatis提 供的Plugin类的wrap方法

setProperties:注入插件配置时设置的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin.intercept:" + invocation.getMethod());

// 动态改变SQL运行参数:查2号员工得出3号员工
Object target = invocation.getTarget();
System.out.println("被拦截的对象:" + target);
// 拿到:StatementHandler->ParameterHandler->parameterObject
// 拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("SQL语句参数:" + value);
// 修改SQL参数
metaObject.setValue("parameterHandler.parameterObject", 3);

// 执行方法
Object proceed = invocation.proceed();
// 返回执行后的返回值
return proceed;
}

扩展

PageHelper

MyBatis中非常方便的第三方分页插件,可以对照官方文档的说明快速的使用插件

官方文档: https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md

使用步骤:

  1. 导入相关包pagehelper-x.x.x.jar和jsqlparser-
    0.9.5.jar。

  2. 在MyBatis全局配置文件中配置分页插件

    1
    2
    3
    <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
  3. 使用PageHelper提供的方法进行分页

  4. 可以使用更强大的PageInfo封装返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Test
public void getAllEmps() throws IOException {
try(SqlSession openSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-3-config.xml")).openSession(true)) {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Page<Object> page = PageHelper.startPage(1, 3);
List<Employee> emps = mapper.getAllEmps();
// PageInfo<Employee> info = new PageInfo<>(emps);
// 连续显示5页
PageInfo<Employee> info = new PageInfo<>(emps, 5);
emps.forEach(System.out::println);
// System.out.println("当前页码:" + page.getPageNum());
// System.out.println("页记录数:" + page.getPageSize());
// System.out.println("总计路数:" + page.getTotal());
// System.out.println("总计页码:" + page.getPages());
System.out.println("当前页码:" + info.getPageNum());
System.out.println("页记录数:" + info.getPageSize());
System.out.println("总计路数:" + info.getTotal());
System.out.println("总计页码:" + info.getPages());

int[] nums = info.getNavigatepageNums();
for (int num : nums) {
System.out.println("连续显示的页码" + num);
}
System.out.println(info);
}
}

批量操作

默认的openSession()方法没有参数,它会创建有如下特性的

  • 会开启一个事务,也就是不自动提交
  • 连接对象会从由活动环境配置的数据源实例得到。
  • 事务隔离级别将会使用驱动或数据源的默认设置。
  • 预处理语句不会被复用,也不会批量处理更新。

openSession方法的ExecutorType类型的参数,枚举类型:

  • ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情(这是默认装配的)。它为每个语句的执行创建一个新的预处理语句
  • ExecutorType.REUSE:这个执行器类型会复用预处理语句
  • ExecutorType.BATCH:这个执行器会批量执行所有更新语句

批量操作我们是使用MyBatis提供的BatchExecutor进行的, 他的底层就是通过jdbc攒sql的方式进行的。我们可以让他 攒够一定数量后发给数据库一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void batchTest() throws IOException {
// 可以执行批量操作的SqlSession
try (SqlSession openSession = getFactory().openSession(ExecutorType.BATCH)) {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
for (int i = 1; i < 100; i++) {
String uuid = UUID.randomUUID().toString().substring(0, 5);
String lastName = "user-" + uuid;
String email = lastName + "@mousse.cc";
mapper.addEmpsBatch(new Employee(null, lastName, 'f', email, new Department(1)));
}
openSession.commit();
}
}

Spring整合

推荐额外的配置一个可以专门用来执行批量操作的sqlSession

1
2
3
4
5
6
7
<!--配置一个可以进行批量执行的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--传入sqlSessionFactory对象-->
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
<!--指定executorType-->
<constructor-arg name="executorType" value="BATCH"/>
</bean>

需要用到批量操作的时候,我们可以注入配置的这个批量 SqlSession。通过他获取到mapper映射器进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class EmployeeService {

@Autowired
private EmployeeMapper employeeMapper;

// 自动装配批量的SqlSession
@Autowired
private SqlSession sqlSession;

public List<Employee> getAllEmps() {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
return mapper.getAllEmps();
}

}

注意:

  • 批量操作是在session.commit()以后才发送sql语句给数据库进行执行的
  • 如果我们想让其提前执行,以方便后续可能的查询操作 获取数据,我们可以使用sqlSession.flushStatements()方法,让其直接冲刷到数据库进行执行

存储过程

实际开发中,我们通常也会写一些存储过程, MyBatis也支持对存储过程的调用

存储过程的调用:

  1. select标签中statementType=“CALLABLE”
  2. 标签体中调用语法:{call procedure_name(#{param1_info},#{param2_info})}

游标处理:MyBatis对存储过程的游标提供了一个JdbcType=CURSOR的支持, 可以智能的把游标读取到的数据,映射到我们声明的结果集中

typeHandler

默认mybatis在处理枚举对象的时候保存的是枚举的名字:EnumTypeHandler

  • 改变使用:EnumOrdinalTypeHandler:

    1
    2
    3
    <typeHandlers>
    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="cc.mousse.bean.EmpStatus"/>
    </typeHandlers>

可以通过自定义TypeHandler的形式来在设置参数或取出结果集的时候自定义参数封装策略

步骤:

  1. 实现TypeHandler接口或者继承BaseTypeHandler

  2. 使用@MappedTypes定义处理的java类型,使用@MappedJdbcTypes定义jdbcType类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {

    // 定义数据库如何保存到数据库中
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, EmpStatus empStatus, JdbcType jdbcType) throws SQLException {
    preparedStatement.setString(i, String.valueOf(empStatus.getCode()));
    }

    @Override
    public EmpStatus getResult(ResultSet resultSet, String s) throws SQLException {
    // 根据从数据库拿到的枚举状态码返回一个枚举对象
    int code = Integer.parseInt(resultSet.getString(s));
    System.out.println("从数据库中获取的状态码:"+code);
    return EmpStatus.getEmpStatus(code);
    }

    @Override
    public EmpStatus getResult(ResultSet resultSet, int i) throws SQLException {
    int code = resultSet.getInt(i);
    System.out.println("从数据库中获取的状态码:"+code);
    return EmpStatus.getEmpStatus(code);
    }

    @Override
    public EmpStatus getResult(CallableStatement callableStatement, int i) throws SQLException {
    int code = callableStatement.getInt(i);
    System.out.println("从数据库中获取的状态码:"+code);
    return EmpStatus.getEmpStatus(code);
    }
    }
  3. 在自定义结果集标签或者参数处理的时候声明使用自定义TypeHandler进行处理,或者在全局配置TypeHandler要处理的javaType

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <typeHandlers>
    <!--1、配置我们自定义的TypeHandler -->
    <typeHandler handler="cc.mousse.typeHandler.MyEnumEmpStatusTypeHandler" javaType="cc.mousse.bean.EmpStatus"/>
    <!--2、也可以在处理某个字段的时候告诉MyBatis用什么类型处理器
    保存:#{empStatus,typeHandler=xxxx}
    查询:
    <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmp">
    <id column="id" property="id"/>
    <result column="empStatus" property="empStatus" typeHandler=""/>
    </resultMap>
    注意:如果在参数位置修改TypeHandler,应该保证保存数据和查询数据用的TypeHandler是一样的。
    -->
    </typeHandlers>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 希望数据库保存状态码
public enum EmpStatus {

LOGIN(100, "用户登录"),
LOGOUT(200, "用户登出"),
REMOVED(300, "用户不存在");

private Integer code;
private String msg;

EmpStatus(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

public int getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

// 按照状态码返回枚举对象
public static EmpStatus getEmpStatus(Integer code) {
return switch (code) {
case 100 -> LOGIN;
case 200 -> LOGOUT;
case 300 -> REMOVED;
default -> null;
};
}

}
1
2
3
4
5
6
7
8
9
@Test
public void getEmpByIdTest() throws IOException {
try (SqlSession openSession = getFactory().openSession(true)) {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(108);
System.out.println(employee);
System.out.println(employee.getEmpStatus());
}
}

MyBatis
http://docs.mousse.cc/MyBatis/
作者
Mocha Mousse
发布于
2025年5月26日
许可协议