springboot多数据源动态切换和自定义mybatis分页插件
1、配置多数据源
增加druid依赖
完整pom文件
数据源配置文件
route.datasource.driver-class-name= com.mysql.jdbc.Driver route.datasource.url= jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8 route.datasource.username= root route.datasource.password= 123456operate.datasource.driver-class-name= com.mysql.jdbc.Driver operate.datasource.url= jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8 operate.datasource.username= root operate.datasource.password= 123456
multiple-datasource.properties
初始化数据源
/*** 多数据源配置* @author zhouliang* @date 2017年9月20日*/ @Configuration @PropertySource("classpath:multiple-datasource.properties") public class MyBatisConfig {@Autowiredprivate Environment env;@Autowired MybatisSpringPageInterceptor inteceptor;/*** 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)*/@Beanpublic DataSource operateDataSource() throws Exception {Properties props = new Properties();props.put("driverClassName", env.getProperty("operate.datasource.driver-class-name"));props.put("url", env.getProperty("operate.datasource.url"));props.put("username", env.getProperty("operate.datasource.username"));props.put("password", env.getProperty("operate.datasource.password"));return DruidDataSourceFactory.createDataSource(props);}@Beanpublic DataSource routeDataSource() throws Exception {Properties props = new Properties();props.put("driverClassName", env.getProperty("route.datasource.driver-class-name"));props.put("url", env.getProperty("route.datasource.url"));props.put("username", env.getProperty("route.datasource.username"));props.put("password", env.getProperty("route.datasource.password"));return DruidDataSourceFactory.createDataSource(props);}/*** @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)*/@Bean@Primarypublic DynamicDataSource dataSource(@Qualifier("routeDataSource") DataSource routeDataSource,@Qualifier("operateDataSource") DataSource operateDataSource) {Map<Object, Object> targetDataSources = new HashMap<Object, Object>();targetDataSources.put(DatabaseType.routeDS, routeDataSource);targetDataSources.put(DatabaseType.operateDS, operateDataSource);DynamicDataSource dataSource = new DynamicDataSource();dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法return dataSource;}/*** 根据数据源创建SqlSessionFactory*/@Beanpublic SqlSessionFactory sqlSessionFactory(DynamicDataSource ds)throws Exception {SqlSessionFactoryBean fb = new SqlSessionFactoryBean();fb.setDataSource(ds);// 指定数据源(这个必须有,否则报错)// 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));// fb.setPlugins(new Interceptor[]{inteceptor});return fb.getObject();}}
MyBatisConfig.java
@Bean是分别注入两个数据源,
当自动注入多个同样的bean时需要指定一个默认额,所以这里指定了一个默认的抽象数据源@primary
数据源注入之后需要创建SqlSessionFactory
创建抽象数据源类集成AbstractRoutingDataSource
1 public class DynamicDataSource extends AbstractRoutingDataSource { 2 3 4 @Override 5 protected Object determineCurrentLookupKey() { 6 // TODO Auto-generated method stub 7 return DatabaseContextHolder.getDatabaseType(); 8 } 9 10 }
DynamicDataSource.java
1 public enum DatabaseType { 2 routeDS,operateDS 3 }
DatabaseType.java
1 public class DatabaseContextHolder { 2 private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<DatabaseType>(); 3 4 public static void setDatabaseType(DatabaseType type){ 5 contextHolder.set(type); 6 } 7 8 public static DatabaseType getDatabaseType(){ 9 return contextHolder.get(); 10 } 11 public static void clearDatabaseType(){ 12 contextHolder.remove(); 13 }
DatabaseContextHolder.java
上述配置完成之后需要在配置文件中指定mybatis映射的xml文件的位置
指定mybatis映射文件之后需要在项目的启动类上排除springboot默认的数据库的配置以及指定mybatis映射文件对应的接口
不排除springboot默认的数据库配置类,项目启动的时候会报错启动失败
@MapperScan指定了mybatis映射文件对应的接口所在的目录,这样避免了再每个接口上都加上@Mapper的注解
到此两个数据源已经配置完成
2、数据源的动态切换
这里设置了所有的实体bean都继承了一个父类,父类信息如下
1 public class BaseBean implements Serializable{ 2 private int pageSize=10; 3 private int pageNo=0; 4 private long totalNum; 5 private String totalMappedStatementId; 6 7 8 private long shardValue = 0l; 9 10 public BaseBean() { 11 super(); 12 // TODO Auto-generated constructor stub 13 } 14 15 public BaseBean(int pageSize, int pageNo, int totalNum) { 16 super(); 17 this.pageSize = pageSize; 18 this.pageNo = pageNo; 19 this.totalNum = totalNum; 20 } 21 22 public int getPageSize() { 23 return pageSize; 24 } 25 26 public long getShardValue() { 27 return shardValue; 28 } 29 30 public void setShardValue(long shardValue) { 31 this.shardValue = shardValue; 32 } 33 34 public String getTotalMappedStatementId() { 35 return totalMappedStatementId; 36 } 37 38 public void setTotalMappedStatementId(String totalMappedStatementId) { 39 this.totalMappedStatementId = totalMappedStatementId; 40 } 41 42 public void setPageSize(int pageSize) { 43 this.pageSize = pageSize; 44 } 45 46 public int getPageNo() { 47 return pageNo; 48 } 49 50 public void setPageNo(int pageNo) { 51 this.pageNo = pageNo; 52 } 53 54 public long getTotalNum() { 55 return totalNum; 56 } 57 58 public void setTotalNum(long totalNum) { 59 this.totalNum = totalNum; 60 } 61 62 }
BaseBean.java
BaseBean中的shardValue属性是用来指定数据源的,默认值为0,其余属性是分页相关的。
数据源的动态切换是通过spring的切面编程来实现的,通过对mybatis的映射文件对应的接口进行监控,代码如下
1 @Aspect 2 @Component 3 public class DataSourceAspect { 4 Logger logger = LoggerFactory.getLogger(DataSourceAspect.class); 5 6 @Before("execution(* zl.mybatis.mapper.*.*(..))") 7 public void setDataSourcePgKey(JoinPoint point) { 8 Object args[] = point.getArgs(); 9 for(Object obj:args){ 10 if(obj instanceof BaseBean){ 11 BaseBean bean = (BaseBean) obj; 12 if(Common.DB_0==bean.getShardValue()){ 13 logger.info("===========================使用数据源DB_route======================="); 14 DatabaseContextHolder.setDatabaseType(DatabaseType.routeDS); 15 }else{ 16 logger.info("===========================使用数据源DB_operate======================="); 17 DatabaseContextHolder.setDatabaseType(DatabaseType.operateDS); 18 } 19 break; 20 } 21 } 22 } 23 24 }
DataSourceAspect.java
@Before("execution(* zl.mybatis.mapper.*.*(..))")是对mybatis的映射文件对应的接口进行监控,根据获取到的参数实体类判断里面的shardValue的值来决定使用哪个数据源
完成这一步之后springboot的多数据源动态切换完成了,接下来继续
3、实现自定义mybatis的分页插件
mybatis本身提供了对数据库操作的拦截器,所以实现自定义分页的时候只需要实现这个接口自定义里面的拦截方法。这里我是只拦截了查询的方法
具体代码如下
1 @Component 2 @Intercepts({ @Signature(type = Executor.class, method = "query", 3 args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) }) 4 public class MybatisSpringPageInterceptor implements Interceptor { 5 6 private static final Logger logger = LoggerFactory.getLogger(MybatisSpringPageInterceptor.class.getName()); 7 8 9 @SuppressWarnings("unused") 10 public Object intercept(Invocation arg0) throws Throwable { 11 MappedStatement mappedStatement = (MappedStatement) arg0.getArgs()[0]; 12 Object parameter = arg0.getArgs()[1]; 13 BoundSql boundSql = mappedStatement.getBoundSql(parameter); 14 if (null == boundSql || StringUtils.isBlank(boundSql.getSql())) { 15 return null; 16 } 17 RowBounds rowBounds = (RowBounds) arg0.getArgs()[2]; 18 Object parameterObject = boundSql.getParameterObject(); 19 BaseBean model = null; 20 if (parameterObject instanceof BaseBean) { 21 model = (BaseBean) parameterObject; 22 } else { 23 BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, boundSql.getSql()); 24 arg0.getArgs()[0] = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql)); 25 return arg0.proceed(); 26 } 27 if (null == model) { 28 throw new Exception("无法获取分页参数."); 29 } 30 if (model.getPageNo() == -1) { 31 BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, boundSql.getSql()); 32 arg0.getArgs()[0] = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql)); 33 return arg0.proceed(); 34 } 35 String shardSql = boundSql.getSql(); 36 queryTotal(mappedStatement, shardSql, parameterObject, boundSql,model); 37 38 if (null == rowBounds || rowBounds == RowBounds.DEFAULT) { 39 rowBounds = new RowBounds(model.getPageSize() * (model.getPageNo() - 1), model.getPageSize()); 40 } 41 String pagesql = getLimitSql(shardSql, rowBounds.getOffset(), rowBounds.getLimit()); 42 arg0.getArgs()[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT); 43 BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, pagesql); 44 arg0.getArgs()[0] = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql)); 45 return arg0.proceed(); 46 } 47 48 public static class BoundSqlSqlSource implements SqlSource { 49 BoundSql boundSql; 50 51 public BoundSqlSqlSource(BoundSql boundSql) { 52 this.boundSql = boundSql; 53 } 54 55 public BoundSql getBoundSql(Object parameterObject) { 56 return boundSql; 57 } 58 } 59 private String getLimitSql(String sql, int start, int end) throws Exception{ 60 if(sql ==null){ 61 throw new Exception("execute sql is empty."); 62 } 63 StringBuffer sqlBuffer = new StringBuffer(sql.length()+300); 64 sqlBuffer.append(sql); 65 sqlBuffer.append(" LIMIT ").append(start).append(",").append(end); 66 return sqlBuffer.toString(); 67 } 68 private void queryTotal(MappedStatement mappedStatement, String replaceSql, Object parameterObject, BoundSql boundSql,BaseBean model) throws Exception{ 69 StringBuffer countSql = new StringBuffer(); 70 71 if(model.getTotalMappedStatementId()!=null && model.getTotalMappedStatementId().length()>0){ 72 MappedStatement totalMappedStatement=mappedStatement.getConfiguration().getMappedStatement(model.getTotalMappedStatementId()); 73 BoundSql totalBoundSql = totalMappedStatement.getBoundSql(parameterObject); 74 75 countSql.append(totalBoundSql.getSql()); 76 }else{ 77 // 未指定,自动拼装 78 countSql.append("SELECT COUNT(1) FROM (").append(replaceSql).append(") as total"); 79 } 80 81 Connection conn = null; 82 PreparedStatement ps = null; 83 ResultSet rs = null; 84 try { 85 conn = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection(); 86 if (logger.isDebugEnabled()) { 87 logger.debug(countSql.toString()); 88 } 89 ps = conn.prepareStatement(countSql.toString()); 90 BoundSql countBS = copyFromBoundSql(mappedStatement, boundSql, countSql.toString()); 91 setParameters(ps, mappedStatement, countBS, parameterObject); 92 rs = ps.executeQuery(); 93 if (rs.next()) { 94 model.setTotalNum(rs.getLong(1)); 95 } 96 } catch (Exception e) { 97 logger.error(e.getMessage(), e); 98 throw new Exception(e.getMessage(), e); 99 } finally { 100 try { 101 if (null != rs) { 102 rs.close(); 103 } 104 } catch (Exception e) { 105 logger.error("rs.close() error!", e); 106 } 107 try { 108 if (null != ps) { 109 ps.close(); 110 } 111 } catch (Exception e) { 112 logger.error("ps.close() error!", e); 113 } 114 try { 115 if (null != conn) { 116 conn.close(); 117 } 118 } catch (Exception e) { 119 logger.error("conn.close() error!", e); 120 } 121 } 122 } 123 protected MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) { 124 Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType()); 125 builder.resource(ms.getResource()); 126 builder.fetchSize(ms.getFetchSize()); 127 builder.statementType(ms.getStatementType()); 128 builder.keyGenerator(ms.getKeyGenerator()); 129 // builder.keyProperty(ms.getKeyProperties()); 130 builder.timeout(ms.getTimeout()); 131 builder.parameterMap(ms.getParameterMap()); 132 builder.resultMaps(ms.getResultMaps()); 133 builder.cache(ms.getCache()); 134 MappedStatement newMs = builder.build(); 135 return newMs; 136 } 137 138 /** 139 * 140 * @param ps 141 * @param mappedStatement 142 * @param boundSql 143 * @param parameterObject 144 * @throws SQLException 145 */ 146 private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException { 147 ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); 148 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); 149 if (parameterMappings != null) { 150 Configuration configuration = mappedStatement.getConfiguration(); 151 TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 152 MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); 153 for (int i = 0; i < parameterMappings.size(); i++) { 154 ParameterMapping parameterMapping = parameterMappings.get(i); 155 if (parameterMapping.getMode() != ParameterMode.OUT) { 156 Object value; 157 String propertyName = parameterMapping.getProperty(); 158 if (parameterObject == null) { 159 value = null; 160 } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { 161 value = parameterObject; 162 } else if (boundSql.hasAdditionalParameter(propertyName)) { 163 value = boundSql.getAdditionalParameter(propertyName); 164 } else { 165 value = metaObject == null ? null : metaObject.getValue(propertyName); 166 } 167 TypeHandler typeHandler = parameterMapping.getTypeHandler(); 168 if (typeHandler == null) { 169 throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId()); 170 } 171 logger.debug(i + 1 + ":" + value); 172 typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType()); 173 } 174 } 175 } 176 } 177 @Override 178 public Object plugin(Object arg0) { 179 return Plugin.wrap(arg0, this); 180 } 181 182 public void setProperties(Properties arg0) { 183 184 } 185 186 187 private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) { 188 BoundSql newBoundSql = new BoundSql(ms.getConfiguration(),sql, boundSql.getParameterMappings(), boundSql.getParameterObject()); 189 for (ParameterMapping mapping : boundSql.getParameterMappings()) { 190 String prop = mapping.getProperty(); 191 if (boundSql.hasAdditionalParameter(prop)) { 192 newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop)); 193 } 194 } 195 return newBoundSql; 196 } 197 198 }
MybatisSpringPageInterceptor.java
插件重写完成之后需要在注入SqlSessionFactory的时候指定这个插件,下面的代码是在MyBatisConfig.java中
现在已经完成了多数据源的动态切换以及自定义mybatis的分页查询了,剩下的测试步骤就不再啰嗦了,排版比较乱大家就将就着看吧!
项目的完整代码
转载于:https://www.cnblogs.com/gulang-jx/p/7868119.html
springboot多数据源动态切换和自定义mybatis分页插件相关推荐
- springboot多数据源动态切换,事务下切换数据源(非分布式事务)
目录 1.业务场景 2.主要思路 3.加载默认数据源 4.多数据源规则配置 5.程序启动加载子公司数据源 6.自定义事务注解 7.程序中调用 1.业务场景 因为业务业务需求,需要把基础数据与子公司业务 ...
- mysql双数据源动态切换_Spring boot+Mybatis多数据源动态切换
通过重写AbstractRoutingDataSource类中方法determineTargetDataSource,determineCurrentLookupKey,afterProperties ...
- springboot使用mybatis多数据源动态切换的实现
需求:项目使用了读写分离,或者数据进行了分库处理,我们希望在操作不同的数据库的时候,我们的程序能够动态的切换到相应的数据库,执行相关的操作. 首先,你需要一个能够正常运行的springboot项目,配 ...
- Spring-Boot + AOP实现多数据源动态切换
2019独角兽企业重金招聘Python工程师标准>>> 最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据 ...
- SpringBoot+AOP实现动态切换数据源
首先描述下笔者的基本步骤: 1.yml配置文件中定义多数据源: 2.自定义一个注解类(@DataSource 可标注在类或方法上),定义String类型的变量value,value中存储数据源名称: ...
- SpringBoot+AOP实现多数据源动态切换
SpringBoot+AOP实现多数据源动态切换 背景 设计总体思路 步骤 背景 系统后端需要访问多个数据库,现有的数据库连接配置写入配置文件中.后端需要从一个数据库的配置表里动态的读取其它mysql ...
- Spring+Mybatis多数据源配置(四)——AbstractRoutingDataSource实现数据源动态切换
欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...
- Proxool配置多数据源动态切换
2019独角兽企业重金招聘Python工程师标准>>> 前段时间遇到多数据源动态切换问题,总结一下,做个记录,以备后续之需! 首先附上proxool连接池的配置方法:http://3 ...
- springboot整合mybatis分页插件
1.springboot版本为2.0.1,数据库为mysql,引入pagehelper的pom依赖 <!--mybatis分页插件--> <dependency><gro ...
最新文章
- 【鉴轻尘】你说你炒的了币,开发得了项目,然而这些你都知道吗?
- Java多线程之CyclicBarrier用法
- Redis常用概念简介
- 厉害!这位80后连发2篇Science!身处普通高校,告诉你科研不决取于平台
- jvm学习笔记(4)——jvm配置调优
- java 图片拼接_Java拼接多张图片,可以连接在一起 | 学步园
- mysql更换主键遇到的一个问题
- python第三方库——requests
- 软件也要歧视大龄程序员吗?
- 通过web的方式动态查看tomcat的catalina.out的日志(web.py)
- vue组件化的基本使用
- android 自定义地图标注,Android高德地图自定义Markers【原创】
- BNS100数码人像采集系统产品说明书
- 智慧交通管理和服务平台系统集成解决方案
- u盘删除的文件怎么恢复
- 高度坍塌的产生条件和解决方法
- ubuntu mysql的my.cnf_Ubuntu下面MySQL的参数文件my.cnf浅析
- 陶哲轩是如何安排时间的
- 电音(5)Bass类电音
- 解决VC中编译出现”SHSQ.dll This required file cannot be loaded. Please re-install Microsoft Visual C++“方法之一
热门文章
- python电脑安装-如何安装python
- 如何在电脑上安装python-在电脑上安装python的方法
- python是软件吗-python运行环境是什么
- python找工作难吗-为什么我不建议你通过 Python 去找工作?
- python能在excel运行吗-使用PyXLL在Excel中执行Python脚本
- python手册中文版apk-python怎么打包apk
- python程序从哪里开始执行-python如何启动
- python太慢了-Python很慢?不一定哦
- python学成需要多久-小白学python怎么快速入门?多久能完成一个项目?
- python可以做什么开发-Python能做什么,能够开发什么项目?