mybatis框架
待整理。。。
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)
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO( Plain Old Java Objects,普通的Java对象)映射成数据库中的记录.
为什么要使用MyBatis?
MyBatis是一个半自动化的持久化层框架。
JDBC
- SQL夹在Java代码块里,耦合度高导致硬编码内伤
- 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见
Hibernate和JPA
- 长难复杂SQL,对于Hibernate而言处理也不容易
- 内部自动生产的SQL,不容易做特殊优化。
- 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降。
对开发人员而言,核心sql还是需要自己优化, sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据
MyBatis-HelloWorld
创建 maven 工程
创建 mybatis01 的工程,工程信息如下
添加 Mybatis3.4.5 的坐标 在 pom.xml 文件中添加 Mybatis3.4.5 的坐标,如下:
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<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</dependency>
</dependencies>
编写 User 实体类
创建对应的javaBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class User implements Serializable{
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
编写持久层接口
写持久层接口 IUserDao
1
2
3
4
5
6
7
8
9
10// IUserDao 接口就是我们的持久层接口(也可以写成 UserDao 或者 UserMapper) ,具体代码如下:
public interface IUserDao {
/**
* 查询所有操作
* @return
*/
List<User> findAll();
}
创建mybatis配置文件
MyBatis 的全局配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息、如数据库连接池信息等。指导着MyBatis进行工作。我们可以参照官方文件的配置示例。 SqlMapConfig.xml 文件如下:
1 |
|
创建sql映射文件
映射文件的作用就相当于是定义Dao接口的实现类如何工作。这也是我们使用MyBatis时编写的最多的文件。
创建位置: 必须和持久层接口在相同的包中。
名称: 必须以持久层接口名称命名文件名,扩展名是.xml
1 |
|
编写测试类
1 | public class MybatisTest { |
SqlSession
SqlSession 的实例不是线程安全的,因此是不能被共享的。
SqlSession每次使用完成后需要正确关闭,这个关闭操作是必须的
SqlSession可以直接调用方法的id进行数据库操作,但是我们一般还是推荐使用SqlSession获取到Dao接口的代理类,执行代理对象的方法,可以更安全的进行类型检查操作
基于注解的 mybatis 使用
在使用基于注解的 Mybatis 配置时,请移除 xml 的映射配置(IUserDao.xml)。
在持久层接口中添加注解
1
2
3
4
5
6
7
8public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
}
MyBatis-全局配置文件
MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息。文档的顶层结构如下:
configuration 配置如下:
- properties 属性
- settings 设置
- typeAliases 类型命名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
- environment 环境变量
- transactionManager 事务管理器
- dataSource 数据源
- environment 环境变量
- databaseIdProvider 数据库厂商标识
- mappers 映射器
properties属性
如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:
- 在 properties 元素体内指定的属性首先被读取。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
1
2
3
4
5
6
7<properties resource="dbconfig.properties></properties>
# dbconfig.properties
driver=com.mysql. jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
username=root
password=123456
settings设置
这是 MyBatis 中极为重要的调整设置,它们会改变MyBatis 的运行时行为
1 | <settings> |
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关 | true | false | TRUE |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过fetchType属性来覆盖该项的开关状态 | true | false | TRUE |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同模式来观察所驱动的结果 | true |false | TRUE |
defauItStatementTimcout | 设置超时时间,它决定驱动等待数据库响应的秒数 | Any positive integer | NULL |
mapUnderscoreToCamelCase | 否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名A_COLUMN 到经典Java 属性名aColumn的类似映射 | true |false | FALSE |
typeAliases别名处理器
类型别名是为 Java 类型设置一个短的名字,可以方便我们引用某个类。
1
2
3
4<typeAliases>
<typeAlias type="pers.fulsun.bean.Employee" alias="empLoyee" />
<typeAlias type="pers.fulsun.bean.Department" alias="department"/>
</typeAliases>类很多的情况下,可以批量设置别名这个包下的每一个类创建一个默认的别名,就是简单类名小写。
1
2
3<typeAliases>
<package name="pers.fulsun.bean" />
</typeAliases>也可以使用
@Alias
注解为其指定一个别名1
2
3
public class Employee {
}
值得注意的是, MyBatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名
1 | 别名 映射的类型 |
typeHandlers类型处理器
无论是 MyBatis 在预处理语句( PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
1 | 类型处理器 Java 类型 JDBC 类型 |
日期类型的处理
日期和时间的处理, JDK1.8以前一直是个头疼的问题。我们通常使用JSR310规范领导者Stephen Colebourne创建的Joda-Time来操作。 1.8已经实现全部的JSR310规范了。
日期时间处理上,我们可以使用MyBatis基于JSR310( Date and Time API)编写的各种日期
时间类型处理器。
MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的
1
2
3
4
5
6
7
8
9
10
11
12
13<typeHandlers>
<typeHandle.handler="org.apache.ibatis.type.InstantTypeHandler1"/>
<typeHandle.handler="or,g.apache.ibatis.type.LocalDateTimeTypeHandler"/>
<typeHandle.handler="org.apache.ibatis.type.LocalDateTypeHandler"/>
<typeHandle.handler="org.apache.ibatis.type.LocalTimeTypeHandler"/>
<typeHandle.handler="org.apache.ibatis.type.OffsetDateTimeTypeHandler"/>
<typeHandle.handler="or,g.apache.ibatis.type.OffsetTinicTypeHandler"/>
<typeHandle.handler="orig.apache.ibatis.type.ZonudDateTimeTypeHandler"/>
<typeHandle.handler="org.apache.ibatis.type.YearTypeHandler"/>
<typeHandle.handler="org.apache.ibatis.type.HonthTypeHandler"/>
<typeHandle.handler="org.apache.ibatis.type.YearMonthTypeHandler"/>
<typeHandle.handler="or,g.apache.ibatis.type.JapaneseDateTypeHandlerr"/>
</typeHandlers>
自定义类型处理器
我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。步骤:
实现
org.apache.ibatis.type.TypeHandler
接口或者继承org.apache.ibatis.type.BaseTypeHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# 接口中的方法分别处理不同的数据转换场景:
# setParameter(PreparedStatement ps, int index, T parameter, JdbcType jdbcType)
当你在 Java 代码中执行一个 SQL 语句并且需要向这个语句中传入参数时,这个方法就发挥作用了。
它告诉 MyBatis 如何将 Java 类型的数据(parameter)转换成数据库能理解的格式,并把它放在 SQL 语句的正确位置(index)。
# getResult(ResultSet rs, String columnName)
当你执行了一个 SQL 查询并从数据库得到结果集(ResultSet)时,这个方法帮助你把结果集中某一列的数据取出,并转换成 Java 类型的数据。
你告诉它具体要转换哪一列(columnName),它就会处理这一列的数据。
# getResult(ResultSet rs, int columnIndex)
这个方法和上一个方法类似,但它是通过列的索引(位置)而不是列的名称来获取数据的。
它也是用来把结果集中的数据转换成 Java 类型的数据。
# getResult(CallableStatement cs, int columnIndex)
这个方法用在存储过程的场景。存储过程是在数据库中执行的一系列操作,它可以返回多个结果。
当你调用一个存储过程并想要处理返回的结果时,这个方法就会根据你指定的列索引来获取并转换这些结果。指定其映射某个JDBC类型(可选操作)
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 枚举类 StatusEnum,它有两个值:ACTIVE 和 INACTIVE
public enum StatusEnum {
ACTIVE,
INACTIVE;
public static StatusEnum fromValue(String value) {
for (StatusEnum status : values()) {
if (status.name().equalsIgnoreCase(value)) {
return status;
}
}
throw new IllegalArgumentException("Unknown enum value: " + value);
}
}自定义一个类型处理器来处理 StatusEnum:这个类型处理器将数据库中的字符串映射到 StatusEnum 枚举上。它假设数据库中存储的是枚举值的名称(例如,“ACTIVE” 或 “INACTIVE”)。
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
35import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class StatusEnumTypeHandler extends BaseTypeHandler<StatusEnum> {
public void setNonNullParameter(PreparedStatement ps, int i, StatusEnum parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.name());
}
public StatusEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return value == null ? null : StatusEnum.fromValue(value);
}
public StatusEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return value == null ? null : StatusEnum.fromValue(value);
}
public StatusEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return value == null ? null : StatusEnum.fromValue(value);
}
}把TypeHandler配置到程序中
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# 在Mapper.xml中声明(应用单个指定字段)
<resultMap id="BaseResultMap" type="com.xxx.EntiyDto">
<result column="enum1" jdbcType="INTEGER" property="enum1" typeHandler="com.xxx.handler.IntegerArrayTypeHandler"/>
</resultMap>
# 在springboot的yml配置文件中设置类型处理器所在的包名,不是处理器路径(应用到全局)
mybatis-plus:
type-handlers-package: com.xxx.handler
# 实体类指定类型处理器。必须在实体类上加@TableName(autoResultMap = true),否则不生效
@Data
@Accessors(chain = true)
@TableName(autoResultMap = true)
public class User {
private Long id;
...
/**
* 注意!! 必须开启映射注解
*
* @TableName(autoResultMap = true)
*
* 以下两种类型处理器,二选一 也可以同时存在
*
* 注意!!选择对应的 JSON 处理器也必须存在对应 JSON 解析依赖包
*/
@TableField(typeHandler = IntegerArrayTypeHandler.class)
private Integer[] integerArray;
}
# 在mybatis配置文件中设置
<typeHandlers>
<typeHandler handler="com.xxx.handler.IntegerArrayTypeHandler"/>
</typeHandlers>
plugins插件
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。 插件通过动态代理机制,可以介入四大对象的任何
一个方法的执行。后面会有专门的章节我们来介绍mybatis运行原理以及插件
1 | Executor (update, query, flushStatements, commit, rollback, |
environments环境
MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。
每种环境使用一个
environment
标签进行配置并指定唯一标识符可以通过
environments
标签中的default
属性指定一个环境的标识符来快速的切换环境environment
-指定具体环境id
:指定当前环境的唯一标识,transactionManager
、和dataSource
都必须有
1 | <environments default="mysql"> |
transactionManager
- Type:
JDBC | MANAGED | 自定义
- JDBC(
JdbcTransactionFactory
):使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围。 - MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 ManagedTransactionFactory
- 自定义:实现TransactionFactory接口, type=全类名/别名
- JDBC(
dataSource
- type:
UNPOOLED | POOLED | JNDI | 自定义
- UNPOOLED:不使用连接池,
UnpooledDataSourceFactory
- POOLED:使用连接池,
PooledDataSourceFactory
- JNDI: 在EJB 或应用服务器这类容器中查找指定的数据源
- 自定义:实现DataSourceFactory接口,定义数据源的获取方式。
- UNPOOLED:不使用连接池,
- 实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置
databaseIdProvider环境
MyBatis 可以根据不同的数据库厂商执行不同的语句
如果配置了
databaseIdProvider
,MyBatis 会加载所有的不带databaseId
或匹配当前databaseId
的语句;如果带或者不带databaseId的语句都有,则不带的会被忽略。新增,修改和删除都有这个属性
Type:
DB_VENDOR
: 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider
接口来自定义。Property-name
:数据库厂商标识Property-value
:为标识起一个别名,方便SQL语句使用databaseId属性引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle" />
<property name="SQL Server" value="sqLserver"/>
</databaseIdProvider>
<select id="SelectTime" resultType="String" databaseId="mysql">
SELECT NOW() FROM dual
</select>
<select id="SelectTime" resultType="String" databaseId="oracle">
SELECT 'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') FROM dual
</select>DB_VENDOR
: 会通过DatabaseMetaData#getDatabaseProductName()
返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短MyBatis匹配规则如下:
- 如果没有配置databaseIdProvider标签,那么databaseId=null
- 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null
- 如果databaseId不为null,他只会找到配置databaseId的sql语句
- MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带databaseId 的相同语句, 则后者会被舍弃。
mapper映射
mapper逐个注册SQL映射文件
1
2
3
4
5<mappers>
<mapper resource="mybatis/mapper/PersonDao.xml" />
<mapper url="fiLe:///D:/UserDao.xml"/>
<mapper class="pers.fulsun.dao.PersonDaoAnnotation"/ >
</mappers>或者使用批量注册:这种方式要求SQL映射文件名必须和接口名相同并且在同一目录下
1
2
3<mappers>
<package name="pers.fulsun.daoo"/>
</mappers>
MyBatis-映射文件
映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义;
- cache –命名空间的二级缓存配置
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 自定义结果集映射
- parameterMap – 已废弃!老式风格的参数映射
- sql –抽取可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
insert、 update、 delete元素
1 | id |
主键生成方式
若数据库支持自动生成主键的字段(比如 MySQL和 SQL Server),则可以设置
useGeneratedKeys="true"
,然后再把keyProperty
设置到目标属性上。1
2
3<insert id="insertCustomer" databaseld="mysqL" useGeneratedKeys="true" keyProperty="id">
INSERT INTO customers2 (last_name, email, age) VALUES (#{lastName},#{email},#{age})
</insert>而对于不支持自增型主键的数据库(例如Oracle),则可以使用 selectKey 子元素:selectKey 元素将会首先运行, id 会被设置, 然
后插入语句会被调用1
2
3
4
5
6
7<insert id="insertCustomer" databaseld="oracLe" parameterType="customer">
<selectKey order="BEFORE" keyProperty="id" resultType= "_int" >
SELECT crm_seq.nextval
FROM dual
</selectKey>
INSERT INTO customers2 (id, last_name, email, age) VALUES (#{id},#{lastName},#{email},#{age})
</insert>
selectKey
keyProperty
selectKey 语句结果应该被设置的目标雇性。keyColumn
匹配属性的返回结果集中的列名称。resultType
结果的类型。MyBatis 通常可以推算出来 ,但是为了更加确定写上也不会有什么问题。MyBatis 允许任何简单类型用作主键的类型 ,包括字符串。order
可以被设置为BEFORE
或AFTER
. 如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty然后执行插入语句. 如果设置为AFTER
,那么先丸行插入语句 ,然后是 SElectKey 元素statementType
与前面相同 ,MyBatis支持STATEMENT
,PREPARED
和CALLABLE
语句的映射 ,分别代表PreparedStateinent
和CallableStatement
娄型
参数( Parameters) 传递
- 单个参数
- 可以接受基本类型,对象类型,集合类型的值。这种情况MyBatis可直接使用这个参数,不需要经过任何处理。
- 多个参数
- 任意多个参数,都会被MyBatis重新包装成一个Map传入。
- Map的key是param1, param2, 0, 1…,值就是参数的值。
- 命名参数
- 为参数使用
@Param
起一个名字, MyBatis就会将这些参数封装进map中, key就是我们自己指定的名字
- 为参数使用
- POJO
- 当这些参数属于我们业务POJO时,我们直接传递POJO
- Map
- 我们也可以封装多个参数为map,直接传递
参数处理
参数位置支持的属性
javaType
、jdbcType
、mode
、numericScale
、resultMap
、typeHandler
、jdbcTypeName
、expression
#{key}
:获取参数的值,预编译到SQL中。安全。${key}
:获取参数的值,拼接到SQL中。有SQL注入问题。 ORDER BY ${name}参数也可以指定一个特殊的数据类型:
1
2#{property,javaType=int, jdbcType=NUMERIC}
#{height, javaType=double,jdbcType=NUMERIC, numericScale=2}javaType
通常可以从参数对象中来去确定- 如果 null 被当作值来传递,对于所有可能为空的列,jdbcType 需要被设置
- 对于数值类型,还可以设置小数点后保留的位数:
mode 属性允许指定 IN, OUT 或 INOUT 参数。如果参数为 OUT 或 INOUT,参数对象属性的真实值将会被改变,就像在获取输出参数时所期望的那样。
select元素
Select元素来定义查询操作。
- Id:唯一标识符。
- 用来引用这条语句,需要和接口的方法名一致
- parameterType:参数类型。
- 可以不传, MyBatis会根据TypeHandler自动推断,默认值为 unsetn
- resultType: 返回值类型。
- 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合 ,那应该是集合可以包含的类型 ,而不能是集合本身该属性和 resultMap 不能同时使用
- resultMap 外部 resultMap 的命名引用。和 resultType 属性不能同时使用。
- flushCache 将其设置为 true ,任何时候只要语句被调用 ,都会导致本地缓存和二级缓存都会被清空 ,默认值 :false
- useCache 将其设置为true,将会导致本条语句的结果被二级缓存,默认值:对 select元素为 true
- timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为unset(依赖驱动)。
- fetchSize 影响驱动程序每次批量返回的结果行数。默认值为unset(依赖驱动)。
- statementType : STATEMENT,PREPARED或CALLABLE的一个。这会让 MyBatis 分别使用Statement,PreparedStatement 或
CallableStatement,默认值:PREPARED - resultSetType : FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为unset (依赖驱动)
- databaseld :如果配置了databaseIdProvider,MyBatis 会加载所有的不带databaseld的语句或匹配当前 databaseId的语句; 如果带不带的都有,则不带的会被忽略。
- resultOrdered :区个设置仅针对嵌套结果 selet 语句适用:如果为true,就假设包含了嵌套结果集或是分组,这样当返回一个主结果行,就不会发生有对前面结果集引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false
- resultSets :这个设置仅对多结果集的情况适用 ,它将列出语句执行后返回的结果集并每个结果集给一个名称 ,名称是逗号分隔的
字段自动映射
- 全局setting设置
- autoMappingBehavior默认是
PARTIAL
,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致 - 如果autoMappingBehavior设置为null则会取消自动映射
- 数据库字段命名规范, POJO属性符合驼峰命名法,如A_COLUMN→ aColumn,我们可以开启自动驼峰命名规则映射功能,
mapUnderscoreToCamelCase=true
。
- autoMappingBehavior默认是
- 自定义resultMap, 实现高级结果集映射。
resultMap
constructor
: 类在实例化时, 用来注入结果到构造方法中idArg
: ID 参数; 标记结果作为 ID 可以帮助提高整体效能arg
: 注入到构造方法的一个普通结果
id
:一个 ID 结果; 标记结果作为 ID 可以帮助提高整体效能result:
注入到字段或 JavaBean 属性的普通结果association
:一个复杂的类型关联;许多结果将包成这种类型- 嵌入结果映射:结果映射自身的关联,或者参考一个
collection
:复杂类型的集- 嵌入结果映射:结果映射自身的集,或者参考一个
discriminator
:使用结果值来决定使用哪个结果映射case
:基于某些值的结果映射- 嵌入结果映射:这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。
一对多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!-- type:对象类型 orm -->
<resultMap type="dept" id="deptMap">
<!-- 主键字段映射 -->
<id column="deptno" property="deptno"/>
<!-- 非主键字段映射 -->
<result column="dname" property="dname"/>
<result column="loc" property="loc"/>
<!-- 封装结果到集合 property:属性 javaType:每个对象类型 -->
<collection property="emps" ofType="emp" column="deptno">
<id column="empno" property="empno"/>
<result column="ename" property="ename"/>
<result column="job" property="job"/>
<result column="hiredate" property="hiredate"/>
<result column="mgr" property="mgr"/>
<result column="sal" property="sal"/>
<result column="comm" property="comm"/>
<result column="deptno" property="deptno"/>
</collection>
</resultMap>一对一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<resultMap type="emp" id="empMap">
<id column="empno" property="empno"/>
<result column="ename" property="ename"/>
<result column="job" property="job"/>
<result column="hiredate" property="hiredate"/>
<result column="mgr" property="mgr"/>
<result column="sal" property="sal"/>
<result column="comm" property="comm"/>
<result column="deptno" property="deptno"/>
<!-- 封装结果到对象中 -->
<association property="dept" column="deptno" javaType="dept">
<id column="deptno" property="deptno"/>
<result column="dname" property="dname"/>
<result column="loc" property="loc"/>
</association>
</resultMap>
<select id="findEmpAndDept" resultMap="empMap">
select * from emp left join dept on emp.deptno = dept.deptno
</select>
id & result
id 和 result 映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的属性或字段。
property
映射到列结果的字段或属性。例如:“username”或“address.street.number”。column
数据表的列名。通常和resutset.getString(columnName)的返回值一牧。javaType
一个Java类的完全限定名,或一个类型别名。如果映射到一个JavaBean, MyBatis 通常可以断定类型jdbcType
JDBC类型是仅仅需要对插入,更新和制除操作可能为空的列进行处理typeHandler
类型处理器。使用这个属性,可以覆盖默认的类型处理器。这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名
association
复杂对象映射
POJO中的属性可能会是一个对象
我们可以使用联合查询,并以级联属性的方式封装对象。
1
2
3
4
5
6<resultMap type="pers.fulsun.bean.Lock" id="myLock">
<id column="id" property="id"/>
<result column="lockName" property="lockName"/>
<result column="key_id" property="key.id"/>
<result column="keyName" property="key.keyName"/>
</resultMap>使用association标签定义对象的封装规则
1
2
3
4
5
6
7
8<resultMap type="com.atguxgu.bean.Lock" id="myLock2">
<id column="id" property="id"/>
<result column="lockName" property="lockName"/>
<association property="key" javaType="pers.fulsun.bean.Key">
<id column="key_id" property="id"/ >
<result column="keyName" property="keyName"/>
</association>
</resultMap>association-分段查询
- select:调用目标的方法查询当前属性的值
- column:将指定列的值传入目标方法
1
2
3
4
5
6
7<resultMap type="com.atguxgu.bean.Lock" id="myLock3">
<id column="id" property="id"/>
<result column="lockName" property="lockName"/>
<association property="key" select="pers.fulsun.dao.KeyMapper.getKeyByld"
column="key_id">
</association>
</resultMap>分段查询&延迟加载
开启延迟加载和属性按需加载 ,旧版本的MyBatis需要额外的支持包 asm-3.3.1.jar cglib-2.2.2.jar
1
2
3
4<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="faLse"/>
</settings>
Collection
集合类型&嵌套结果集
1
2
3
4
5
6
7
8<select id="getDeptByld" resultMap="MyDept">
SELECT d.id d_id,d.dept_name d_deptName,
e.id e_id,e.last_name e_lastName, e.email e_email,
e.gender e_gender,e.dept_id e_deptld
FROM department d
LEFT JOIN employee e ON e.`dept_id` =d.`id`
WHERE d.`id`=#{id)
</select>1
2
3
4
5
6
7
8
9
10<resultMap type="pers.fulsun.bean.Department" id="MyDept">
<id column="d_id" property="id"/>
<result column="d_deptName" property="deptName"/>
<collection property="emps " ofType="pers.fulsun.bean.EmpLoyee" columnPrefix="e_">
<id column="id" property="id"/>
<result column="lastName" property= "lastName"/ >
<result column="email" property="email"/>
<result column="gender" property="gender" />
</collection>
</resultMap>多列值封装map传递
- 分步查询的时候通过column指定,将对应的列的数据传递过去,我们有时需要传递多列数据。
- 使用
{key1=column1,key2=column2…}
的形式
1
2
3
4
5
6
7
8<resultMap type="pers.fulsun.bean.Department" id="MyDeptStep">
<id column="id" property="id"/>
<result column="dept_name" property="deptName">
<collection property="emps"
select="pers.fulsun.dao.EmpLoyeeMapper.getEmpsByDeptId"
column="{deptId=id}">
</collection>
</resultMap>association或者collection标签的
fetchType=eager/lazy
可以覆盖全局的延迟加载策略,指定立即加载( eager) 或者延迟加载( lazy)
MyBatis-动态SQL
- 动态 SQL是MyBatis强大特性之一。极大的简化我们拼装SQL的操作。
- 动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。
- MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作
if
<if>
标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。另外要注意 where 1=1 的作用~!1
2
3
4
5
6
7
8
9<select id="findByUser" resultType="user" parameterType="user">
select * from user where 1=1
<if test="username!=null and username != '' ">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</select>
choose (when, otherwise)
1 | <select id="getEmpChoose" resultType="pers.fulsun.bean.EmpLoyee"> |
trim ,where, set
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14<select id="getEmp^herelf" resultType="pers.fulsun , bean.EmpLoyee">
select * from employee
<where>
<if test="id!=nuLL">
id=#{id) and
</if>
<if test="LastName!=nuLL and !"" .equaLs( LastName)">
last_name=#{lastName} and
</if >
<if test="gender==0 or gender==1">
gender=#{gender}
</if>
</where>
</select>set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<update id="updateEmpLoyee" parameterType="pers.fulsun.bean.EmpLoyee">
update employee
<set>
<if test="LastName!=nuLL">
last_name=#{lastName)j
</if>
<if test="gender!=nuLL and (gender==0 or gender==1)">
gender=#{gender),
</if>
<if test="emaiL !=nuLL">
email=#{email)
</if>
</set>
where id=#{id)
</update>trim: 一般用于去除sql语句中多余的and关键字,逗号,或者给sql语句前拼接 “where“、“set“以及“values(“ 等前缀,或者添加“)“等后缀,可用于选择性插入、更新、删除或者条件查询等操作。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
1
2
3
4
5
6<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。
上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。使用trim元素来达到Set同样的效果
1
2
3<trim prefix="SET" suffixOverrides=",">
...
</trim>
foreach
动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。
1
2
3
4
5
6
7
8
9
10<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>当迭代列表、集合等可迭代对象或者数组时
- index是当前迭代的次数, item的值是本次迭代获取的元素
当使用字典(或者Map.Entry对象的集合) 时
- index是键, item是值
bind
bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如: like查询 在MySQL中,使用concat函数连接字符串 这个函数支持多个参数
1 | <if test = "userName != null and userName != ''"> |
但在Oracle中支持两个参数 如果更换数据库,有些SQL语句可能就需要重写,使用bind标签来避免由于更换数据库带来的一些麻烦
1 | <select id="selectBlogsLike" resultType="Blog"> |
bind标签的两个属性都是必选项,name为绑定到上下文的变量名,value为OGNL表达式。创建一个bind标签的变量后,就可以在下面直接使用,使用bind拼接字符串不仅可以避免因更换数据库而修改SQL,也能防止SQL注入。
多数据库支持
如果配置了 databaseIdProvider,你就可以在动态代码中使用名为 “_databaseId
” 的变量来为不同的数据库构建特定的语句。比如下面的例子:
1 | <insert id="insert"> |
OGNL( Object Graph Navigation Language )
对象图导航语言, 这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。 类似于我们的EL, SpEL等
1 | 访问对象属性: person.name |
MyBatis-缓存机制
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率,MyBatis系统中默认定义了两级缓存。
- 一级缓存和二级缓存。
- 默认情况下,只有一级缓存( SqlSession级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性。 MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存
一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
本地缓存不能被关闭, 但可以调用 clearCache()来清空本地缓存, 或者改变缓存的作用域
在mybatis3.1之后, 可以配置本地缓存的作用域.在 mybatis.xml 中配置
- 该参数的取值为
SESSION
、STATEMENT
- 当指定localCacheScope参数值为
SESSION
时,缓存对整个SqlSession有效,只有执行DML语句(更新语句)时,缓存才会被清除。 - 当localCacheScope值为
STATEMENT
时,缓存仅对当前执行的语句有效,当语句执行完毕后,缓存就会被清空
1
2
3<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>- 该参数的取值为
一级缓存失效的四种情况
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
二级缓存
- 二级缓存(second level cache),全局作用域缓存
- 二级缓存默认不开启,需要手动配置, MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
- 二级缓存在 SqlSession 关闭或提交之后才会生效
• 使用步骤
全局配置文件中开启二级缓存
- 在 mybatis 中,二级缓存有全局开关和分开关, 全局开关, 在
mybatis-config.xml
中如下配置:
1
2
3
4
5<settings>
<!-- cacheEnabled是二是级缓存的总开关,默认是开启的为true,置为false代表关闭二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>- 在 mybatis 中,二级缓存有全局开关和分开关, 全局开关, 在
由于mybaits的二级缓存是mapper范围级别,所以除了在SqlMapConfig.xml设置二级缓存的总开关外,还要在具体的mapper.xml中开启二级缓存。注意: POJO需要实现Serializable接口
需要使用二级缓存的映射文件处使用cache配置缓存
- eviction=“FIFO”: 缓存回收策略:
- LRU – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
- 默认的是 LRU。
- flushInterval: 刷新间隔,单位毫秒
- 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
- size: 引用数目,正整数
- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
- readOnly: 只读, true/false
- true:只读缓存;会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这提供了很重要的性能优势。
- false:读写缓存; 会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<!--
表示此namespace使用二级缓存
cache标签的属性:
eviction="LRU" :清除策略,默认为LRU,移除最长时间不被使用的对象。
flushInterval="60000" :刷新间隔,以毫秒为单位的合理时间量
size="512" :引用数目
readOnly="true" :只读的缓存会给所有调用者返回缓存对象的相同实例
-->
<cache></cache>
<!--按照学号迭代查询-->
<!--
二级缓存
属性useCache="false",sql语句自己可以决定是否使用二级缓存
属性flushCache="false",sql语句自己可以决定是否刷新缓存,多用与新增,修改,删除语句
-->
<select id="findUser" resultType="User">
SELECT * FROM t_user WHERE id in
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>- eviction=“FIFO”: 缓存回收策略:
二级缓存是SqlSessionFactory级别的,整个应用程序只有一个
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/**
* 二级缓存是SqlSessionFactory级别的,整个应用程序只有一个
* 以namespace划分缓存区域
*/
public void test6() {
try {
Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
//SqlSession1,使用SqlSession查询第一次查询数据
SqlSession sqlSession1 = sessionFactory.openSession();
UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
//处理,假设从客户端传递过来要删除的多个参数为一个集合
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
System.out.println("SqlSession1查询id=1,2的数据为"+userDao1.findUser(list1));
sqlSession1.commit();
sqlSession1.close();//SqlSession关闭时会将数据写入二级缓存
System.out.println("----------------------------------");
//SqlSession2,使用SqlSession查询第二次查询数据
SqlSession sqlSession2 = sessionFactory.openSession();
UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
//处理,假设从客户端传递过来要删除的多个参数为一个集合
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
System.out.println("SqlSession2查询id=1,2的数据为"+userDao2.findUser(list1));
sqlSession2.commit();
sqlSession2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
缓存有关设置
- 全局setting的cacheEnable:
- 配置二级缓存的开关。一级缓存一直是打开的。
- select标签的useCache属性:
- 配置这个select是否使用二级缓存。一级缓存一直是使用的
- sql标签的flushCache属性:
- 增删改默认flushCache=true。 sql执行以后,会同时清空一级和二级缓存。
- 查询默认flushCache=false。
- sqlSession.clearCache():
- 只是用来清除一级缓存。
- 当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
第三方缓存整合
EhCache 是一个纯Java的进程内缓存框架,具有快速、精等特点,是Hibernate中默认的CacheProvider。
MyBatis定义了Cache接口方便我们进行自定义扩展。
步骤:
导入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
编写ehcache.xml配置文件
配置cache标签
1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
参照缓存: 若想在命名空间中共享相同的缓存配置和实例。可以使用 cache-ref 元素来引用另外一个缓存。
1
<cache-ref namespace="pers.fulsun.mybatis.exampLe.CustomerMapper" />
MyBatis-Spring整合
查看不同MyBatis版本整合Spring时使用的适配包 mybatis-spring
下载整合适配包
1
2
3MyBatis-Spring MyBatis Spring Framework Spring Batch Java
3.0 3.5+ 6.0+ 5.0+ Java 17+
2.1 3.5+ 5.x 4.x Java 8+官方整合示例, jpetstore https://github.com/mybatis/jpetstore-6
整合关键配置
1 | <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> |
MyBatis-逆向工程
- MyBatis Generator:简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、存储过程等这些复杂sql的定义需要我们手工编写
- 官方文档地址
- 官方工程地址
MyBatis-工作原理
MyBatis的工作原理以及核心流程介绍 - MyBatis中文官网
MyBatis四大核心对象:
- SqlSession对象,该对象中包含了执行SQL语句的所有方法【1】。类似于JDBC里面的Connection 【2】。
- Executor接口,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。
- MappedStatement对象,该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信息。
- ResultHandler对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型。
在JDBC中,Connection不直接执行SQL方法,而是利用Statement或者PrepareStatement来执行方法。在使用JDBC建立了连接之后,可以使用Connection接口的createStatement()方法来获取Statement对象,也可以调用prepareStatement()方法获得PrepareStatement对象,通过executeUpdate()方法来执行SQL语句。而在MyBatis中,SqlSession对象包含了执行SQL语句的所有方法,但是它是委托Executor执行的。从某种意义上来看,MyBatis里面的SqlSession类似于JDBC中的Connection,他们都是委托给其他类去执行。
MyBatis的工作原理如下图所示:
上面中流程就是MyBatis内部核心流程,每一步流程的详细说明如下文所述:
(1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
(2)加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
(3)构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。
(4)创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
(5)Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
(6)MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
(7)输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
(8)输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
MyBatis-插件开发
- MyBatis在四大对象的创建过程中,都会有插件进行介入。 插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
- MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
- 默认情况下, MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback,
- getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
插件开发
创建自定义 MyBatis 插件(拦截器)需要实现 org.apache.ibatis.plugin.Interceptor 接口,在类上添加 @Intercepts 注解,MyBatis 插件(拦截器)默认可以拦截的类型有四种,分别是 Executor、StatementHandler、ParameterHandler、ResultSetHandler,对自定插件(拦截器)必须使用 MyBatis 提供的 @Intercepts 注解来表明我们要拦截的是哪一种获几种接口,如下
1
2
3
4
5
6
7
8统计 SQL 执行消耗的时间,并打印SQL。
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
public class MyBatisSqlInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
//MappedStatement 中有 SQL 语句参数等信息
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
long startTime = System.currentTimeMillis();
try {
//继续执行业务方法
return invocation.proceed();
} finally {
//业务方法执行完了才执行 因此就是 SQL 耗时
//得到 SQL
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
String sql = boundSql.getSql();
sql = sql.replace("\n", "")
.replace("\t", "").replace(" ", " ")
.replace("( ", "(").replace(" )", ")")
.replace(" ,", ",").replaceAll(" +", " ");
log.info("执行SQL消耗的时间:{},SQL语句:{}", System.currentTimeMillis() - startTime, sql);
}
}
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}在全局配置文件中注册插件,Spring管理可以在类上添加 @Component 注解,交给 Spring 管理。
1
2
3
4<plugins>
<plugin interceptor="pers.fulsun.pLugin.MyBatisSqlInterceptor">
</plugin>
</plugins>Spring 管理方式如下:
1
2
3
4
5
6
7
8
9
10
11
public class MyInterptorConfig implements SmartInitializingSingleton {
private SqlSessionFactory sqlSessionFactory;
public void afterSingletonsInstantiated() {
sqlSessionFactory.getConfiguration().addInterceptor(new MyBatisSqlInterceptor());
}
}
Interceptor接口
Intercept:拦截目标方法执行
plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法
setProperties:注入插件配置时设置的属性
从代理链中分离真实被代理对象
1
2
3
4
5
6
7
8
9
10//1、分离代理对象。由于会形成多次代理,所以需要通过一个while 循环分离出最终被代理对象,从而方便提取信息
MetaObject metaObject = SystemMetaObject.forObject(target);
while (metaObject.hasGetter("h")) {
Object h = metaObject.getValue("h");
metaObject = SystemMetaObject.forObject(h);
}
//2、获取到代理对象中包含的被代理的真实对象
Object obj = metaObject.getValue("target");
//3、获取被代理对象的MetaObject方便进行信息提取
MetaObject forObject = SystemMetaObject.forObject(obj);
MyBatis实用场景
PageHelper插件
- PageHelper是MyBatis中非常方便的第三方分页插件。
- 官方文档:https://github.com/pagehelper/MybatisPageHelper/blob/master/README_zh.md
- 我们可以对照官方文档的说明,快速的使用插件
批量操作
默认的 openSession() 方法没有参数,它会创建有如下特性的
- 会开启一个事务(也就是不自动提交)
- 连接对象会从由活动环境配置的数据源实例得到。
- 事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
openSession 方法的 ExecutorType 类型的参数,枚举类型:
- ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情(这是默认装配的)。它为每个语句的执行创建一个新的预处理语句。
- ExecutorType.REUSE: 这个执行器类型会复用预处理语句。
- ExecutorType.BATCH: 这个执行器会批量执行所有更新语句
1
2
3
4
5
6
7
8SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionisolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionlsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);批量操作我们是使用MyBatis提供的BatchExecutor进行的,他的底层就是通过jdbc攒sql的方式进行的。我们可以让他攒够一定数量后发给数据库一次。
1
2
3
4
5
6
7
8
9
10
11
12
13public void test01() {
SqlSession openSession = build.openSession(ExecutorType.BATCH);
UserDao mapper = openSession.getMapper(UserDao.class);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
String name = UUID.randomUUID().toString().substring(0, 5);
mapper.addUser(new User(null, name, 13));
}
openSession.commit();
openSession.close();
long end = System.currentTimeMillis();
System.out.println("耗时时间: "+(end-start));
}与Spring整合中,我们推荐,额外的配置一个可以专门用来执行批量操作的sqlSession, 需要用到批量操作的时候,我们可以注入配置的这个批量SqlSession。通过他获取到mapper映射器进行操作
1
2
3
4<bean id= "sqLSession" class="org.mybatis.spring.SqLSessionTempLate">
<constructor-arg name="sqLSessionFactory" ref="sqLSessionFactoryBean"/>
<constructor-arg name="executorType" "value="BATCH""/>
</bean>批量操作是在
session.commit()
以后才发送sql语句给数据库进行执行的, 如果我们想让其提前执行,以方便后续可能的查询操作获取数据,我们可以使用sqlSession.flushStatements()
方法,让其直接冲刷到数据库进行执行。
存储过程
实际开发中,我们通常也会写一些存储过程,MyBatis也支持对存储过程的调用
一个最简单的存储过程
1
2
3
4
5
6delimiter $$
create pr replace procedure test()
begin
select 'hello';
end $$
delimiter ;存储过程的调用
select标签中
statementType="CALLABLE"
标签体中调用语法:
1
{call procedure_name(#{param1_info},#{param2_info})}
存储过程-游标处理
MyBatis对存储过程的游标提供了一个
dbcType=CURSOR
的支持,可以智能的把游标读取到的数据,映射到我们声明的结果集中调用实例:
1
2
3
4
5
6
7
8
9
10
11
12<select id="getPage" parameterType="PageEmp" statementType="CALLABLE" databaseld="oracLe">
{call PAGE_EMP(
#{start,mode=IN,jdbcType=INTEGER),
#{end,mode=IN,jdbcType=INTEGER),
#{count,mode=0UT,jdbcType=INTEGER),
#{emps,mode=0UT,jdbcType=CURSOR, javaType=ResultSet,resultMap=TestEmp}
)}
</select>
<resultMap type="Emp" id="TestEmp">
<id column="EMPNO" property="id"/>
</resultMap>
自定义TypeHandler处理枚举
我们可以通过自定义TypeHandler的形式来在设置参数或者取出结果集的时候自定义参数封装策略。
步骤:
实现TypeHandler接口或者继承BaseTypeHandler
使用
@MappedTypes
定义处理的java类型, 使用@MappedJdbcTypes
定义jdbcType类型在自定义结果集标签或者参数处理的时候声明使用自定义TypeHandler进行处理或者在全局配置TypeHandler要处理的javaType
1
2
3
4<insert id="addDept">
insert into department(dept_name,status)
values(#{deptName},#{status,typeHandler=pers.fulsun.type.MyEnumTypeHandler})
</insert>
Mybatis 注解
常用注解说明
1
2
3
4
5
6
7
8
9
10
11@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用使用注解方式开发持久层接口
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
37public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
User findById(Integer userId);
int saveUser(User user);
int updateUser(User user);
int deleteUser(Integer userId);
int findTotal();
List<User> findByName(String name);
}SqlMapConfig 配置文件
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
<configuration>
<!-- 配置 properties 文件的位置 -->
<properties resource="jdbcConfig.properties"></properties>
<!-- 配置别名的注册 -->
<typeAliases>
<package name="pers.fulsun.domain" />
</typeAliases>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境 -->
<environment id="mysql">
<!-- 配置事务的类型是 JDBC -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 配置映射信息 -->
<mappers>
<!-- 配置 dao 接口的位置,它有两种方式
第一种:使用 mapper 标签配置 class 属性
第二种:使用 package 标签,直接指定 dao 接口所在的包
-->
<package name="pers.fulsun.dao" />
</mappers>
</configuration>
springboot下的配置
禁用一级缓存:mybatis没有提供一级缓存的启用、禁用开关,但在Mapper文件对应的语句中增加
flushCache="true"
可以达到实际禁用一级缓存的效果,一般同时还会加上useCache=”false”,以便关闭二级缓存;下面讨论使用springboot配置的方式控制一级缓存。
1
2
3
4mybatis: statement
configuration:
cache-enabled: false #禁用二级缓存
local-cache-scope: session #一级缓存指定为session级别 statement每次查询结束都会清掉一级缓存