你要有种,你就扬着脸一直往前冲。可是你得跟妒忌,毁谤,庸俗斗争,跟所有的人斗争。——巴尔扎克《高老头》
今天学了点Mybatis
拦截器,参考了Mybatis-Plus
部分代码
首先是使用@Intercepts
注解,它的源码注释告诉我们可以这样使用:
mybatis-plus
中使用的就是这种方式,参考:
这里可以传入@Signature
,指定它的type
为:
Executor
,执行器,我们可以看到它包含了如下方法,说明它是一个比较全能的范围,可以做很多事情参数如处理、返回处理、重写sql
等
我们依葫芦画瓢写两个:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package com.ruben.simplescaffold.plugin;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Opt; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.enums.SqlKeyword; import com.ruben.simplescaffold.entity.UserDetail; 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.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.stereotype.Component;
import java.lang.reflect.Method; import java.util.ArrayList;
@Component @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}), } ) public class MybatisExecutorQueryPlugin implements Interceptor {
@Override public Object intercept(Invocation invocation) throws Throwable { Executor executor = (Executor) invocation.getTarget(); Object[] args = invocation.getArgs(); Method method = invocation.getMethod();
MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; BoundSql boundSql; if (args.length == 4) { boundSql = ms.getBoundSql(parameter); } else { boundSql = (BoundSql) args[5]; } MetaObject metaObject = SystemMetaObject.forObject(boundSql); String sql = (String) metaObject.getValue("sql"); if (!StrUtil.containsIgnoreCase(sql, SqlKeyword.ORDER_BY.getSqlSegment())) { metaObject.setValue("sql", sql + " order by id asc"); } return invocation.proceed(); }
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); } }
|
写完了查询,再写一个更新的
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| package com.ruben.simplescaffold.plugin;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.lang.func.Func1; import cn.hutool.core.lang.func.LambdaUtil; import cn.hutool.core.util.ReflectUtil; import com.baomidou.mybatisplus.core.conditions.AbstractWrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.update.Update; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.mapper.Mapper; import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.ruben.simplescaffold.entity.UserDetail; import com.ruben.simplescaffold.pojo.common.BaseEntity; import lombok.extern.slf4j.Slf4j; import org.apache.http.Consts; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.*; import org.springframework.stereotype.Component;
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Objects;
@Component @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) public class MybatisExecutorUpdatePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; if (SqlCommandType.INSERT.equals(ms.getSqlCommandType())) { fillTime(args[1], UserDetail::getGmtCreate); fillTime(args[1], UserDetail::getGmtModified); } if (SqlCommandType.UPDATE.equals(ms.getSqlCommandType()) && args[1] instanceof MapperMethod.ParamMap) { MapperMethod.ParamMap<?> paramMap = (MapperMethod.ParamMap<?>) args[1]; Object bean = paramMap.get(Constants.ENTITY); if (paramMap.containsKey(Constants.WRAPPER)) { Object wrapper = paramMap.get(Constants.WRAPPER); if (wrapper instanceof AbstractWrapper && wrapper instanceof Update) { String fieldName = LambdaUtil.getFieldName(UserDetail::getGmtModified); String mapperClassName = ms.getId().substring(0, ms.getId().lastIndexOf('.')); Class<?> entityClass = ReflectionKit.getSuperClassGenericType(Class.forName(mapperClassName), Mapper.class, 0); List<TableFieldInfo> fieldList = TableInfoHelper.getTableInfo(entityClass).getFieldList(); String columnName = fieldList.stream().filter(fieldInfo -> fieldInfo.getProperty().equals(fieldName)).findAny().map(TableFieldInfo::getColumn).orElseThrow(() -> new MybatisPlusException("未找到字段" + fieldName)); final Map<String, Object> paramNameValuePairs = ((AbstractWrapper<?, ?, ?>) wrapper).getParamNameValuePairs(); String placeholder = "#" + fieldName + "#"; paramNameValuePairs.put(placeholder, LocalDateTime.now()); ((Update<?, ?>) wrapper).setSql(String.format("%s = #{%s.%s}", columnName, "ew.paramNameValuePairs", placeholder)); } } else if (Objects.nonNull(bean)) { fillTime(bean, UserDetail::getGmtModified); } } return invocation.proceed(); }
private <T> void fillTime(Object bean, Func1<T, ?> func) { String fieldName = LambdaUtil.getFieldName(func); if (ReflectUtil.hasField(bean.getClass(), fieldName)) { BeanUtil.setFieldValue(bean, fieldName, LocalDateTime.now()); } }
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); } }
|
然后是指定@Signature
的type
为StatementHandler
,也是有各种方法
我们演示一个隔离级别设置、日志打印、sql
修改(此处只是取出来再设置进去,可以按需自己处理)
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
| package com.ruben.simplescaffold.plugin;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; 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 org.springframework.stereotype.Component;
import java.sql.Connection;
@Slf4j @Component @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class MybatisStatementHandlerPreparePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); Object[] args = invocation.getArgs(); Connection connection = (Connection) args[0]; connection.prepareStatement("set transaction isolation level read committed"); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); String sql = (String) metaObject.getValue("delegate.boundSql.sql"); log.info("sql: {}", sql); metaObject.setValue("delegate.boundSql.sql", sql); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } }
|
然后是@Signature
的type
为ParameterHandler
,一看名字就是用来处理参数的
简单演示下
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
| package com.ruben.simplescaffold.plugin;
import cn.hutool.core.lang.func.LambdaUtil; import cn.hutool.core.util.ReflectUtil; import com.ruben.simplescaffold.entity.UserDetail; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.plugin.*; import org.springframework.stereotype.Component;
import java.sql.PreparedStatement; import java.sql.ResultSetMetaData;
@Component @Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})}) public class MybatisParameterHandlerSetParametersPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget(); Object[] args = invocation.getArgs(); PreparedStatement preparedStatement = (PreparedStatement) args[0]; Object parameterObject = parameterHandler.getParameterObject(); if (parameterObject instanceof UserDetail) { ReflectUtil.setFieldValue(parameterObject, LambdaUtil.getFieldName(UserDetail::getTenantId), 1L); } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } }
|
最后就是@Signature
为ResultSetHandler
,用于处理结果
演示这里查询过滤掉密码
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
| package com.ruben.simplescaffold.plugin;
import cn.hutool.core.collection.CollUtil; import com.ruben.simplescaffold.entity.UserDetail; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.plugin.*; import org.springframework.stereotype.Component;
import java.sql.Statement; import java.util.ArrayList; import java.util.List;
@Component @Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})) public class MybatisResultSetHandlerHandleResultSetsPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { List<?> resultList = (List<?>) invocation.proceed(); Object bean = CollUtil.get(resultList, 0); if (bean instanceof UserDetail) { @SuppressWarnings("unchecked") ArrayList<UserDetail> userList = (ArrayList<UserDetail>) resultList; userList.forEach(user -> user.setPassword(null)); return userList; } return resultList; }
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); } }
|
对了,顺便一提Plugin.wrap
使用动态代理的方式,对我们方法进行了代理,其中还判断了拦截器触发的时机等操作