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抽取出来。
配置式编程
操作数据库:
创建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>创建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>
测试:
- 根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象,有数据源一些运行环境信息
- sql映射文件;配置了每一个sql,以及sql的封装规则等
- 将sql映射文件注册在全局配置文件中
- 写代码:
- 根据全局配置文件得到SqlSessionFactory
- 使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
- 一个sqlSession就是代表和数据库的一次会话,用完关闭
- 使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的
1 |
|
接口式编程
常用
创建一个Dao接口:
1 |
|
创建Mapper文件:
1 |
|
测试:
1 |
|
全局配置文件
1 |
|
configuration
配置:子标签有先后顺序
properties
属性:可以使用properties标签引入外部properties配置文件的内容
- resource:引入类路径下的资源
- url:引入网络或磁盘路径下的资源
1 |
|
settings
设置:极为重要的调整设置,它们会改变MyBatis的运行时行为
settings:用来设置每一个设置项
name:设置项名
- cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存
- lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态
- useColumnLabel:使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察
- defaultStatementTimeout:设置超时时间,它决定数据库驱动等待数据库响应的秒数
- mapUnderscoreToCamelCase:是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN映射到经典Java属性名aColumn
- ……
value:设置项取值
1 |
|
typeAliases
类型命名:类型别名可为Java类型设置一个缩写名字,可以方便引用某个类,别名不区分大小写
- typeAlias:为某个类起别名
- type:类型全类名,默认类名小写
- alias:指定新别名
- package:为某个包下的类批量起别名,若包和子包中的类名一样则会报错
- name:为当前包和所有后代包的每个类都起个别名
- @Alias:在类上加此注解为其制定别名
1 |
|
1 |
|
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 |
数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler |
java.lang.Short , short |
数据库兼容的 NUMERIC 或 SMALLINT |
IntegerTypeHandler |
java.lang.Integer , int |
数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler |
java.lang.Long , long |
数据库兼容的 NUMERIC 或 BIGINT |
FloatTypeHandler |
java.lang.Float , float |
数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler |
java.lang.Double , double |
数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler |
java.math.BigDecimal |
数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler |
java.lang.String |
CHAR , VARCHAR |
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 | 任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。 |
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 |
VARCHAR 或 LONGVARCHAR |
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 |
|
environment
环境变量:配置一个具体的环境信息,必须有两个transactionManager和dataSource
- id:代表当前环境的唯一标识
1 |
|
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接口
- DB_VENDOR:使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识
1 |
|
1 |
|
匹配规则:
- 如果没有配置databaseIdProvider标签,那么databaseId=null
- 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null
- 如果databaseId不为null,他只会找到配置databaseId的sql语句
- MyBatis会加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有语句。如果同时找到带有databaseId和不带databaseId的相同语句,则后者会被舍弃
mappers
映射器:将SQL映射注册到全局配置中
- mapper:注册一个映射
- resource:引用类路径下的文件
- url:引用网络或磁盘路径下的文件
- class:引用注册接口
- 方式1:SQL映射名必须与接口同名同目录
- 方式2:SQL利用注解写在接口上
- 可以按需混合使用
- package:批量注册,SQL映射文件名必须和接口名相同并且在同一目录下
1 |
|
映射文件
映射文件指导着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
- userGeneratedKeys:MyBatis利用statement.getGeneratedKeys()获取主键值策略(增改时)
- 默认:false
- keyProperty:指定对应的主键属性,获取到主键值后将此值封装给JavaBean的哪个属性值
- 默认:unset
- keyColumn:通过生成的键值设置表中的列名(增改时),仅在某些数据库中是必须的
- databaseId:若配置的databaseIdProvider,MyBatis会加载所有不带databaseId或匹配当前databaseId的语句
- 若带或不带的语句都有则不带的会被忽略
1 |
|
1 |
|
主键生成
数据库支持:如MySQL/SQL Server等,设置useGeneratedKeys=”true”,然后再把keyProperty设置到目标属性上
1 |
|
数据库不支持:如Oracle,使用selectKey子元素
- selectKey元素将会首先运行,id会被设置,然后插入语句会被调用
- keyProperty:查出的主键值封装给JavaBean的哪个属性
- order:当前SQL运行顺序
- BEFORE
- AFTER
- resultType:查出数据的返回值类型
- keyColumn:匹配属性的返回结果集中的列名称
- statementType:与之前相同
1 |
|
参数传递
单个参数:
- #{参数名}:取出参数值
- 可接受基本类型,对象类型,集合类型的值
- 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
5public 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
5public 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
10public 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 |
|
源码:
1 |
|
参数值获取:
1 |
|
#{}:可以获取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
2select * 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
9List<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_COLUMNaColumn,我们可以开启自动驼峰命名规 则映射功能,mapUnderscoreToCamelCase=true
自定义resultMap
实现高级结果集映射
resultMap id:唯一ID方便引用
resultMap type:自定义Java类型
id:指定主键列的封装规则,定义主键底层有优化
- column:指定哪一列
- property:指定对应JavaBean属性
resoult:定义普通列封装规则
- 参数同上
其它不指定的列:自动封装
- 只要写resoultMap就把所有的列全部写上映射规则
1 |
|
1 |
|
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 |
|
动态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 |
|
choose
分支选择
when, otherwise,类似于带了break的switch-case
1 |
|
trim
字符串截取
where标签不能解决后面多出的and或者or,需要使用trim解决
trim标签体中是整个字符串拼串后的结果
- prefix=””:前缀,prefix给拼串后的整个字符串加一个前缀
- prefixOverrides=””:前缀覆盖,去掉整个字符串前面多余的字符
- suffix=””:后缀,suffix给拼串后的整个字符串加一个后缀
- suffixOverrides=””:后缀覆盖,去掉整个字符串后面多余的字符
where:封装查询条件
1 |
|
set:封装修改条件
1 |
|
foreach
遍历
- collection:指定要遍历的集合
- list类型的参数会特殊处理封装在map中,map的key就叫list
- item:将当前遍历出的元素赋值给指定的变量
- separator:每个元素之间的分隔符
- open:遍历出所有结果拼接一个开始的字符
- close:遍历出所有结果拼接一个结束的字符
- index;索引
- 遍历list的时候是index就是索引,item就是当前值
- 遍历map的时候index表示的就是map的key,item就是map的值
- #{变量名}就能取出变量的值也就是当前遍历出的元素
1 |
|
1 |
|
内置参数
不只是方法传递过来的参数可以被用来判断,mybatis默认还有两个内置参数
_parameter:代表整个参数
- 单个参数:_parameter就是这个参数
- 多个参数:参数会被封装为一个map,_parameter就是代表这个map
_databaseId:如果配置了databaseIdProvider标签
- _databaseId就是代表当前数据库的别名
1 |
|
bind
可以从OGNL表达式中创建一个变量并将其绑定到上下文
1 |
|
sql
抽取可重用的sql片段,方便后面引用
sql:经常将要查询的列名,或者插入用的列名抽取出来方便引用
include:引用已经抽取的sql
- include可以自定义property,sql标签内部就能使用自定义的属性
- include-property:取值的正确方式${prop},#{不能使用这种方式}
1 |
|
Multi-db vendor support
在mybatis配置文件中配置了databaseIdProvider , 则可以使用“_databaseId”变量,这样就可以根据不同的数据库 厂商构建特定的语句
缓存机制
可以非常方便地配置和定制,缓存可以极大的提升查询效率
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存(SqlSession级别的缓存, 也称为本地缓存)开启
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性MyBatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存
查询顺序:
- 二级缓存
- 一级缓存
- 数据库
一级缓存
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关闭或提交之后才会生效
工作机制:
- 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息就可以参照二级缓存中的内容
- 不同namespace查出的数据会放在自己对应的缓存中(map)
- 数据会从二级缓存中获取
- 查出的数据都会被默认先放在一级缓存中
- 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
使用步骤:
全局配置文件中开启二级缓存
1
<setting name="cacheEnabled" value="true"/>
需要使用二级缓存的映射文件(XxxMapper)处使用cache配置缓存
- 属性:
eviction=“FIFO”:缓存回收策略
- LRU:最近最少使用的,移除最长时间不被使用的对象,默认
- FIFO:先进先出,按对象进入缓存的顺序来移除它们
- SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInterval:刷新间隔,单位毫秒
- 缓存多长时间清空一次,默认不清空
size:引用数目,正整数
- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly:只读,true/false
- true:只读
- mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据
- mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。
- 不安全,速度快
- false:非只读
- mybatis觉得获取的数据可能会被修改
- mybatis会利用序列化&反序列的技术克隆一份新的数据给你
- 安全,速度慢
- true:只读
type:指定自定义缓存的全类名,实现Cache接口即可
1
<cache eviction="FIFO" flushInterval="300" readOnly="true"/>
- 属性:
<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接口方便我们进行自定义扩展,步骤:
导入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配置文件
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(先进先出)
-->配置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 |
|
web
1 |
|
Spring
Spring管理所有的业务逻辑组件:数据源,事务控制,AOP
1 |
|
SpringMVC
SpringMVC控制网站跳转逻辑
1 |
|
MyBatis
1 |
|
逆向工程
MyBatisGenerator,简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件、接口以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、 存储过程等这些复杂sql的定义需要我们手工编写
官方文档地址 http://www.mybatis.org/generator/
官方工程地址 https://github.com/mybatis/generator/releases
使用步骤:
编写MBG的配置文件:重要几处配置
- jdbcConnection配置数据库连接信息
- javaModelGenerator配置javaBean的生成策略
- sqlMapGenerator配置sql映射文件生成策略
- javaClientGenerator配置Mapper接口的生成策略
- 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>运行代码生成器生成代码
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向后追加内容出现的问题
工作原理
运行顺序:
根据配置文件创建SQLSessionFactory
- SqlSessionFactoryBuilder
- 创建SqlSessionFactoryBuilder对象
- build(inputStream)
- XmlConfigBuilder
- 创建解析器parser
- 解析每一个标签并把详细信息保存在Configuration中
- 解析mapper.xml
- Configutation
- 返回Configuration
- XmlConfigBuilder
- build(Configuration)
- DefaultSqlSessionFactory
- new DefaultSqlSessionFactory()
- 返回创建的DefaultSqlSessionFactory,包含了保存全局配置信息的Configuration
- SqlSessionFactoryBuilder
- SqlSessionFactoryBuilder
返回SqlSession的实现类DefaultSession对象
- DefaultSqlSessionFactory
- openSessionFromDataSource()
- Configuration
- 获取信息创建tx
- new Executor()
- 根据Executor在全局配置中的类型创建SimpleExecutor/ReuseExecutor/BatchExecitor
- Executor
- 若开启二级缓存,创建CachingExecutor(executor)
- executor = (Executor) interceptorChain.pluginAll(executor),使用每一个拦截器重新包装executor并返回
- 创建DefaultSqlSession,包含Configuration和Executor
- DefaultSqlSession:SqlSessioon
- 返回DefaultSqlSession
- DefaultSqlSessionFactory
- DefaultSqlSessionFactory
getMapper返回接口的代理对象
- DefaultSqlSession:SqlSession
- getMapper(type)
- Configuration
- getMapper(type)
- MapperRegistry
- getMapper()
- 根据接口类型说去MapperProxyFactory
- MapperProxyFactory
- newInstance(sqlSession)
- 创建MapperProxy,是一个InvocationHandler
- 创建MapperProxy的代理对象
- 返回MapperProxy的代理对象
- DefaultSqlSession:SqlSession
- DefaultSqlSession:SqlSession
- MapperProxy
- invoke()
- MapperMethod
- 判断增删改查类型
- 包装参数为一个Map或直接返回
- sqlSession.selectOne()
- DefaultSqlSession:SqlSession
- selectList()
- 获取MappedStatement
- Executor.query(ms, xxx, x)
- Executor
- 获取BoundSql,它代表SQL语句的详细信息
- executor.query
- SimpleExecutor
- 查看本地缓存是否有数据,没有就调用queryFromDatabase,查出以后会保存在本地缓存
- doQuery()
- BaseExecutor
- 创建StatementHandler对象,PreparedStatementHandler
- StatementHandler(处理SQL语句预编译,设置参数等工作,可以创建出Statement对象)
- (StatementHandler)interceptorChain.pluginAll(statementHandler)
- 创建ParameterHandler(设置参数),interceptorChain.pluginAll(parameterHandler)
- 创建ResultSetHandler(处理结果),interceptorChain.pluginAll(resultSetHandler)
- 预编译SQL产生PreparedStatement对象
- 调用ParamentHandler设置参数
- 调用TypeHandler(在整个过程中进行数据库类型和JavaBean类型的映射)给SQL预编译设置参数
- 查出数据使用ResultSetHandler处理结果,使用TypeHanler获取value值
- 后续的连接关闭
- DefaultSqlSession:SqlSession
- 返回list第一个
- MapperMethod
- MapperProxy
获取sqlSessionFactory对象:
- 解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession
- MappedStatement代表一个增删改查的详细信息
获取sqlSession对象:
- 返回一个DefaultSqlSession对象,包含Executor和Configuration
- 这一步会创建Executor对象
获取接口的代理对象:MapperProxy
- getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
- 代理对象里面包含了,DefaultSqlSession(Executor)
执行增删改查方法
总结:
- 根据配置文件(全局,SQL映射)初始化出Configuration对象
- 创建一个DefaultSqlSession对象,他里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
- DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy
- MapperProxy里面有(DefaultSqlSession)
- 执行增删改查方法:
- 调用DefaultSqlSession的增删改查(Executor)
- 会创建一个StatementHandler对象(同时也会创建出ParameterHandler和ResultSetHandler)
- 调用StatementHandler预编译参数以及设置参数值(使用ParameterHandler来给sql设置参数)
- 调用StatementHandler的增删改查方法
- ResultSetHandler封装结果
四大对象:
- Executor
- StatementHander:处理SQL语句预编译,设置参数等相关工作
- ParameterHandler和ResultHandler
- ParameterHandler:设置预编译参数用
- ResultHandler:处理结果集
- TypeHandler:在整个过程中进行数据库类型和JavaBean类型的映射
四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler)
1 |
|
插件开发
MyBatis在四大对象的创建过程中都会有插件进行介入,插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果
MyBatis允许在已映射语句执行过程中的某一点进行拦截调用
默认情况下,MyBatis允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
原理:
在四大对象创建时:
每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler)
获取到所有的Interceptor(拦截器)(插件需要实现的接口)调用interceptor.plugin(target)返回target包装后的对象
插件机制:
- 可以使用插件为目标对象创建一个代理对象:AOP(面向切面)
- 我们的插件可以为四大对象创建出代理对象
- 代理对象就可以拦截到四大对象的每一个执行
开发步骤:
编写插件实现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);
}
}在全局配置文件中注册插件
1
2
3
4
5
6<plugins>
<plugin interceptor="MyFirstPlugin">
<property name="username" value="admin"/>
<property name="password" value="12345"/>
</plugin>
</plugins>
插件原理:
- 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
- 多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹,形成代理链
- 目标方法执行时依次从外到内执行插件的intercept方法
- 多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获 取最后一层的h以及target属性的值
Interceptor
Intercept:拦截目标方法执行
plugin:生成动态代理对象,可以使用MyBatis提 供的Plugin类的wrap方法
setProperties:注入插件配置时设置的属性
1 |
|
扩展
PageHelper
MyBatis中非常方便的第三方分页插件,可以对照官方文档的说明快速的使用插件
官方文档: https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
使用步骤:
导入相关包pagehelper-x.x.x.jar和jsqlparser-
0.9.5.jar。在MyBatis全局配置文件中配置分页插件
1
2
3<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>使用PageHelper提供的方法进行分页
可以使用更强大的PageInfo封装返回结果
1 |
|
批量操作
默认的openSession()方法没有参数,它会创建有如下特性的
- 会开启一个事务,也就是不自动提交
- 连接对象会从由活动环境配置的数据源实例得到。
- 事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
openSession方法的ExecutorType类型的参数,枚举类型:
- ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情(这是默认装配的)。它为每个语句的执行创建一个新的预处理语句
- ExecutorType.REUSE:这个执行器类型会复用预处理语句
- ExecutorType.BATCH:这个执行器会批量执行所有更新语句
批量操作我们是使用MyBatis提供的BatchExecutor进行的, 他的底层就是通过jdbc攒sql的方式进行的。我们可以让他 攒够一定数量后发给数据库一次
1 |
|
Spring整合
推荐额外的配置一个可以专门用来执行批量操作的sqlSession
1 |
|
需要用到批量操作的时候,我们可以注入配置的这个批量 SqlSession。通过他获取到mapper映射器进行操作
1 |
|
注意:
- 批量操作是在session.commit()以后才发送sql语句给数据库进行执行的
- 如果我们想让其提前执行,以方便后续可能的查询操作 获取数据,我们可以使用sqlSession.flushStatements()方法,让其直接冲刷到数据库进行执行
存储过程
实际开发中,我们通常也会写一些存储过程, MyBatis也支持对存储过程的调用
存储过程的调用:
- select标签中statementType=“CALLABLE”
- 标签体中调用语法:{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的形式来在设置参数或取出结果集的时候自定义参数封装策略
步骤:
实现TypeHandler接口或者继承BaseTypeHandler
使用@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
30public 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);
}
}在自定义结果集标签或者参数处理的时候声明使用自定义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 |
|
1 |
|