Mybatis 之 插件
插件编写(plugin)
步骤:
1. 编写Interceptor的实现类:
MyFirstPlug
2. 使用@Intercepts注解完成插件签名
3. 将写好的插件注册到全局配置文件中
package com.feng.config;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
/**
* @Desc:完成插件签名:
* 告诉mybatis当前插件用来拦截哪个对象的哪个方法
* @Param: Sinature type:拦截哪个对象(四大对象)
* method:对象的哪个方法
* args: 当前方法的参数列表
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstPlug implements Interceptor {
/**
* 拦截目标对象的目标方法的执行
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
//TODO ... 动态修改mybatis运行流程;比如sql分页、修改sql使用的参数。。。
//执行目标方法: parameterrize
Object proceed = invocation.proceed();
//返回执行后的返回值
//TODO ...
return proceed;
}
/**
* 包装目标对象,为目标对象创建一个代理对象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
//借助Plugin的wrap方法来使用当前Interceptor包装目标对象
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理对象
return wrap;
}
/**
* 将插件注册时的property属性设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置信息:"+properties);
String username = (String) properties.get("username");
String password = (String) properties.get("password");
}
}
mybatis-config.xml
<configuration>
<!-- 配置全局插件 -->
<plugins>
<plugin interceptor="com.feng.config.MyFirstPlug">
<property name="username" value="zhangsan"/>
<property name="password" value="123456"/>
</plugin>
</plugins>
</configuration>
插件扩展:
插件拦截的场景:
场景一:修改sql使用的参数
package com.feng.config;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.util.Properties;
/**
* @Desc:完成插件签名:
* 告诉mybatis当前插件用来拦截哪个对象的哪个方法
* @Param: Sinature type:拦截哪个对象(四大对象)
* method:对象的哪个方法
* args: 当前方法的参数列表
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstPlug implements Interceptor {
/**
* 拦截目标对象的目标方法的执行
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("intercept:"+invocation.getTarget());
//动态的改变一下sql的参数:
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", 12);
//返回执行后的返回值
Object proceed = invocation.proceed();
return proceed;
}
/**
* 包装目标对象,为目标对象创建一个代理对象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
//借助Plugin的wrap方法来使用当前Interceptor包装目标对象
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理对象
return wrap;
}
/**
* 将插件注册时的property属性设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置信息:"+properties);
String username = (String) properties.get("username");
String password = (String) properties.get("password");
}
}
场景二:PageHelper插件进行分页
a . 引入依赖
在pom.xml文件添加如下依赖:
<!-- mybatis分页插件依赖 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.2.0</version>
</dependency>
b. 在Mybatis配置xml中配置拦截器插件
在mybatis-config.xml文件中添加插件后的内容如下:
<?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>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样-->
<property name="offsetAsPageNum" value="true"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="false"/>
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<property name="params" value="pageNum=start;pageSize=limit;"/>
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="check"/>
</plugin>
</plugins>
</configuration>
c. 默认jar包中的实现类
package com.github.pagehelper;
import com.github.pagehelper.util.MSUtils;
import com.github.pagehelper.util.StringUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* 最简单的分页插件拦截器
*/
@SuppressWarnings("rawtypes")
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PaginationInterceptor implements Interceptor {
protected Dialect dialect;
protected Field additionalParametersField;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取拦截方法的参数
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameterObject = args[1];
RowBounds rowBounds = (RowBounds) args[2];
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameterObject, rowBounds)) {
ResultHandler resultHandler = (ResultHandler) args[3];
//当前的目标对象
Executor executor = (Executor) invocation.getTarget();
BoundSql boundSql = ms.getBoundSql(parameterObject);
//反射获取动态参数
Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameterObject, rowBounds)) {
//创建 count 查询的缓存 key
CacheKey countKey = executor.createCacheKey(ms, parameterObject, RowBounds.DEFAULT, boundSql);
countKey.update("_Count");
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
MappedStatement countMs = MSUtils.newCountMappedStatement(ms);
//调用方言获取 count sql
String countSql = dialect.getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);
BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
//当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
for (String key : additionalParameters.keySet()) {
countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行 count 查询
Object countResultList = executor.query(countMs, parameterObject, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
Long count = (Long) ((List) countResultList).get(0);
//处理查询总数
dialect.afterCount(count, parameterObject, rowBounds);
if (count == 0L) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameterObject, rowBounds);
}
}
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameterObject, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql);
//处理参数对象
parameterObject = dialect.processParameterObject(ms, parameterObject, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObject);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行分页查询
resultList = executor.query(ms, parameterObject, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
resultList = new ArrayList();
}
} else {
args[2] = RowBounds.DEFAULT;
resultList = (List) invocation.proceed();
}
//返回默认查询
return dialect.afterPage(resultList, parameterObject, rowBounds);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
String dialectClassStr = properties.getProperty("dialect");
if (StringUtil.isEmpty(dialectClassStr)) {
throw new RuntimeException("使用 PaginationInterceptor 分页插件时,必须设置 dialect 属性");
}
try {
Class dialectClass = Class.forName(dialectClassStr);
dialect = (Dialect) dialectClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("初始化 dialect [" + dialectClassStr + "]时出错:" + e.getMessage());
}
dialect.setProperties(properties);
try {
//反射获取 BoundSql 中的 additionalParameters 属性
additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
additionalParametersField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
}
d.使用page

或

场景三:批量操作
与Spring整合进行批量操作
<!--整合mybatis
目的:1、Spring管理所有组件,
2、Spring用来管理事务
-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- configLocation 指定全局配置文件的位置-->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!--mapperLocations:指定mapper文件的位置-->
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
</bean>
<!-- 配置一个可以进行批量执行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
<constructor-arg name="executorType" ref="BATCH"></constructor-arg>
</bean>
java中使用:
@Autowired
private SqlSession sqlSession;
场景四:存储过程
场景五:typeHandler处理枚举
可以通过自定义TypeHandler的形式来在设置参数或取出结果集的时候自定义参数封装策略
步骤:
1. 实现TypeHandler接口或继承BaseTypeHandler
2. 使用@MappedTypes定义处理的java类型
3. 在自定义结果集标签或参数处理的时候声明使用自定义TypeHandler进行处理
或在全局配置TypeHandler要处理的javaType
多插件编写:
<configuration>
<!-- 配置全局插件 -->
<plugins>
<plugin interceptor="com.feng.config.MyFirstPlug">
<property name="username" value="zhangsan"/>
<property name="password" value="123456"/>
</plugin>
<plugin interceptor="com.feng.config.MySecondPlug"></plugin>
</plugins>
</configuration>


浙公网安备 33010602011771号