实战业务优化方案总结—主目录https://blog.csdn.net/grd_java/article/details/124346685 |
---|
问题如标题所示,mybatis自动填充不生效,使用分页插件发现这个插件没有向下传递拦截链。
解决方案
- 选择使用Mybatis-plus的分页插件,满足向下传递拦截链的要求。
- 仿造MyBatis-plus的自动填充使用,自定义自动填充插件。解决Mybatis-plus自动填充时而有效时而失效的情况。
文章目录自动填充效果展示
- 1. 分页插件
- 2. 自定义mybatis
- 3. 实现自动填充
1. 使用Mybatis-plus的分页插件,满足向下传递拦截链的要求 |
---|
- 很简单,MyBatis配置文件中,配置一下
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* MyBatis相关配置
*/
@Configuration
@EnableTransactionManagement
public class MyBatisConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
2. 自定义mybatis
2. 这里推荐,自己决定使用哪些插件,包括配置DataSource,事务管理器等等 |
---|
- 很简单,创建一个配置类配置SqlSession就可以了(这个对象保存了MyBatis很多东西,其中拦截器就在这里面)
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "cn.xx.mapper",sqlSessionTemplateRef = "sqlOneSqlSessionTemplate")
public class DataSourceConfigOne {
@Autowired
private MyBatisConfig myBatisConfig;
@Bean(name = "sqlOneDataSource")
@ConfigurationProperties(prefix = "spring.datasource.sqlone")
@Primary
public DataSource sqlOne1DataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "sqlOneSqlSessionFactory")
@Primary
public SqlSessionFactory sqlOneSql1SessionFactory(@Qualifier("sqlOneDataSource") DataSource dataSource) throws Exception{
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/sqlone/*.xml"));
bean.setPlugins(myBatisConfig.paginationInterceptor(),myBatisConfig.myPluginAutoFill());//将自定义自动填充拦截器添加到插件中
return bean.getObject();
}
@Bean(name = "sqlOneTransactionManager")
@Primary
public DataSourceTransactionManager sqlOne1TransactionManager(@Qualifier("sqlOneDataSource") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "sqlOneSqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlOne1SqlSessionTemplate(@Qualifier("sqlOneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception{
return new SqlSessionTemplate(sqlSessionFactory);
}
}
3. 实现自动填充
3. 自定义自动填充插件(仿照mybatis-plus的自动填充),需要对MyBatis源码有深入了解。 |
---|
- 注解用来标识字段是否需要自动填充,Fill标识字段在哪些情况下被填充
import java.lang.annotation.*;
/**
* 2022-04-14===>>>yinzhipeng
* 添加此注解的字段,可以被自动填充拦截器扫描到。
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface MyTableField {
//数据库字段值, 不需要配置该值的情况:
String value() default "";
//字段自动填充策略
MyFieldFill fill() default MyFieldFill.DEFAULT;
}
/**
* 2022-04-14===>>>yinzhipeng
* 字段填充策略枚举类,需要配合MyPluginAutoFill类进行指定
*/
public enum MyFieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}
- 在需要自动填充的字段上,添加注解
- 编写自定义插件(我们选择拦截ParameterHandler的setParameters方法),拦截后,会对INSERT和UPDATE *** 作的parameterObject 进行设置值,就达到自动填充的效果了。
import com.baomidou.mybatisplus.core.MybatisDefaultParameterHandler;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
/**
* 2022-04-14===>>>yinzhipeng
* 根据目前实验,MyBatis plus对于不继承BaseMapper的接口有可能不生效,无法保证百分百自动填充
* 自定义MyBatis拦截器,实现自动填充,方便日后扩展功能
*/
//@Component //因为在DataSourceConfigOne,自定义了SqlSessionFactory,并指定拦截链,所以这里不需要用注解
@Intercepts({//可以定义多个@Singnature,对多个地方拦截,都用这个拦截器
@Signature(type = ParameterHandler.class,//指定拦截ParameterHandler接口
method = "setParameters",//指定要拦截这个接口(指定拦截ParameterHandler接口)的具体方法名,必须写对
args={PreparedStatement.class}//拦截方法的参数列表,必须和你要拦截的方法一一对应
)
})
//指定在某个类加载后再加载,防止其它的拦截器,比如分页拦截器不invocation.proceed()向下传递拦截链
//因为越往后的优先级越高,所以要在其它拦截器后面加载,这样它的优先级会更高
//@AutoConfigureAfter(PaginationInterceptor.class) //因为在DataSourceConfigOne,自定义了SqlSessionFactory,并指定拦截链,所以这里不需要
public class MyPluginAutoFill implements Interceptor {
@Autowired
private LoginUserUtil loginUserUtil;//用来获取当前登录的用户信息
//insert *** 作时,需要填充的字段
//只填充有@MyTableField(fill = MyFieldFill.INSERT),和@MyTableField(fill = MyFieldFill.INSERT_UPDATE)注解的字段
//字段的自动填充,需要满足两个条件,在下面指定字段,并且在需要填充字段的地方,加上上面的注解,只满足其中一个条件,不会进行填充
private void insertBefore(Object o) throws NoSuchFieldException, IllegalAccessException {
// 属性名称,不是字段名称
this.doBefore("createTime",MyFieldFill.INSERT,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),o);
this.doBefore("updateTime",MyFieldFill.INSERT,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),o);
this.doBefore("createUser",MyFieldFill.INSERT,loginUserUtil.getCurrentAdmin().getUsername(),o);
this.doBefore("updateUser",MyFieldFill.INSERT,loginUserUtil.getCurrentAdmin().getUsername(),o);
}
//update *** 作时,需要填充的字段
//只填充有@MyTableField(fill = MyFieldFill.UPDATE),和@MyTableField(fill = MyFieldFill.INSERT_UPDATE)注解的字段
private void updateBefore(Object o) throws NoSuchFieldException, IllegalAccessException {
this.doBefore("updateTime",MyFieldFill.UPDATE,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),o);
this.doBefore("updateUser",MyFieldFill.UPDATE,loginUserUtil.getCurrentAdmin().getUsername(),o);
}
//每次执行 *** 作,都会进入此拦截
@Override
public Object intercept(Invocation invocation) throws Throwable {
//递归获取MybatisDefaultParameterHandler对象,因为可能是多层代理
MybatisDefaultParameterHandler mybatisDefaultParameterHandler = realTarget(invocation.getTarget());
//反射mappedStatement对象出来
Field mappedStatementField = mybatisDefaultParameterHandler.getClass().getDeclaredField("mappedStatement");
mappedStatementField.setAccessible(true);//此属性是private的,需要强制
//获取MappedStatement
MappedStatement mappedStatement =(MappedStatement)mappedStatementField.get(mybatisDefaultParameterHandler);
// 如果不是insert或update,就不需要自动填充,直接传递拦截链
if(mappedStatement.getSqlCommandType()!=SqlCommandType.INSERT && mappedStatement.getSqlCommandType()!=SqlCommandType.UPDATE)
return invocation.proceed();//原方法执行,并返回
// 获取parameterObject对象,用于设置参数
Object parameterObject = realTarget(mybatisDefaultParameterHandler.getParameterObject());
// 如果是insert或update,就执行对应的自动填充
if(mappedStatement.getSqlCommandType()==SqlCommandType.INSERT){
insertBefore(parameterObject);
}else if (mappedStatement.getSqlCommandType()==SqlCommandType.UPDATE){
updateBefore(parameterObject);
}
return invocation.proceed();//原方法执行,并返回,传递拦截链,不传递的话,后面的拦截器将无法获取
}
/**
* 获得真正的处理对象,可能多层代理.
*/
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h.target"));
}
return (T) target;
}
/**
* 真正做填充的方法。填充时,只填充有特定注解的字段
* @param fieldName 要填充的字段名
* @param operationType 需要的注解
* @param fieldVal 要填充的值
* @param metaObject 要填充的对象
*/
private void doBefore(String fieldName,MyFieldFill operationType,Object fieldVal,Object metaObject) throws NoSuchFieldException, IllegalAccessException {
//获取对象的指定Field属性
Field declaredField = metaObject.getClass().getDeclaredField(fieldName);
//如果填充的值存在,属性存在,并且有指定注解
if (Objects.nonNull(fieldVal) && declaredField!=null && declaredField.isAnnotationPresent(MyTableField.class)) {
//获取注解
MyTableField annotation = declaredField.getAnnotation(MyTableField.class);
//如果注解是规定的注解就进行填充
if (annotation.fill() == MyFieldFill.INSERT_UPDATE||
annotation.fill()==operationType){
declaredField.setAccessible(true);
declaredField.set(metaObject,fieldVal);
}
}
}
// /**
// * 将当前拦截器,生成代理对象放在拦截链中
// * @param target 目标对象,被代理对象,被拦截对象
// * @return 代理对象
// */
// @Override
// public Object plugin(Object target) {
// System.out.println("为"+target+"生成代理");
// return Plugin.wrap(target,this);
// }
// //获取配置文件属性,xml中配置的属性,从Properties获取,插件初始化时调用。只调用一次,插件配置的属性从这里设置
// @Override
// public void setProperties(Properties properties) {
// System.out.println("插件配置的初始化参数:"+properties);
// }
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)