在mybatis的mapper.xml文件中,我们可以使用#{}或${}的方式获取到参数,这些参数都需要提前我们在mapper.java接口文件中通过参数的方式传入参数才能取到
为了扩展参数,我们需要了解mybatis是怎么帮我们保管mapper.java中传入的参数的
进入Executor.java接口查看query方法,可以看到第一个参数MappedStatement对象中有一个parameterMap字段,该字段是Map类型保存我们的参数,那我们只需要在拦截器中对MappedStatement对象的parameterMap中put自己想要的参数即可
@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}) }) public class DeptDataScopeInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); if (ArrayUtils.isNotEmpty(args)) { MappedStatement mappedStatement = (MappedStatement) args[0]; DataScope dataScope = getDataScope(mappedStatement); StringBuilder sqlString = new StringBuilder(); if (dataScope != null) { Long companyOrgId = SecurityUtils.getCompanyOrgId(); if (companyOrgId != null) { sqlString.append(StringUtils.format(" {}.{} = {} ", dataScope.orgAlias(), dataScope.orgColumnName(), companyOrgId)); } else { sqlString.append(" 1=0 "); } } if (args[1] == null) { args[1] = new MapperMethod.ParamMap<>(); } Map map = (Map) args[1]; // 此处为重点 map.put("dataScope", sqlString.length() > 0 ? " (" + sqlString.toString() + ")" : ""); } return invocation.proceed(); } private DataScope getDataScope(MappedStatement mappedStatement) { String id = mappedStatement.getId(); // 获取 Class Method String clazzName = id.substring(0, id.lastIndexOf('.')); String mapperMethod = id.substring(id.lastIndexOf('.') + 1); Class<?> clazz; try { clazz = Class.forName(clazzName); } catch (ClassNotFoundException e) { return null; } Method[] methods = clazz.getMethods(); DataScope dataScope = null; for (Method method : methods) { if (method.getName().equals(mapperMethod)) { dataScope = method.getAnnotation(DataScope.class); break; } } return dataScope; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }
在map中加入dataScope后,即可在相应的mapper.xml中使用#{dataScope}或${dataScope}或取到该参数
如果项目中使用了pageHelper插件,则启动项目后,执行到对应的mapper查询的时候,如果mapper.xml中使用了上述方式添加的参数,那么项目会报错,因为pageHelper的拦截器会在我们的拦截器之前执行,pageHelper的拦截器中对参数进行了校验,因为自定的拦截器还没有执行,则Map中不会有自定义参数,当pageHelper的拦截器开始校验参数的时候就会报错找不到参数
如何将自定义的拦截器放在PageHelper拦截器前方执行
查阅资料后发现SqlSessionFactory中加入的拦截器,先加入的会后执行,后加入的先执行,那么我们需要让自定义的拦截器在PageHelper后加入,查看PageHelper的自动配置类
@Configuration @ConditionalOnBean(SqlSessionFactory.class) @EnableConfigurationProperties(PageHelperProperties.class) @AutoConfigureAfter(MybatisAutoConfiguration.class) @Lazy(false) public class PageHelperAutoConfiguration { @Autowired private List<SqlSessionFactory> sqlSessionFactoryList; @Autowired private PageHelperProperties properties; /** * 接受分页插件额外的属性 * * @return */ @Bean @ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX) public Properties pageHelperProperties() { return new Properties(); } @PostConstruct public void addPageInterceptor() { PageInterceptor interceptor = new PageInterceptor(); Properties properties = new Properties(); //先把一般方式配置的属性放进去 properties.putAll(pageHelperProperties()); //在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步 properties.putAll(this.properties.getProperties()); interceptor.setProperties(properties); for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) { org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration(); if (!containsInterceptor(configuration, interceptor)) { configuration.addInterceptor(interceptor); } } } /** * 是否已经存在相同的拦截器 * * @param configuration * @param interceptor * @return */ private boolean containsInterceptor(org.apache.ibatis.session.Configuration configuration, Interceptor interceptor) { try { // getInterceptors since 3.2.2 return configuration.getInterceptors().contains(interceptor); } catch (Exception e) { return false; } } }
pageHelper的拦截器会在Mybatis的自动配置后在做相应配置,那么我们可以写一个配置类,让他在PageHelper的配置类后再执行
@Configuration @ConditionalOnBean(SqlSessionFactory.class) @EnableConfigurationProperties(PageHelperProperties.class) @Lazy(false) @AutoConfigureAfter(PageHelperAutoConfiguration.class) public class DeptDataScopeInterceptorConfig { @Autowired private List<SqlSessionFactory> sqlSessionFactoryList; @PostConstruct public void addPageInterceptor() { DeptDataScopeInterceptor interceptor = new DeptDataScopeInterceptor(); for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) { sqlSessionFactory.getConfiguration().addInterceptor(interceptor); } } }
此处需要注意的是,如果DeptDataScopeInterceptorConfig配置类会被SpringBoot项目自动扫描到,尽管配置的顺序是在PageHelper自动配置后再配置,但是被SpringBoot扫描到的配置类会优先加载,所以要防止SpringBoot扫描带该配置,有如下几种方案
如果该配置类已经会被扫描到,则可以使用排出的方式排除掉该类,然后用spring.factories的方式做自动配置,可以使用
1.@SpringBootApplication(exclude = DeptDataScopeInterceptorConfig.class)
2.@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = DeptDataScopeInterceptorConfig.class))
3.添加到spring.factories中文件路径地址resrouces/META-INF/spring.factories(如果报错,可以试试在加上@EnableAutoConfiguration(exclude = DeptDataScopeInterceptorConfig.class))
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
top.sclf.common.datascope.config.DeptDataScopeInterceptorConfig
参考
https://blog.csdn.net/qq1010830256/article/details/115617442
http://xtong.tech/2018/08/01/MyBatis%E6%8B%A6%E6%88%AA%E5%99%A8%E5%9B%A0pagehelper%E8%80%8C%E5%A4%B1%E6%95%88%E7%9A%84%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/