1、配置多数据源

增加druid依赖

69ed034763c15c74e3afa27907b72bbb.png

完整pom文件

数据源配置文件

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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

初始化数据源

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/*** 多数据源配置

*@authorzhouliang

* @date 2017年9月20日*/@Configuration

@PropertySource("classpath:multiple-datasource.properties")public classMyBatisConfig {

@AutowiredprivateEnvironment env;

@Autowired MybatisSpringPageInterceptor inteceptor;/*** 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)*/@Beanpublic DataSource operateDataSource() throwsException {

Properties props= newProperties();

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"));returnDruidDataSourceFactory.createDataSource(props);

}

@Beanpublic DataSource routeDataSource() throwsException {

Properties props= newProperties();

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"));returnDruidDataSourceFactory.createDataSource(props);

}/*** @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错

* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)*/@Bean

@PrimarypublicDynamicDataSource dataSource(

@Qualifier("routeDataSource") DataSource routeDataSource,

@Qualifier("operateDataSource") DataSource operateDataSource) {

Map targetDataSources = new HashMap();

targetDataSources.put(DatabaseType.routeDS, routeDataSource);

targetDataSources.put(DatabaseType.operateDS, operateDataSource);

DynamicDataSource dataSource= newDynamicDataSource();

dataSource.setTargetDataSources(targetDataSources);//该方法是AbstractRoutingDataSource的方法

returndataSource;

}/*** 根据数据源创建SqlSessionFactory*/@BeanpublicSqlSessionFactory sqlSessionFactory(DynamicDataSource ds)throwsException {

SqlSessionFactoryBean fb= newSqlSessionFactoryBean();

fb.setDataSource(ds);//指定数据源(这个必须有,否则报错)//下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加

fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));//指定基包

fb.setMapperLocations(newPathMatchingResourcePatternResolver()

.getResources(env.getProperty("mybatis.mapperLocations")));// fb.setPlugins(newInterceptor[]{inteceptor});returnfb.getObject();

}

}

MyBatisConfig.java

@Bean是分别注入两个数据源,

当自动注入多个同样的bean时需要指定一个默认额,所以这里指定了一个默认的抽象数据源@primary

数据源注入之后需要创建SqlSessionFactory

04c55e6e4d08cb24db1d74aed3f6ac22.png

创建抽象数据源类集成AbstractRoutingDataSource

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public class DynamicDataSource extendsAbstractRoutingDataSource {2

3

4 @Override5 protectedObject determineCurrentLookupKey() {6 //TODO Auto-generated method stub

7 returnDatabaseContextHolder.getDatabaseType();8 }9

10 }

DynamicDataSource.java

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public enumDatabaseType {2 routeDS,operateDS3 }

DatabaseType.java

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classDatabaseContextHolder {2 private static final ThreadLocal contextHolder = new ThreadLocal();3

4 public static voidsetDatabaseType(DatabaseType type){5 contextHolder.set(type);6 }7

8 public staticDatabaseType getDatabaseType(){9 returncontextHolder.get();10 }11 public static voidclearDatabaseType(){12 contextHolder.remove();13 }

DatabaseContextHolder.java

上述配置完成之后需要在配置文件中指定mybatis映射的xml文件的位置

3ef0b415c018f8520d39fdd769a48450.png

指定mybatis映射文件之后需要在项目的启动类上排除springboot默认的数据库的配置以及指定mybatis映射文件对应的接口

4af7e6671b942fb7584aeb7a7a9a3201.png

不排除springboot默认的数据库配置类,项目启动的时候会报错启动失败

@MapperScan指定了mybatis映射文件对应的接口所在的目录,这样避免了再每个接口上都加上@Mapper的注解

到此两个数据源已经配置完成

2、数据源的动态切换

这里设置了所有的实体bean都继承了一个父类,父类信息如下

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public class BaseBean implementsSerializable{2 private int pageSize=10;3 private int pageNo=0;4 private longtotalNum;5 privateString totalMappedStatementId;6

7

8 private long shardValue = 0l;9

10 publicBaseBean() {11 super();12 //TODO Auto-generated constructor stub

13 }14

15 public BaseBean(int pageSize, int pageNo, inttotalNum) {16 super();17 this.pageSize =pageSize;18 this.pageNo =pageNo;19 this.totalNum =totalNum;20 }21

22 public intgetPageSize() {23 returnpageSize;24 }25

26 public longgetShardValue() {27 returnshardValue;28 }29

30 public void setShardValue(longshardValue) {31 this.shardValue =shardValue;32 }33

34 publicString getTotalMappedStatementId() {35 returntotalMappedStatementId;36 }37

38 public voidsetTotalMappedStatementId(String totalMappedStatementId) {39 this.totalMappedStatementId =totalMappedStatementId;40 }41

42 public void setPageSize(intpageSize) {43 this.pageSize =pageSize;44 }45

46 public intgetPageNo() {47 returnpageNo;48 }49

50 public void setPageNo(intpageNo) {51 this.pageNo =pageNo;52 }53

54 public longgetTotalNum() {55 returntotalNum;56 }57

58 public void setTotalNum(longtotalNum) {59 this.totalNum =totalNum;60 }61

62 }

BaseBean.java

BaseBean中的shardValue属性是用来指定数据源的,默认值为0,其余属性是分页相关的。

数据源的动态切换是通过spring的切面编程来实现的,通过对mybatis的映射文件对应的接口进行监控,代码如下

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 @Aspect2 @Component3 public classDataSourceAspect {4 Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);5

6 @Before("execution(* zl.mybatis.mapper.*.*(..))")7 public voidsetDataSourcePgKey(JoinPoint point) {8 Object args[] =point.getArgs();9 for(Object obj:args){10 if(obj instanceofBaseBean){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的值来决定使用哪个数据源

87bbadb1341eb183e8f79becf892e9cc.png

完成这一步之后springboot的多数据源动态切换完成了,接下来继续

3、实现自定义mybatis的分页插件

mybatis本身提供了对数据库操作的拦截器,所以实现自定义分页的时候只需要实现这个接口自定义里面的拦截方法。这里我是只拦截了查询的方法

eb58fdbc7ad0a24d4d78560ab3be1baa.png

具体代码如下

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 @Component2 @Intercepts({ @Signature(type = Executor.class, method = "query",3 args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) })4 public class MybatisSpringPageInterceptor implementsInterceptor {5

6 private static final Logger logger = LoggerFactory.getLogger(MybatisSpringPageInterceptor.class.getName());7

8

9 @SuppressWarnings("unused")10 public Object intercept(Invocation arg0) throwsThrowable {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 instanceofBaseBean) {21 model =(BaseBean) parameterObject;22 } else{23 BoundSql newBoundSql =copyFromBoundSql(mappedStatement, boundSql, boundSql.getSql());24 arg0.getArgs()[0] = copyFromMappedStatement(mappedStatement, newBoundSqlSqlSource(newBoundSql));25 returnarg0.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, newBoundSqlSqlSource(newBoundSql));33 returnarg0.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] = newRowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);43 BoundSql newBoundSql =copyFromBoundSql(mappedStatement, boundSql, pagesql);44 arg0.getArgs()[0] = copyFromMappedStatement(mappedStatement, newBoundSqlSqlSource(newBoundSql));45 returnarg0.proceed();46 }47

48 public static class BoundSqlSqlSource implementsSqlSource {49 BoundSql boundSql;50

51 publicBoundSqlSqlSource(BoundSql boundSql) {52 this.boundSql =boundSql;53 }54

55 publicBoundSql getBoundSql(Object parameterObject) {56 returnboundSql;57 }58 }59 private String getLimitSql(String sql, int start, int end) throwsException{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 returnsqlBuffer.toString();67 }68 private void queryTotal(MappedStatement mappedStatement, String replaceSql, Object parameterObject, BoundSql boundSql,BaseBean model) throwsException{69 StringBuffer countSql = newStringBuffer();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 newException(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 protectedMappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {124 Builder builder = newMappedStatement.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 returnnewMs;136 }137

138 /**

139 *140 *@paramps141 *@parammappedStatement142 *@paramboundSql143 *@paramparameterObject144 *@throwsSQLException145 */

146 private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throwsSQLException {147 ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());148 List 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 @Override178 publicObject plugin(Object arg0) {179 return Plugin.wrap(arg0, this);180 }181

182 public voidsetProperties(Properties arg0) {183

184 }185

186

187 privateBoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) {188 BoundSql newBoundSql = newBoundSql(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 returnnewBoundSql;196 }197

198 }

MybatisSpringPageInterceptor.java

插件重写完成之后需要在注入SqlSessionFactory的时候指定这个插件,下面的代码是在MyBatisConfig.java中

88bfecfed44d19ccc07ebe78204e9770.png

现在已经完成了多数据源的动态切换以及自定义mybatis的分页查询了,剩下的测试步骤就不再啰嗦了,排版比较乱大家就将就着看吧!

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐