tsx81429 发表于 2024-8-25 01:55:16

Mybatis 适配多数据源 关键字查扣问题

Mybatis 适配多数据源 关键字查扣问题

前言

最近项目使用Mybatis拦截器对国产化数据源数据进行关键字处置惩罚,以下记载如何将拦截器集成到项目中以及在使用过程中踩过的一些小坑
1.Myabtis拦截器是什么?

MyBatis答应使用者在映射语句实行过程中的某一些指定的节点进行拦截调用,通过织入拦截器,在不同节点修改一些实行过程中的关键属性,从而影响SQL的生成、实行和返回结果,如:来影响Mapper.xml到SQL语句的生成、实行SQL前对预编译的SQL实行参数的修改、SQL实行后返回结果到Mapper接口方法返参POJO对象的类型转换和封装等。
2,Mybatis核心对象

从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
Configuration:初始化基础设置,比如MyBatis的别名等,一些重要的类型对象,如插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的设置信息都维持在Configuration对象之中。
SqlSessionFactory:SqlSession工厂。
SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要的数据库增删改查功能。
Executor:MyBatis的内部实行器,它负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射,另外,它还处置惩罚二级缓存的操作。
StatementHandler:MyBatis直接在数据库实行SQL脚本的对象。另外它也实现了MyBatis的一级缓存。
ParameterHandler:负责将用户通报的参数转换成JDBC Statement所必要的参数。是MyBatis实现SQL入参设置的对象。
ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合。是MyBatis把ResultSet集合映射成POJO的接口对象。
TypeHandler:负责Java数据类型和JDBC数据类型之间的映射和转换。
MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装。
SqlSource :负责根据用户通报的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回。
BoundSql:表示动态生成的SQL语句以及相应的参数信息。
3,如何使用

3.1 起首先容一下mybatis拦截器拦截的类型

https://i-blog.csdnimg.cn/direct/90e8ea0bfe414287b4d25ee20ccc4901.png#pic_center
自定义拦截器必须使用@Intercepts注解,并实现Interceptor接口
@Intercepts注解:代表该类是一个拦截器
@Signature注解:表示要拦截哪一个方法
同时@Signature注解有三个参数
type:MyBatis拦截器默认可以拦截的类型只有四种,即四种接口类型Executor、StatementHandler、ParameterHandler和ResultSetHandler,type可以选取四种类型中的一个
method:对应接口中的方法,比图Executor的query方法
args:对应某个接口方法中的参数
3.2 代码示例

代码中使用的mybatis-plus的版本
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.3</version>
            </dependency>
拦截器类
@Component
@Intercepts({@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
      @Signature(
                type = Executor.class,
                method = "update",
                args = {MappedStatement.class, Object.class}
      )})
public class KeywordInterceptor implements Interceptor {


    public DataSourceProperties getDataSourceProperties() {
      return SpringContextUtil.getBean(DataSourceProperties.class);
    }

    private static final Logger logger = LoggerFactory.getLogger(KeywordInterceptor.class);
    public static final String DATABASE_TYPE_DM = "dm";

    public static final String DATABASE_TYPE_KINGBASE = "kingbase";

    public static final String DATABASE_TYPE_GBASE = "gbase";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
      logger.info("Intercepted target class: {}", invocation.getTarget().getClass().getName());
      if (invocation.getTarget() instanceof Executor) {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs();
            Object parameter = invocation.getArgs();
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String originalSql = boundSql.getSql();
            System.out.println("Original SQL: " + originalSql);
            if (containsKeyword(originalSql, "关键字") || containsKeyword(originalSql, "关键字")) {
                String databaseId = mappedStatement.getDatabaseId();
                String currentDataSourceType;
                if (StringUtils.isNotEmpty(databaseId)) {
                  currentDataSourceType = getCurrentDataSourceType(databaseId);
                } else {
                  DataSourceProperties dataSourceProperties = getDataSourceProperties();
                  String driverClassName = dataSourceProperties.getDriverClassName();
                  currentDataSourceType = getCurrentDataSourceType(driverClassName);
                }
                SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
                String modifiedSql = modifySqlForDataSource(originalSql, currentDataSourceType, sqlCommandType);
                logger.info("Modified SQL: {}", modifiedSql);
                // 修改 SQL 语句(注意,这里是简化示例,实际情况可能需要更多处理)
                BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), modifiedSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
                // 复制其他字段
                copyBoundSqlFields(boundSql, newBoundSql);
                // 创建新的 MappedStatement 对象
                MappedStatement newMappedStatement = newMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
                // 替换原始的 MappedStatement 对象
                invocation.getArgs() = newMappedStatement;

                return invocation.proceed();
            } else {
                return invocation.proceed();
            }
            
      } else {
            return invocation.proceed();
      }
    }

    private void copyBoundSqlFields(BoundSql source, BoundSql target) throws IllegalAccessException {
      Field[] fields = BoundSql.class.getDeclaredFields();
      for (Field field : fields) {
            if (!field.getName().equals("sql")) {
                field.setAccessible(true);
                field.set(target, field.get(source));
            }
      }
    }


    private boolean containsKeyword(String sql, String keyword) {
      // 使用正则表达式检查 SQL 中是否包含关键字
      Matcher matcher = Pattern.compile("\\b" + keyword + "\\b", Pattern.CASE_INSENSITIVE).matcher(sql);
      return matcher.find();
    }


    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
      MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
      builder.resource(ms.getResource());
      builder.fetchSize(ms.getFetchSize());
      builder.statementType(ms.getStatementType());
      builder.keyGenerator(ms.getKeyGenerator());
      if (null != ms.getKeyProperties()) {
            builder.keyProperty(String.join(",", ms.getKeyProperties()));
      }
      builder.timeout(ms.getTimeout());
      builder.parameterMap(ms.getParameterMap());
      builder.resultMaps(ms.getResultMaps());
      builder.resultSetType(ms.getResultSetType());
      builder.cache(ms.getCache());
      builder.flushCacheRequired(ms.isFlushCacheRequired());
      builder.useCache(ms.isUseCache());
      return builder.build();
    }

    private static class BoundSqlSqlSource implements SqlSource {
      private BoundSql boundSql;

      public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
      }

      @Override
      public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
      }
    }

    private String getCurrentDataSourceType(String driverClassName) {
      if (StringUtils.contains(driverClassName, DATABASE_TYPE_KINGBASE)) {
            return DbType.KINGBASE_ES.getDb();
      } else if (StringUtils.contains(driverClassName, DATABASE_TYPE_DM)) {
            return DbType.DM.getDb();
      } else if (StringUtils.contains(driverClassName, DATABASE_TYPE_GBASE)) {
            return DbType.GBASE.getDb();
      } else {
            return DbType.MYSQL.getDb();
      }
    }

    private String modifySqlForDataSource(String sql, String dataSourceType, SqlCommandType sqlCommandType){
      if ("mysql".equalsIgnoreCase(dataSourceType)) {
            return sql.replaceAll("\\b关键字\\b", "`关键字`");
      } else {
            if ("kingbasees".equalsIgnoreCase(dataSourceType)) {
                return sql.replaceAll("\\b关键字\\b", "\"关键字\"");
            } else {
                if ("dm".equals(dataSourceType)) {
                  return sql.replaceAll("\\b关键字\\b", "\"关键字大写\"");
                } else {
                  return sql.replaceAll("\\b关键字\\b", "\"关键字\"");
                }
            }
      }
    }

    @Override
    public Object plugin(Object target) {
      return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
      // 可以通过配置文件传递参数
    }
}
DataProperties类
@Component
@ConfigurationProperties(
      prefix = "spring.datasource"
)
public class DataSourceProperties {
    private String driverClassName;
    private String url;
    private String username;
    private String password;

    // Getters and Setters
    public String getDriverClassName() {
      return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
      this.driverClassName = driverClassName;
    }
}
Mybatis自动设置类
@Configuration
@EnableAutoConfiguration
@MapperScan(value = "扫的类路径", annotationClass = Mapper.class, sqlSessionFactoryRef = "sqlSessionFactory")
@MapperScan(value = "扫的类路径", annotationClass = Repository.class, sqlSessionFactoryRef = "sqlSessionFactory")
public class DGMybatisAutoConfiguration {

    @Autowired
    private DataSourceProperties dataSourceProperties;

    @Resource
    private MultiTenantHandler multiTenantHandler;

    public static final String DATABASE_TYPE_MYSQL = "mysql";

    public static final String DATABASE_TYPE_DM = "dm";

    public static final String DATABASE_TYPE_KINGBASE = "kingbase";

    public static final String DATABASE_TYPE_GBASE = "gbase";

    @Bean(destroyMethod = "")
    public DruidDataSource dataSource() {

      DruidDataSource druidDataSource = new DruidDataSource();

      druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
      druidDataSource.setUrl(dataSourceProperties.getUrl());
      druidDataSource.setUsername(dataSourceProperties.getUsername());
      druidDataSource.setPassword(dataSourceProperties.getPassword());

      druidDataSource.setPoolPreparedStatements(true);
      druidDataSource.setTestWhileIdle(true);
      druidDataSource.setTestOnBorrow(true);
      druidDataSource.setTestOnReturn(true);
      druidDataSource.setKeepAlive(true);
      druidDataSource.setMinIdle(5);
      druidDataSource.setMaxActive(50);
      druidDataSource.setMaxWait(60000);
      druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
      druidDataSource.setInitialSize(5);
      druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
      druidDataSource.setTimeBetweenConnectErrorMillis(60000);
      druidDataSource.setMinEvictableIdleTimeMillis(300000);
      druidDataSource.setValidationQueryTimeout(3);
      //auto commit
      druidDataSource.setDefaultAutoCommit(true);
      return druidDataSource;
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
      MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
      mybatisPlusInterceptor.addInnerInterceptor(new TenantLineInnerInterceptor(multiTenantHandler));
      String driverClassName = dataSourceProperties.getDriverClassName();
      // 分页插件
      if (StringUtils.contains(driverClassName, DATABASE_TYPE_GBASE)) {
            mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.GBASE));
      } else if (StringUtils.contains(driverClassName, DATABASE_TYPE_DM)) {
            mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.DM));
      } else if (StringUtils.contains(driverClassName, DATABASE_TYPE_KINGBASE)) {
            mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.KINGBASE_ES));
      } else {
            mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
      }
      return mybatisPlusInterceptor;
    }


    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
      MybatisConfiguration configuration = new MybatisConfiguration();
      configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class);
      configuration.setMapUnderscoreToCamelCase(true);
      configuration.setCacheEnabled(false);
      configuration.setCallSettersOnNulls(true);
      configuration.setJdbcTypeForNull(JdbcType.NULL);
      configuration.addInterceptor(mybatisPlusInterceptor());
      MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
      sqlSessionFactoryBean.setConfiguration(configuration);
      sqlSessionFactoryBean.setDataSource(dataSource());

      GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
      dbConfig.setIdType(IdType.AUTO);
      GlobalConfig globalConfig = new GlobalConfig();
      globalConfig.setDbConfig(dbConfig);
      sqlSessionFactoryBean.setPlugins(new KeywordInterceptor());
      return sqlSessionFactoryBean.getObject();
    }
}
4. 总结

固然也可以使用Mybatis拦截器来做其他事变,比如数据加密脱敏,sql监控报警等功能,这也会额外产生一些性能开销,合理利用拦截器将会大大缩减开发本钱。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Mybatis 适配多数据源 关键字查扣问题