使用的若依前后端分离框架,
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分析下
如下是一个自定义拦截器
- package com.hsf.framework.interceptor;
-
- import com.hsf.common.utils.StringUtils;
- import com.hsf.common.utils.reflect.ReflectUtils;
- 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.session.ResultHandler;
- import org.apache.ibatis.session.RowBounds;
- import org.springframework.stereotype.Component;
-
- import java.util.Map;
- import java.util.Properties;
-
- /**
- * ClassName: DbSwitchInterceptor
- * Description:
- * date: 2022/8/10 11:57
- *
- * @author yanglp
- * @since JDK 1.8
- */
- @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}),
- }
- )
- @Component
- public class DbSwitchInterceptor implements Interceptor {
-
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- Object[] args = invocation.getArgs();
- MappedStatement ms = (MappedStatement) args[0];
- Object parameter = args[1];
- RowBounds rowBounds = (RowBounds) args[2];
- ResultHandler resultHandler = (ResultHandler) args[3];
- Executor executor = (Executor) invocation.getTarget();
- CacheKey cacheKey;
- BoundSql boundSql;
- //由于逻辑关系,只会进入一次
- if (args.length == 4) {
- //4 个参数时
- boundSql = ms.getBoundSql(parameter);
- cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
- } else {
- //6 个参数时
- cacheKey = (CacheKey) args[4];
- boundSql = (BoundSql) args[5];
- }
-
- if (!executor.getTransaction().getConnection().getMetaData().getURL().contains("mysql")) {//mysql不做拦截处理
- String sqlTmp = boundSql.getSql();
- String tenant = "";
- Object _parameter = boundSql.getAdditionalParameter("_parameter");
- if (_parameter != null) {
- if (_parameter instanceof Map) {
- if (((Map) _parameter).containsKey("tenant")) {
- Object obj = ((Map) _parameter).get("tenant");
- tenant = obj != null ? obj.toString() : null;
- }
- } else {
- tenant = ReflectUtils.getFieldValue(_parameter, "tenant");
- }
- if (!StringUtils.isEmpty(tenant)) {
- sqlTmp = "USE " + tenant + "; " + sqlTmp;
- ReflectUtils.setFieldValue(boundSql, "sql", sqlTmp);
- }
- }
- }
- //TODO 自己要进行的各种处理
- //注:下面的方法可以根据自己的逻辑调用多次,在分页插件中,count 和 page 各调用了一次
- return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
-
- /*
- // 拦截sql
- Object[] args = invocation.getArgs();
- MappedStatement statement = (MappedStatement) args[0];
- Object parameterObject = args[1];
- BoundSql boundSql = statement.getBoundSql(parameterObject);
- String sql = boundSql.getSql();
- LOGGER.info("获取到的SQL:{}"+sql);
- if (StringUtils.isBlank(sql)) {
- return invocation.proceed();
- }
- // 返回
- return invocation.proceed();
- */
- }
-
- @Override
- public Object plugin(Object obj) {
- return Plugin.wrap(obj, this);
- }
-
- @Override
- public void setProperties(Properties properties) {
- }
- }
怎么让自定义拦截器生效呢
1.在mybatis的配置文件(mytatis-config.xml)中配置
- <plugins>
- <plugin interceptor="com.hsf.framework.interceptor.DbSwitchInterceptor"/>
- 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再拦截处理
- <plugins>
- <plugin interceptor="com.hsf.framework.interceptor.DbSwitchInterceptor"/>
- <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
- plugins>
重启验证,直接重启报错
Caused by: java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!
mybatis默认就已经加载了PageInterceptor拦截器,那么怎么禁止mybatis自动加载改为手动加载呢,在启动类上加上配置PageHelperAutoConfiguration.class
- @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,PageHelperAutoConfiguration.class
- })
再重启,依然还是顺序不对.
在尝试另外一种办法,将前面在mybatis-config.xml中的
sessionFactory.setPlugins(new Interceptor[]{dbSwitchInterceptor});
- package com.hsf.framework.config;
-
- import com.hsf.common.utils.StringUtils;
- import com.hsf.framework.interceptor.DbSwitchInterceptor;
- import org.apache.ibatis.io.VFS;
- import org.apache.ibatis.plugin.Interceptor;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.mybatis.spring.SqlSessionFactoryBean;
- import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.core.env.Environment;
- import org.springframework.core.io.DefaultResourceLoader;
- import org.springframework.core.io.Resource;
- import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
- import org.springframework.core.io.support.ResourcePatternResolver;
- import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
- import org.springframework.core.type.classreading.MetadataReader;
- import org.springframework.core.type.classreading.MetadataReaderFactory;
- import org.springframework.util.ClassUtils;
-
- import javax.sql.DataSource;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.HashSet;
- import java.util.List;
-
- /**
- * Mybatis支持*匹配扫描包
- *
- * @author hsf
- */
- @Configuration
- public class MyBatisConfig
- {
- @Autowired
- private Environment env;
-
- @Autowired
- DbSwitchInterceptor dbSwitchInterceptor;
-
- static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
-
- public static String setTypeAliasesPackage(String typeAliasesPackage)
- {
- ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
- MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
- List
allResult = new ArrayList(); - try
- {
- for (String aliasesPackage : typeAliasesPackage.split(","))
- {
- List
result = new ArrayList(); - aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
- + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
- Resource[] resources = resolver.getResources(aliasesPackage);
- if (resources != null && resources.length > 0)
- {
- MetadataReader metadataReader = null;
- for (Resource resource : resources)
- {
- if (resource.isReadable())
- {
- metadataReader = metadataReaderFactory.getMetadataReader(resource);
- try
- {
- result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
- }
- catch (ClassNotFoundException e)
- {
- e.printStackTrace();
- }
- }
- }
- }
- if (result.size() > 0)
- {
- HashSet
hashResult = new HashSet(result); - allResult.addAll(hashResult);
- }
- }
- if (allResult.size() > 0)
- {
- typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
- }
- else
- {
- throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
- }
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- return typeAliasesPackage;
- }
-
- public Resource[] resolveMapperLocations(String[] mapperLocations)
- {
- ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
- List
resources = new ArrayList(); - if (mapperLocations != null)
- {
- for (String mapperLocation : mapperLocations)
- {
- try
- {
- Resource[] mappers = resourceResolver.getResources(mapperLocation);
- resources.addAll(Arrays.asList(mappers));
- }
- catch (IOException e)
- {
- // ignore
- }
- }
- }
- return resources.toArray(new Resource[resources.size()]);
- }
-
- @Bean
- public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
- {
- String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
- String mapperLocations = env.getProperty("mybatis.mapperLocations");
- String configLocation = env.getProperty("mybatis.configLocation");
- typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
- VFS.addImplClass(SpringBootVFS.class);
-
- final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
- sessionFactory.setDataSource(dataSource);
- sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
- sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
- sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
- sessionFactory.setPlugins(new Interceptor[]{dbSwitchInterceptor});
- return sessionFactory.getObject();
- }
- }
重启之后拦截顺序达到目的了,先执行PageInterceptor再执行自定义拦截器