当前位置: 首页 > news >正文

java注解+AOP切面:实现sql片段动态插入

目录

1.前序

2.具体步骤

2.1 创建注解@InjectWhereSqlCondition

2.2 创建切面类SqlEnhanceAspect

2.3 创建上下文SqlEnhanceContext

2.4 创建拦截器SqlEnhanceInterceptor

3.5 创建mybatisPlus配置类MyBatisPlusConfig


1.前序

当时有需求需要对采购订单等7个功能模块做数据权限,说白了就是要往7个列表展示的接口中插入sql片段(需添加sql查询条件,需要设置表名、字段名以及字段值),但是一个个的去插入,这些操作难免有些重复了并且不方便扩展,于是想通过添加注解的方式实现sql片段的动态插入,这样只需要添加一个注解就可以实现了。

嗯,废话不多说,直接开启步骤上代码:

2.具体步骤

2.1 创建注解@InjectWhereSqlCondition

因需添加sql查询条件,需要设置表名、字段名以及字段值,具体注解定义的代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectWhereSqlCondition {/*** 表名*/String tableCode() default "";/*** 字段名*/String fieldName() default "";/***查询方式*/String type() default "=";/*** 注解是否生效*/boolean enabled() default false;}

2.2 创建切面类SqlEnhanceAspect

在切面中设置切入点为使用注解@injectWhereSqlCondition的方法,并捕获该注解中定义的各个值,并将注解的值放在上下文SqlEnhanceContext中。

简单版如下所示:

@Aspect
@Component
public class SqlEnhanceAspect {//该切面会拦截所有被 @injectWhereSqlCondition 注解标记的方法,在这些方法执行前后插入额外的逻辑处理。@Around("@annotation(injectWhereSqlCondition)")public Object interceptSapData(ProceedingJoinPoint joinPoint, InjectWhereSqlCondition injectWhereSqlCondition) throws Throwable {try {// 获取注解的各项值String tableCode = injectWhereSqlCondition.tableCode();String fieldName = injectWhereSqlCondition.fieldName();String type = injectWhereSqlCondition.type();String sqlCondition = "WHERE " + tableCode +"."+ fieldName + " " + type + " ";Map<String, Object> whereMap = new HashMap<>();List<String> sqlData = Arrays.asList("1000");whereMap.put("sqlCondition", sqlCondition);whereMap.put("sqlData", sqlData);// 将SAP数据存入线程本地变量SqlEnhanceContext.setWhereSqlData("sqlCondition", sqlCondition);SqlEnhanceContext.setWhereSqlData("sqlData", sqlData);SqlEnhanceContext.setWhereSqlData("enabled", StringUtils.isBlank(fieldName) ? null : true);// 继续执行原方法return joinPoint.proceed();} finally {// 清理线程本地变量SqlEnhanceContext.clear();}}
}

我的需求是,通过CurrentUserInfoService判断若当前账号为供应商则不需要添加数据权限,否则从DataAuthMyService中获取当前账号的数据权限值并一同保存在上下文中,具体详细的代码如下:

@Aspect
@Component
public class SqlEnhanceAspect {//可以获得当前账号的数据权限有哪些private final DataAuthMyService dataAuthMyService;//当前登录人身份判断以及当前登录人所在公司信息private final CurrentUserInfoService currentUserInfoService;public SqlEnhanceAspect(CurrentUserInfoService currentUserInfoService, DataAuthMyService dataAuthMyService) {this.currentUserInfoService = currentUserInfoService;this.dataAuthMyService = dataAuthMyService;}//该切面会拦截所有被 @injectWhereSqlCondition 注解标记的方法,在这些方法执行前后插入额外的逻辑处理。@Around("@annotation(injectWhereSqlCondition)")public Object interceptSapData(ProceedingJoinPoint joinPoint, InjectWhereSqlCondition injectWhereSqlCondition) throws Throwable {//若为供应商账号就不添加sql片段if (currentUserInfoService.isSupplierCustomer()) {SqlEnhanceContext.setWhereSqlData("enabled", false);return joinPoint.proceed();}try {// 获取注解的各项值String tableCode = injectWhereSqlCondition.tableCode();String fieldName = injectWhereSqlCondition.fieldName();String type = injectWhereSqlCondition.type();String sqlCondition = "WHERE " + tableCode+"." + fieldName + " " + type + " ";Map<String, Object> whereMap = new HashMap<>();DataAuthRelations dataAuthMy = this.dataAuthMyService.getAuthInfoByUserId(SecurityUserHolder.getCurrentAccountId());List<String> sqlData = CollectionUtils.isEmpty(dataAuthMy.getFactoryCode()) ? null : dataAuthMy.getFactoryCode();whereMap.put("sqlCondition", sqlCondition);whereMap.put("sqlData", sqlData);// 将SAP数据存入线程本地变量SqlEnhanceContext.setWhereSqlData("sqlCondition", sqlCondition);SqlEnhanceContext.setWhereSqlData("sqlData", sqlData);SqlEnhanceContext.setWhereSqlData("enabled", StringUtils.isBlank(fieldName) ? null : true);// 继续执行原方法return joinPoint.proceed();} finally {// 清理线程本地变量SqlEnhanceContext.clear();}}
}

2.3 创建上下文SqlEnhanceContext

上下文用于存储每一个注解在使用时,出发切面时获取的注解中定义的值以及一些自定义的固定值。具体代码如下:

public class SqlEnhanceContext {private static final ThreadLocal<Map<String, Object>> whereDataHolder = new ThreadLocal<>();public static void setWhereSqlData(String key, Object sapData) {Map<String, Object> dataMap = whereDataHolder.get();if (dataMap == null) {dataMap = new HashMap<>();whereDataHolder.set(dataMap);}dataMap.put(key, sapData);}public static Object getWhereSqlData(String key) {Map<String, Object> dataMap = whereDataHolder.get();return dataMap != null ? dataMap.get(key) : null;}public static <T> T getWhereSqlData(String key, Class<T> type) {Object data = getWhereSqlData(key);if (data != null && type.isInstance(data)) {return type.cast(data);}return null;}public static void clear() {whereDataHolder.remove();}
}

2.4 创建拦截器SqlEnhanceInterceptor

创建拦截器,拦截使用指定注解@InjectWhereSqlCondition下面的sql语句,并插入sql片段,具体代码如下:

@Component
@Slf4j
public class SqlEnhanceInterceptor implements InnerInterceptor {@Overridepublic void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {if(Objects.isNull(SqlEnhanceContext.getWhereSqlData("enabled"))){return;}// 获取原始SQLBoundSql boundSql = sh.getBoundSql();String sql = boundSql.getSql();log.info("原始SQL: " + sql);if (StringUtils.isNotBlank(sql) && (boolean) SqlEnhanceContext.getWhereSqlData("enabled")) {// 获取数据String insertWhereCondition = (String) SqlEnhanceContext.getWhereSqlData("sqlCondition");String inClause;if (insertWhereCondition.endsWith(" IN ")) {List<String> sqlData = (List<String>) SqlEnhanceContext.getWhereSqlData("sqlData");if (sqlData != null && !sqlData.isEmpty()) {// 构造IN条件SQL片段StringBuilder inClauseBuilder = new StringBuilder();inClauseBuilder.append("(");for (int i = 0; i < sqlData.size(); i++) {if (i > 0) {inClauseBuilder.append(",");}// 对数据进行安全处理,防止SQL注入String safeValue = sqlData.get(i).replace("'", "''");inClauseBuilder.append("'").append(safeValue).append("'");}inClauseBuilder.append(")");inClause = inClauseBuilder.toString();} else {// 如果没有数据,则构造一个空条件(返回0条记录)inClause = "('')";}} else {inClause = (String) SqlEnhanceContext.getWhereSqlData("sqlData");}String newSql;if (sql.contains(" WHERE ") || sql.contains(" where ")) {// 已存在WHERE,添加AND条件newSql = sql.replaceFirst("(?i)(WHERE)", "$1 " + insertWhereCondition + inClause + " AND ");} else {// 不存在WHERE,需要添加WHERE关键字,使用正则表达式查找FROM子句后的位置newSql = sql.replaceAll("(?i)(FROM\\s+\\w+)", "$1 " + insertWhereCondition + inClause);}log.info("修改后的SQL: " + newSql);// 修改SQLMetaObject metaObject = MetaObject.forObject(sh, SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new org.apache.ibatis.reflection.DefaultReflectorFactory());metaObject.setValue("delegate.boundSql.sql", newSql);// 处理参数映射,移除sapConditions参数避免绑定异常try {handleParameterMappings(metaObject, boundSql);} catch (Exception e) {// 记录日志但不中断执行e.printStackTrace();}}}private void handleParameterMappings(MetaObject metaObject, BoundSql boundSql) throws Exception {List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {List<ParameterMapping> newParameterMappings = new ArrayList<>();for (ParameterMapping mapping : parameterMappings) {if (!"sapConditions".equals(mapping.getProperty())) {newParameterMappings.add(mapping);}}metaObject.setValue("delegate.boundSql.parameterMappings", newParameterMappings);}}
}

3.5 创建mybatisPlus配置类MyBatisPlusConfig

往配置类中添加刚刚创建的拦截器,并添加到mybatisPlus的拦截器中,具体代码如下:

@Configuration
@MapperScan(basePackages = "com.je.business.**.mapper", sqlSessionFactoryRef = "mybatisSqlSession")
public class MyBatisPlusConfig {//配置刚刚创建的拦截器private final SqlEnhanceInterceptor sqlEnhanceInterceptor;public MyBatisPlusConfig(SqlEnhanceInterceptor sqlEnhanceInterceptor) {this.sqlEnhanceInterceptor = sqlEnhanceInterceptor;}@Bean("mybatisSqlSession")public SqlSessionFactory sqlSessionFactory(DataSource dataSource, GlobalConfig globalConfig) throws Exception {MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();/* 数据源 */sqlSessionFactory.setDataSource(dataSource);/* 枚举扫描 *///sqlSessionFactory.setTypeEnumsPackage("com.baomidou.mybatisplus.samples.mysql.enums");/* xml扫描 */sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/**/*.xml"));/* 扫描 typeHandler */
//        sqlSessionFactory.setTypeHandlersPackage("com.baomidou.mybatisplus.samples.mysql.type");MybatisConfiguration configuration = new MybatisConfiguration();configuration.setJdbcTypeForNull(JdbcType.NULL);/* 驼峰转下划线 */configuration.setMapUnderscoreToCamelCase(true);MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//将创建的拦截器添加到MybatisPlusInterceptor中,如果不添加则不生效mybatisPlusInterceptor.addInnerInterceptor(sqlEnhanceInterceptor);mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());sqlSessionFactory.setPlugins(mybatisPlusInterceptor);/* map 下划线转驼峰 */configuration.setObjectWrapperFactory(new MybatisMapWrapperFactory());sqlSessionFactory.setConfiguration(configuration);/* 自动填充插件 *///globalConfig.setMetaObjectHandler(new MysqlMetaObjectHandler());sqlSessionFactory.setGlobalConfig(globalConfig);return sqlSessionFactory.getObject();}@Beanpublic GlobalConfig globalConfig() {GlobalConfig conf = new GlobalConfig();conf.setDbConfig(new GlobalConfig.DbConfig().setColumnFormat("`%s`"));DefaultSqlInjector logicSqlInjector = new DefaultSqlInjector() {/*** 注入自定义全局方法*/@Overridepublic List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);// 不要逻辑删除字段, 不要乐观锁字段, 不要填充策略是 UPDATE 的字段methodList.add(new InsertBatchSomeColumn(t -> !t.isLogicDelete() && !t.isVersion() && t.getFieldFill() != FieldFill.UPDATE));// 不要填充策略是 INSERT 的字段, 不要字段名是 column4 的字段methodList.add(new AlwaysUpdateSomeColumnById(t -> t.getFieldFill() != FieldFill.INSERT && !"column4".equals(t.getProperty())));return methodList;}};conf.setSqlInjector(logicSqlInjector);conf.setMetaObjectHandler(new MyMetaObjectHandler());return conf;}
}

最后直接在对应方法的service或者mapper文件上面使用注解就可以了,注意为interface类型的类上使用,列如如下:

@InjectWhereSqlCondition(tableCode = "account", fieldName = "username", type = "in", enabled = true)
User selectByUsername();

有任何问题欢迎留言~~~

http://www.dtcms.com/a/478478.html

相关文章:

  • 网络安全超详细系统教程、渗透测试与学习路线(2025年最新版)
  • 靖江网站制作多少钱网站的衡量标准
  • 一、前置基础(MVC学习前提)_核心特性_【C# 泛型入门】为什么说 List<T>是程序员的 “万能收纳盒“?避坑指南在此
  • OpenCV(十):NumPy中的ROI
  • Qt插件机制实现动态组件加载详解
  • 重大更新!基于VMD+Transformer-BiLSTM-CrossAttention 故障分类模型
  • YOLO系列——基于Ultralytics YOLOv11模型在C++ OpenCV DNN模块进行模型加载与推理(附源码)
  • 有哪些做统计销量的网站设计了网站
  • 做微信公众号的网站有哪些外贸网站建设团队
  • 广东省省考备考(第一百二十二天10.13)——资料分析、言语(强化训练)
  • MySQL中like模糊查询如何优化
  • 400G QSFP112 FR4光模块:高速数据中心互联的核心力量
  • 旅行商问题(TSP)(1)(Route.py)(TSP 问题中的点与路径核心类)
  • 学习笔记--文件上传
  • Leetcode 26
  • 淘宝领券网站怎么做上海工程咨询行业协会
  • 泰国网站域名wordpress建网站的优点
  • 解锁 JavaScript 字符串补全魔法:padStart()与 padEnd()
  • Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
  • TDengine 数学函数 DEGRESS 用户手册
  • 源码:Oracle AWR报告之Top 10 Foreground Events by Total Wait Time
  • 告别繁琐坐标,让公式“说人话”:Excel结构化引用完全指南
  • 【AI论文】CoDA:面向协作数据可视化的智能体系统
  • 从AAAI2025中挑选出对目标检测有帮助的文献——第六期
  • 【深度学习】反向传播
  • 网站开发交接新闻源发稿平台
  • 滴答时钟延时
  • 【C++篇】:ServiceBus RPC 分布式服务总线框架项目
  • 后训练——Post-training技术介绍
  • 获取KeyStore的sha256