MyBatis提供一种插件plugin的功能,虽是插件,但拥有拦截器的功能。
通过自定义拦截器可以完成字段自动填充,像创建、更新时间,删除标志,创建人等...
还可以完成自定义数据分页、数据权限的拼接替换等...
拦截器的拦截范围
Executor(update,query,commit,rollback,close,isClosed)
拦截执行器的方法
ParamterHandler(getParamterObject setParamters)
拦截参数的处理
ResultSetHandler(handleResultSets,handleOutputparamters)
拦截结果集处理
StatementHandler(prepare,parameterize,batch,update,query)
拦截sql语法构建的处理
自定义拦截器
Executor
通用字段的自动填充
package com.datasource.config.mybatis;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.*;
/**
* 针对insert update操作对 创建人 创建时间 删除标志 更新人 更新时间 拦截填充
*
* @author Neoooo
* @since 2023-08-28
*/
@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class MyBatisOperateInterceptor implements Interceptor {
private static final String CREATE_BY = "createBy";
private static final String UPDATE_BY = "updateBy";
private static final String CREATE_TIME = "createTime";
private static final String UPDATE_TIME = "updateTime";
private static final String IS_DELETE = "isDelete";
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
// 操作类型 只对 insert update 进行拦截
SqlCommandType sqlCommandType = statement.getSqlCommandType();
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
Object arg = invocation.getArgs()[1];
if (arg instanceof Map<?, ?>) {
for (Object obj : ((Map<?, ?>) arg).values()) {
insertOrUpdateOperate(obj, sqlCommandType);
}
} else {
insertOrUpdateOperate(arg, sqlCommandType);
}
}
return invocation.proceed();
}
/**
* 添加或者
*
* @param object 数据对象
* @param sqlCommandType 操作行为 insert or update
*/
private void insertOrUpdateOperate(Object object, SqlCommandType sqlCommandType) throws IllegalAccessException {
if (object == null) {
log.info("object set properties ,object must is not null");
return;
}
List<Field> declaredFields = new ArrayList<>(Arrays.asList(object.getClass().getDeclaredFields()));
if (object.getClass().getSuperclass() != null && object.getClass().getSuperclass() != Object.class) {
// 当前类具有超类父类(所有类都是继承于Object 所以要排除掉)
Field[] superClassFields = object.getClass().getSuperclass().getDeclaredFields();
declaredFields.addAll(Arrays.asList(superClassFields));
}
// 添加
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
System.out.println(declaredField.getName());
switch (declaredField.getName()) {
case CREATE_BY:
// 创建人
declaredField.set(object, "Neoooo");
break;
case CREATE_TIME:
// 创建时间
declaredField.set(object, LocalDateTime.now());
break;
case IS_DELETE:
// 删除标志
declaredField.set(object, false);
break;
default:
break;
}
} else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
switch (declaredField.getName()) {
case UPDATE_BY:
// 更新人 TODO 可获取当前登录用户
declaredField.set(object, "admin");
break;
case UPDATE_TIME:
// 更新时间
declaredField.set(object, LocalDateTime.now());
break;
default:
break;
}
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
/**
* 针对myabtis拦截器的使用,注入至ioc容器即可
*
* @author Neoooo
* @since 2023-08-28
*/
@Configuration
public class MyBatisConfiguration {
@Bean
public MyBatisOperateInterceptor myBatisOperateInterceptor() {
return new MyBatisOperateInterceptor();
}
}
StatementHandler
分页、数据权限的构建
package com.datasource.config.mybatis;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;
/**
* 针对sql做一些 封装
* 分页
* 权限拼接
*
* @author Neoooo
* @since 2023-08-28
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MyBatisQueryInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
// 优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
// 先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// id为执行的mapper方法的全路径名,如com.uv.dao.UserMapper.insertUser
String id = mappedStatement.getId();
// sql语句类型 select、delete、insert、update
if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
// TODO 在此阶段可以做一些分页,数据权限的sql拼接,替换等处理
sql = sql + " limit 1";
Field sqlField = boundSql.getClass().getDeclaredField("sql");
sqlField.setAccessible(true);
sqlField.set(boundSql, sql);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
/**
* 针对myabtis拦截器的使用,注入至ioc容器即可
*
* @author Neoooo
* @since 2023-08-28
*/
@Configuration
public class MyBatisConfiguration {
@Bean
public MyBatisQueryInterceptor myBatisQueryInterceptor() {
return new MyBatisQueryInterceptor ();
}
}