Java mybatis 开发自定义插件
作者:索码理 发布时间:2022-11-26 03:29:24
介绍
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。比如执行前、执行后或者对SQL结果集处理、sql入参处理等,这样就可以在不修改mybatis源码的情况下对sql执行的过程或结果进行修改,实现了解耦。mybatis 是在 * 的基础上实现的。
使用场景
如果业务中需要设置一些通用数据库操作,比如创建时间、创建人等通用字段又或者是分页操作等,这类都可以使用插件开发方式,PageHelper就是基于Interceptor的一个mybatis插件。
Interceptor *
public interface Interceptor {
/**
* 子类 * 必须要实现的方法,
* 在该方法对内自定义拦截逻辑
* @param invocation
* @return
* @throws Throwable
*/
Object intercept(Invocation invocation) throws Throwable;
/**
生成目标类的代理对象
* 也可以根据需求不返回代理对象,这种情况下这个 * 将不起作用
* 无特殊情况使用默认的即可
* @param target
* @return
*/
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 设置变量
* 在注册 * 的时候设置变量,在这里可以获取到
* @param properties
*/
default void setProperties(Properties properties) {
// NOP
}
}
InterceptorChain * 链
在org.apache.ibatis.plugin包下有个InterceptorChain类,该类有个interceptors属性,所有实现了Interceptor接口的 * 都会被存储到interceptors中。
源码如下:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
/**
* 让目标类在所有的 * 中生成代理对象,并返回代理对象
* @param target
* @return
*/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
/**
* 添加过滤器
* @param interceptor
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
拦截方法
默认情况下,MyBatis 允许使用插件来拦截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 接口下面的方法。如果系统中有配置自定义插件,默认情况下,系统会把上面四个类的默认子类都作为目标类来让所有的 * 进行拦截, 以保证所有的 * 都能对Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler子类进行拦截。
源码如下: 在org.apache.ibatis.session.Configuration类中
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 使用 * 进行拦截
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 使用 * 进行拦截
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 使用 * 进行拦截
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 使用 * 进行拦截
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
注解
Intercepts
Intercepts的作用是拦截Signature注解数组中指定的类的方法。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
/**
* Returns method signatures to intercept.
* Signature注解列表
* @return method signatures
*/
Signature[] value();
}
Signature
Signature注解作用是拦截指定类的方法。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
/**
* Returns the java type.
* 要拦截的类
* @return the java type
*/
Class<?> type();
/**
* Returns the method name.
* 要拦截的类的方法
* @return the method name
*/
String method();
/**
* Returns java types for method argument.
* 要拦截的类的方法的参数列表
* @return java types for method argument
*/
Class<?>[] args();
}
示例
步骤
1、实现org.apache.ibatis.plugin.Interceptor接口
2、添加Intercepts和Signature注解
3、根据需求实现Interceptor方法逻辑
入门使用
这里会写两个使用示例,一个是动态给属性赋值,一个是打印SQL。
表结构:
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`gender` varchar(20) DEFAULT NULL,
`userName` text NOT NULL,
`create_date` datetime DEFAULT NULL COMMENT '创建日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
实体类:
public class UserInfo {
private Long id;
private String gender;
private String userName;
private Date createDate;
// 省略get、set方法
}
动态给属性赋值
在创建表时,有些是每个表都有的参数,比如创建时间、修改时间等,这类参数如果在每个类进行保存或修改的时候都进行设值的话就有点重复操作了,所以可以通过mybatis插件进行处理。
1、Interceptor 实现类InsertInterceptor:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class InsertInterceptor implements Interceptor {
private Properties properties;
@Override
public Object intercept(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
MappedStatement mappedStatement= (MappedStatement) args[0];
Object parameter = args[1];
Executor executor = (Executor) invocation.getTarget();
final Class<?> parameterClass = parameter.getClass();
final String createDate = properties.getProperty("createDate");
//获取createDate 属性描述器
final PropertyDescriptor propertyDescriptor = new PropertyDescriptor(createDate , parameterClass);
//获取createDate 写方法
final Method writeMethod = propertyDescriptor.getWriteMethod();
//调用createDate 写方法
writeMethod.invoke(parameter , new Date());
return executor.update(mappedStatement, parameter);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target , this);
}
/**
* 设置变量
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
2、mybatis配置文件中注册InsertInterceptor
<plugins>
<plugin interceptor="plugin.PrintSqlPlugin"/>
<plugin interceptor="plugin.InsertInterceptor">
<property name="createDate" value="createDate"/>
</plugin>
</plugins>
3、测试
public class UserTest {
private final static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
} catch (IOException e) {
System.out.println(e.getMessage());
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
@Test
public void insert(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserInfo userInfo = new UserInfo();
userInfo.setUserName("test1");
userInfo.setGender("male");
UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
mapper.insertUser(userInfo);
sqlSession.commit();
sqlSession.close();
}
}
查看数据库,可以看到在没有给createDate属性收到赋值的情况下,通过 * 进行赋值,最后是保存到数据库中了。
打印SQL
Interceptor 实现类PrintSqlPlugin:
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class PrintSqlPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//被代理对象
Object target = invocation.getTarget();
//代理方法
Method method = invocation.getMethod();
//方法参数
Object[] args = invocation.getArgs();
MappedStatement mappedStatement= (MappedStatement) args[0];
Object parameter = args[1];
final BoundSql mappedStatementBoundSql = mappedStatement.getBoundSql(parameter);
System.err.println("BoundSql="+mappedStatementBoundSql.getSql());
final Configuration configuration = mappedStatement.getConfiguration();
final String showSql = showSql(configuration, mappedStatementBoundSql);
System.err.println("sql="+showSql);
//方法执行
final Object returnValue = invocation.proceed();
return returnValue;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 获取参数
* @param obj
* @return
*/
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
value = value.replaceAll("\\\\", "\\\\\\\\");
value = value.replaceAll("\\$", "\\\\\\$");
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(obj) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
/**
* 打印SQL
* @param configuration
* @param boundSql
* @return
*/
public static String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}
}
使用同样的方法进行测试,查看控制台打印结果:
BoundSql=insert into users (gender, userName ,create_date) values(? , ?, ?)
sql=insert into users (gender, userName ,create_date) values('male' , 'test2', '2022-1-14 18:40:08')
mybatis自定义插就到这里了,其实操作也简单,用好了也很强大。
来源:https://juejin.cn/post/7132293317231902757


猜你喜欢
- 前言网上SSO的框架很多,此篇文章使用的是自写的SSO来实现简单的登录授权功能,目的在于扩展性,权限这方面,自写扩展性会好点。提示:以下是本
- 1.首先什么是JNI呢?JNI——(Java Native Interface),他是java平台的特性,不是安卓系统提供的。他定义了一些J
- Linux Hadoop 2.7.3 安装搭建Hadoop实现了一个分布式文件系统(Hadoop Distributed File Syst
- 一、C#正则表达式符号模式字符描述\转义字符,将一个具有特殊功能的字符转义为一个普通字符,或反过来^匹配输入字符串的开始位置$匹配输入字符串
- 服务端注册功能实现通过web层完成客户端和服务端的数据交互(接受数据,发送数据),service层完成业务逻辑(注册,登录),dao层操作数
- float是单精度类型,精度是8位有效数字,取值范围是10的-38次方到10的38次方,float占用4个字节的存储空间double是双精度
- 问题描述:某天打开项目的activity的java文件界面突然变成下面这样了,但是用Notepad++打开代码什么的都正常,不知道什么原因造
- 前言早期在学习泛型的协变与逆变时,网上的文章讲解、例子算是能看懂,但关于逆变的具体应用场景这方面的知识,我并没有深刻的认识。本文将在具体的场
- 具体代码如下所示:package zhangphil.test; import android.graphics.Bitmap; impor
- 一、前言最近在加强 ITAEM 团队的一个 app 项目——学生教师学习交流平台人员组成:安卓 + 前端 + 后台后台 DAO 层借鉴了华工
- 如何加载权限表达式我们在上章内容中画了一张图,里面有三个分项,用户 角色 权限;那么接下来我们就要思考一个问题了,这三张表中的数据要从何而来
- 单例模式创建唯一的一个变量(对象),在类中将构造函数设为protected或者private(析构函数设为相对应的访问权限),故外部不能实例
- 首先给大家介绍一文件的上传 实体类import java.sql.Timestamp; /** * * @Decription 文件上传实体
- 1、@Valid与@Validated的区别1.1 基本区别@Valid:Hibernate validation校验机制@Validate
- 虽然闭包主要是函数式编程的玩意儿,而C#的最主要特征是面向对象,但是利用委托或lambda表达式,C#也可以写出具有函数式编程风味的代码。同
- Mybatis-Plus将字段设置为null项目场景:最近在做一个需求的时候需要把数据库中的某个字段设置为空问题描述:在代码中通过set方法
- 这篇文章主要介绍了SpringMVC的执行流程及组件详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- 具体实现过程请看下面代码:简单的调用了一下系统的拍照功能代码如下所示://拍照的方法 private void openTakePhoto(
- 前言相对来说呢,jpg格式的相对来说容易破解一点,当然也取决于你的干扰元素,元素越复杂,破解也就难度越高,有的加的多,人都识别不出来了,何况
- 在Java中,不像Python一样直接用个input()就行的。Java控制台输入比较麻烦,下面是比较易懂的教程。首先,新建一个调用Java