MyBatis 工作流程及插件开发
1. MyBatis 框架分层架构
2. MyBatis 工作流程
- 获取 SqlSessionFactory 对象:
- 解析配置文件(全局映射,Sql映射文件)的每一个信息,并保存在Configuration中,返回包含Configuration
的DefaultSqlSession; MappedStatement
: 代表一个增删改查标签的详细信息;
- 解析配置文件(全局映射,Sql映射文件)的每一个信息,并保存在Configuration中,返回包含Configuration
- 获取 SqlSession 对象:
- 返回一个DefaultSqlSession对象,包含Executor和Configuration;
- 获取接口的代理对象(MapperProxy)
getMapper()
使用MapperProxyFactory创建一个MapperProxy的代理对象;- 代理对象中包含了DefaultSqlSession(Executor);
- 执行增删改查方法
- 代理对象查询依赖DefaultSqlSession对象中的Executor,Executor创建StatementHandler对象,
同时,创建ParameterHandler和ResultSetHandler对象,而ParameterHandler和ResultSetHandler都依赖TypeHandler; - StatementHandler: 设置sql语句,预编译,设置参数等相关工作,以及执行增删改查方法;
- ParameterHandler: 设置预编译参数;
- ResultHandler: 处理查询结果集;
- TypeHandler: 在设置参数和处理查询结果时,都是依赖TypeHandler,进行数据库类型和javaBean类型的映射;
- 代理对象查询依赖DefaultSqlSession对象中的Executor,Executor创建StatementHandler对象,
3. MyBatis 插件开发
- MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用.MyBatis支持对以下方法(四大对象)的拦截:
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
- 在创建四大对象的时候,并不是直接返回的,而是
interceptorChain.pluginAll(xxxHandler)
; - pluginAll 方法就是获取到所有的Interceptor(插件需要实现的接口),调用
interceptor.plugin(target)
,
最终,返回包装后的target
对象;
// pluginAll() 源码
public Object pluginAll(Object target){
for(Interceptor interceptor : interceptors){
target = interceptor.plugin(target);
}
return target;
}
3.1 插件的编写
- 步骤:
- 编写Interceptor的实现类;
- 使用
@Intercepts
注解完成插件签名; - 将写好的插件注册到全局配置文件中;
- 多个插件的执行顺序
- 创建动态代理的时候,是按照插件的配置顺序,创建层层代理对象;
- 执行目标方法的时候,按照逆序执行;
// 编写Interceptor实现类: MyFirstPlugin.java
// @Intercepts 注解: 为当前插件指定要拦截哪个对象的哪个方法,以及方法中的参数
@Intercepts(
{
@Signature(type=StatementHandler.class,method="parameterize",
args=java.sql.Statement.class)
}
)
public class MyFirstPlugin implements Interceptor{
// 拦截目标对象中目标方法的执行
public Object intercept(Invocation invocation) throws Throwable{
// 执行目标方法
Object proceed = invocation.proceed();
// 返回拦截之后的目标方法
return proceed;
}
// 包装目标对象,即为目标对象创建一个代理对象
public Object plugin(Object target){
// 借助 Plugin 的 wrap(Object target,Interceptor interceptor); 包装我们的目标对象
// target: 目标对象, interceptor: 拦截器, this 表示使用当前拦截器
Object proxy = Plugin.wrap(target,this);
return proxy;
}
// 可以获取插件注册时,传入的property属性
public void setProperties(Propreties properties){
System.out.println("插件的配置信息:"+properties);
}
}
// 在全局配置文件: mybatis-config.xml 中注册插件
<plugins>
<!-- interceptor: 拦截器的类路径 -->
<plugin interceptor="cn.itcast.mybatis.dao.MyFirstPlugin">
<property name="username" value="zhangsan"/>
</plugin>
</plugins>
4. 使用 PageHelper 插件进行分页
// 测试类: 查询所有员工
public class MyBatisTest{
@Test
public void test() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
// 参数说明: 1 表示第一页数据, 3 表示每页3条数据
Page<Object> page = PageHelper.startPage(1,3);
List<Employee> emps = mapper.getEmps();
for(Employee emp : emps){
System.out.println(emp);
}
System.out.println("当前页码:"+page.getPageNum());
System.out.println("总记录数:"+page.getTotal());
System.out.println("每页记录数:"+page.getPageSize());
System.out.println("总页码:"+page.getPages());
} finally{
openSession.close();
}
}
}
// 在mybatis-plugin中注册PageHelper插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
5. 批量操作
// 测试类: 批量保存员工
public class MyBatisTest{
@Test
public void test() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 可以执行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
// 向数据库中插入10000条数据
for(int i=0; i<10000; i++){
mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0,5),
"zhangsan@163.com","1");
}
openSession.commit();
}finally{
openSession.close();
}
}
6. 自定义类型处理器(TypeHandler)处理枚举
- 我们可以通过自定义TypeHandler的形式来在设置参数或者取出结果集的时候,自定义参数封装策略;
- 步骤:
- 实现TypeHandler接口或者继承BaseTypeHandler;
- 使用
@MappedTypes
定义处理的java类型;
使用@MappedJdbcTypes
定义jdbcType类型; - 在自定义结果集标签或者参数处理的时候,声明使用自定义 TypeHandler 进行处理
或者在全局配置自定义TypeHandler;
// Employee.java
public class Employee{
private Integer id;
private String lastName;
private String email;
private String gender;
// 枚举类型: 用户状态包括登录,登出,不存在
// 用户默认状态:用户登出
private EmpStatus empStatus=EmpStatus.LOGOUT;
get 和 set 方法(略)
}
// EmpStatus.java
public enum EmpStatus{
LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在")
}
// EmployMapper.xml
<!-- 保存客户 -->
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender,empStatus)
values(#{lastName},#{email},#{gender},#{empStatus})
</insert>
// mybatis-config.xml
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="cn.itcast.mybatis.bean.EmpStatus"/>
</typeHandlers>
// 测试类
public class MyBatisTest{
@Test
public void test() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee("test_enum","zhangsan@163.com","1");
// MyBatis 在处理枚举对象时,默认保存的是枚举的名字: EnumTypeHandler
// 也可以使用枚举的索引来保存:EnumOrdinalTypeHandler
//
mapper.addEmp(employee);
System.out.println("保存成功"+employee.getId());
openSession.commit();
}finally{
openSession.close();
}
@Test
public void testEnumUse(){
EmpStatus login = EmpStatus.LOGIN;
System.out.println("枚举的索引:"+login.ordinal());
System.out.println("枚举的名字:"+login.name());
}
}
// 升级版: 数据库保存的是 100, 200等这些自定义的状态码,而不是枚举的索引或者枚举的名字
// EmpStatus.java
public enum EmpStatus{
LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在");
private Integer code; // 状态码
private String msg; // 枚举的提示信息
// 有参构造函数
private EmpStatus(Integer code, String msg){
this.code = code;
this.msg = msg;
}
get 和 set 方法(略)
// 按照状态码,返回枚举对象
public static EmpStatus getEmpStatusByCode(Integer code){
switch(code){
case 100:
return LOGIN;
case 200:
return LOGOUT;
case 300:
return REMOVE;
default:
return LOGOUT;
}
}
}
// 自定义枚举处理器
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus>{
//定义当前数据如何保存到数据库中
public void setParameter(PreparedStatement ps, int i, EmpStatus parameter,
JdbcType jdbcType) throws SQLException{
ps.setString(i,parameter.getCode().toString());
}
//从数据库中获取到枚举状态码,返回一个枚举对象
public EmpStatus getResult(ResultSet rs, String columnName) throws SQLException{
int code = rs.getInt(columnName);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
public EmpStatus getResult(ResultSet rs, String columnIndex) throws SQLException{
int code = rs.getInt(columnIndex);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
public EmpStatus getResult(CallableStatement cs, String columnIndex) throws SQLException{
int code = cs.getInt(columnIndex);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
}
// mybatis-config.xml 中配置自定义枚举处理器
<typeHandlers>
<typeHandler handler="cn.itcast.mybatis.typehandler.MyEnumEmpStatusTypeHandler"
javaType="cn.itcast.mybatis.bean.EmpStatus"/>
</typeHandlers>
// 测试类
public class MyBatisTest{
@Test
public void testEnumUse(){
EmStatus login = EmpStatus.LOGIN;
System.out.println("枚举的索引:"+login.ordinal());
System.out.println("枚举的名字:"+login.name());
System.out.println("枚举的状态码:"+login.getCode());
System.out.println("枚举的提示信息:"+login.getMsg());
}
@Test
public void testEnum() throws IOException{
同上;
}
}
参考资料