• Mybatis PageHelper分页语句执行前做sql拦截并变更


    使用的若依前后端分离框架,

    master数据库用的mysql,slave数据库使用的sqlserver,

    sqlserver数据库实例下创建了很多数据库,业务场景下每个数据库称为一个账套,每个账套里面的表结构都是一样的

    业务需求,要求能在前端传入账套编号(即:数据库名称),后台能在对应的数据库下进行增删该查操作,简而言之就是在进行sql操作之前先USE一下数据库,在进行sql操作.如下:

    use demo;select * from xxx

    照道理我们直接在mapper.xml里面直接写上Use就行了

     然而在使用了PageHelper分页插件之后就行不通了,PageHelper分页在计算count数量是,将整个sql语句作为一个整体包含在了select count(0) from xxx里面,这个xxx里面包含了use,就导致整个select count(0)语句执行失败,如下:

    解决办法:

    mapper.xml 作为分页的select语句里面不能像上图一样直接写use了,

    需要在PageHelperInterceptor分页拦截器将分页语句组装完毕后,再用自定义拦截器拦截在最外层拼接好use,这样最终执行的sql才是正确的sql,如下:

    那么怎么添加mybatis自定义拦截器,并且自定义拦截器执行顺序是怎样的,我们11分析下 

    如下是一个自定义拦截器

    1. package com.hsf.framework.interceptor;
    2. import com.hsf.common.utils.StringUtils;
    3. import com.hsf.common.utils.reflect.ReflectUtils;
    4. import org.apache.ibatis.cache.CacheKey;
    5. import org.apache.ibatis.executor.Executor;
    6. import org.apache.ibatis.mapping.BoundSql;
    7. import org.apache.ibatis.mapping.MappedStatement;
    8. import org.apache.ibatis.plugin.*;
    9. import org.apache.ibatis.session.ResultHandler;
    10. import org.apache.ibatis.session.RowBounds;
    11. import org.springframework.stereotype.Component;
    12. import java.util.Map;
    13. import java.util.Properties;
    14. /**
    15. * ClassName: DbSwitchInterceptor
    16. * Description:
    17. * date: 2022/8/10 11:57
    18. *
    19. * @author yanglp
    20. * @since JDK 1.8
    21. */
    22. @Intercepts(
    23. {
    24. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    25. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    26. }
    27. )
    28. @Component
    29. public class DbSwitchInterceptor implements Interceptor {
    30. @Override
    31. public Object intercept(Invocation invocation) throws Throwable {
    32. Object[] args = invocation.getArgs();
    33. MappedStatement ms = (MappedStatement) args[0];
    34. Object parameter = args[1];
    35. RowBounds rowBounds = (RowBounds) args[2];
    36. ResultHandler resultHandler = (ResultHandler) args[3];
    37. Executor executor = (Executor) invocation.getTarget();
    38. CacheKey cacheKey;
    39. BoundSql boundSql;
    40. //由于逻辑关系,只会进入一次
    41. if (args.length == 4) {
    42. //4 个参数时
    43. boundSql = ms.getBoundSql(parameter);
    44. cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
    45. } else {
    46. //6 个参数时
    47. cacheKey = (CacheKey) args[4];
    48. boundSql = (BoundSql) args[5];
    49. }
    50. if (!executor.getTransaction().getConnection().getMetaData().getURL().contains("mysql")) {//mysql不做拦截处理
    51. String sqlTmp = boundSql.getSql();
    52. String tenant = "";
    53. Object _parameter = boundSql.getAdditionalParameter("_parameter");
    54. if (_parameter != null) {
    55. if (_parameter instanceof Map) {
    56. if (((Map) _parameter).containsKey("tenant")) {
    57. Object obj = ((Map) _parameter).get("tenant");
    58. tenant = obj != null ? obj.toString() : null;
    59. }
    60. } else {
    61. tenant = ReflectUtils.getFieldValue(_parameter, "tenant");
    62. }
    63. if (!StringUtils.isEmpty(tenant)) {
    64. sqlTmp = "USE " + tenant + "; " + sqlTmp;
    65. ReflectUtils.setFieldValue(boundSql, "sql", sqlTmp);
    66. }
    67. }
    68. }
    69. //TODO 自己要进行的各种处理
    70. //注:下面的方法可以根据自己的逻辑调用多次,在分页插件中,count 和 page 各调用了一次
    71. return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    72. /*
    73. // 拦截sql
    74. Object[] args = invocation.getArgs();
    75. MappedStatement statement = (MappedStatement) args[0];
    76. Object parameterObject = args[1];
    77. BoundSql boundSql = statement.getBoundSql(parameterObject);
    78. String sql = boundSql.getSql();
    79. LOGGER.info("获取到的SQL:{}"+sql);
    80. if (StringUtils.isBlank(sql)) {
    81. return invocation.proceed();
    82. }
    83. // 返回
    84. return invocation.proceed();
    85. */
    86. }
    87. @Override
    88. public Object plugin(Object obj) {
    89. return Plugin.wrap(obj, this);
    90. }
    91. @Override
    92. public void setProperties(Properties properties) {
    93. }
    94. }

    怎么让自定义拦截器生效呢

    1.在mybatis的配置文件(mytatis-config.xml)中配置

    1. <plugins>
    2. <plugin interceptor="com.hsf.framework.interceptor.DbSwitchInterceptor"/>
    3. plugins>

    重启发现生效了,但是执行顺序不对了,我们想要DbSwitchInterceptor自定义拦截器在分页拦截器PageInterceptor之后进行sql修改,继续看文档https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Interceptor.md配置多个拦截器的顺序1,2,3执行3,2,1,2,3因此我们将配置改成这样,预期是PageInterceptor执行完之后DbSwitchInterceptor再拦截处理

    1. <plugins>
    2. <plugin interceptor="com.hsf.framework.interceptor.DbSwitchInterceptor"/>
    3. <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    4. plugins>

    重启验证,直接重启报错

    Caused by: java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!

    mybatis默认就已经加载了PageInterceptor拦截器,那么怎么禁止mybatis自动加载改为手动加载呢,在启动类上加上配置PageHelperAutoConfiguration.class

    1. @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,PageHelperAutoConfiguration.class
    2. })

    再重启,依然还是顺序不对.

    在尝试另外一种办法,将前面在mybatis-config.xml中的注释掉,启动类上的PageHelperAutoConfiguration.class也注释掉,在mybatis的配置类(MybatisConfig.java)中注入DbSwitchInterceptor,并且设置到sessionFactory中

    sessionFactory.setPlugins(new Interceptor[]{dbSwitchInterceptor});
    1. package com.hsf.framework.config;
    2. import com.hsf.common.utils.StringUtils;
    3. import com.hsf.framework.interceptor.DbSwitchInterceptor;
    4. import org.apache.ibatis.io.VFS;
    5. import org.apache.ibatis.plugin.Interceptor;
    6. import org.apache.ibatis.session.SqlSessionFactory;
    7. import org.mybatis.spring.SqlSessionFactoryBean;
    8. import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.context.annotation.Bean;
    11. import org.springframework.context.annotation.Configuration;
    12. import org.springframework.core.env.Environment;
    13. import org.springframework.core.io.DefaultResourceLoader;
    14. import org.springframework.core.io.Resource;
    15. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    16. import org.springframework.core.io.support.ResourcePatternResolver;
    17. import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    18. import org.springframework.core.type.classreading.MetadataReader;
    19. import org.springframework.core.type.classreading.MetadataReaderFactory;
    20. import org.springframework.util.ClassUtils;
    21. import javax.sql.DataSource;
    22. import java.io.IOException;
    23. import java.util.ArrayList;
    24. import java.util.Arrays;
    25. import java.util.HashSet;
    26. import java.util.List;
    27. /**
    28. * Mybatis支持*匹配扫描包
    29. *
    30. * @author hsf
    31. */
    32. @Configuration
    33. public class MyBatisConfig
    34. {
    35. @Autowired
    36. private Environment env;
    37. @Autowired
    38. DbSwitchInterceptor dbSwitchInterceptor;
    39. static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    40. public static String setTypeAliasesPackage(String typeAliasesPackage)
    41. {
    42. ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
    43. MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
    44. List allResult = new ArrayList();
    45. try
    46. {
    47. for (String aliasesPackage : typeAliasesPackage.split(","))
    48. {
    49. List result = new ArrayList();
    50. aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
    51. + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
    52. Resource[] resources = resolver.getResources(aliasesPackage);
    53. if (resources != null && resources.length > 0)
    54. {
    55. MetadataReader metadataReader = null;
    56. for (Resource resource : resources)
    57. {
    58. if (resource.isReadable())
    59. {
    60. metadataReader = metadataReaderFactory.getMetadataReader(resource);
    61. try
    62. {
    63. result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
    64. }
    65. catch (ClassNotFoundException e)
    66. {
    67. e.printStackTrace();
    68. }
    69. }
    70. }
    71. }
    72. if (result.size() > 0)
    73. {
    74. HashSet hashResult = new HashSet(result);
    75. allResult.addAll(hashResult);
    76. }
    77. }
    78. if (allResult.size() > 0)
    79. {
    80. typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
    81. }
    82. else
    83. {
    84. throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
    85. }
    86. }
    87. catch (IOException e)
    88. {
    89. e.printStackTrace();
    90. }
    91. return typeAliasesPackage;
    92. }
    93. public Resource[] resolveMapperLocations(String[] mapperLocations)
    94. {
    95. ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    96. List resources = new ArrayList();
    97. if (mapperLocations != null)
    98. {
    99. for (String mapperLocation : mapperLocations)
    100. {
    101. try
    102. {
    103. Resource[] mappers = resourceResolver.getResources(mapperLocation);
    104. resources.addAll(Arrays.asList(mappers));
    105. }
    106. catch (IOException e)
    107. {
    108. // ignore
    109. }
    110. }
    111. }
    112. return resources.toArray(new Resource[resources.size()]);
    113. }
    114. @Bean
    115. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
    116. {
    117. String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
    118. String mapperLocations = env.getProperty("mybatis.mapperLocations");
    119. String configLocation = env.getProperty("mybatis.configLocation");
    120. typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
    121. VFS.addImplClass(SpringBootVFS.class);
    122. final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    123. sessionFactory.setDataSource(dataSource);
    124. sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
    125. sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
    126. sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
    127. sessionFactory.setPlugins(new Interceptor[]{dbSwitchInterceptor});
    128. return sessionFactory.getObject();
    129. }
    130. }

    重启之后拦截顺序达到目的了,先执行PageInterceptor再执行自定义拦截器

  • 相关阅读:
    Windows系统--AD域控--DHCP服务器
    没钱服务器安全怎么搞
    Allegro Design Entry HDL(OrCAD Capture HDL)窗口管理菜单详细介绍
    Spring学习(8) AOP
    Linux线程的同步与互斥
    【面试题】Callable使用
    vscode 如何断点调试ros1工程
    64-基于51单片机的可调直流稳压电源Proteus仿真(源码+仿真+全套资料)
    QJsonParseError::errorString() == “unterminated object“
    LeetCode 1876. 长度为三且各字符不同的子字符串
  • 原文地址:https://blog.csdn.net/QQ317968200/article/details/126320180