Spring Boot 动态数据源示例(多数据源自动切换)
本文实现案例场景:
某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。
为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在springboot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便。
一配置二使用
1.启动类注册动态数据源
2.配置文件中配置多个数据源
3.在需要的方法上使用注解指定数据源
1、在启动类添加@Import({DynamicDataSourceRegister.class,MProxyTransactionManagementConfiguration.class})
@SpringBootApplication @Import({DynamicDataSourceRegister.class})//注册动态多数据源 publicclassSpringBootSampleApplication{ //省略其他代码 }
2、配置文件配置内容为:(不包括项目中的其他配置,这里只是数据源相关的)
#主数据源,默认的 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 #更多数据源 custom.datasource.names=ds1,ds2 custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1 custom.datasource.ds1.username=root custom.datasource.ds1.password=123456 custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2 custom.datasource.ds2.username=root custom.datasource.ds2.password=123456
3、使用方法
packageorg.springboot.sample.service; importjava.sql.ResultSet; importjava.sql.SQLException; importjava.util.List; importorg.springboot.sample.datasource.TargetDataSource; importorg.springboot.sample.entity.Student; importorg.springboot.sample.mapper.StudentMapper; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.jdbc.core.JdbcTemplate; importorg.springframework.jdbc.core.RowMapper; importorg.springframework.stereotype.Service; /** *StudentService * *@author单红宇(365384722) *@mybloghttp://blog.csdn.net/catoop/ *@create2016年1月12日 */ @Service publicclassStudentService{ @Autowired privateJdbcTemplatejdbcTemplate; //MyBatis的Mapper方法定义接口 @Autowired privateStudentMapperstudentMapper; @TargetDataSource(name="ds2") publicList<Student>likeName(Stringname){ returnstudentMapper.likeName(name); } publicList<Student>likeNameByDefaultDataSource(Stringname){ returnstudentMapper.likeName(name); } /** *不指定数据源使用默认数据源 * *@return *@authorSHANHY *@create2016年1月24日 */ publicList<Student>getList(){ Stringsql="SELECTID,NAME,SCORE_SUM,SCORE_AVG,AGEFROMSTUDENT"; return(List<Student>)jdbcTemplate.query(sql,newRowMapper<Student>(){ @Override publicStudentmapRow(ResultSetrs,introwNum)throwsSQLException{ Studentstu=newStudent(); stu.setId(rs.getInt("ID")); stu.setAge(rs.getInt("AGE")); stu.setName(rs.getString("NAME")); stu.setSumScore(rs.getString("SCORE_SUM")); stu.setAvgScore(rs.getString("SCORE_AVG")); returnstu; } }); } /** *指定数据源 * *@return *@authorSHANHY *@create2016年1月24日 */ @TargetDataSource(name="ds1") publicList<Student>getListByDs1(){ Stringsql="SELECTID,NAME,SCORE_SUM,SCORE_AVG,AGEFROMSTUDENT"; return(List<Student>)jdbcTemplate.query(sql,newRowMapper<Student>(){ @Override publicStudentmapRow(ResultSetrs,introwNum)throwsSQLException{ Studentstu=newStudent(); stu.setId(rs.getInt("ID")); stu.setAge(rs.getInt("AGE")); stu.setName(rs.getString("NAME")); stu.setSumScore(rs.getString("SCORE_SUM")); stu.setAvgScore(rs.getString("SCORE_AVG")); returnstu; } }); } /** *指定数据源 * *@return *@authorSHANHY *@create2016年1月24日 */ @TargetDataSource(name="ds2") publicList<Student>getListByDs2(){ Stringsql="SELECTID,NAME,SCORE_SUM,SCORE_AVG,AGEFROMSTUDENT"; return(List<Student>)jdbcTemplate.query(sql,newRowMapper<Student>(){ @Override publicStudentmapRow(ResultSetrs,introwNum)throwsSQLException{ Studentstu=newStudent(); stu.setId(rs.getInt("ID")); stu.setAge(rs.getInt("AGE")); stu.setName(rs.getString("NAME")); stu.setSumScore(rs.getString("SCORE_SUM")); stu.setAvgScore(rs.getString("SCORE_AVG")); returnstu; } }); } }
要注意的是,在使用MyBatis时,注解@TargetDataSource不能直接在接口类Mapper上使用。
按上面的代码中StudentMapper为接口,代码如下:
packageorg.springboot.sample.mapper; importjava.util.List; importorg.springboot.sample.entity.Student; /** *StudentMapper,映射SQL语句的接口,无逻辑实现 * *@author单红宇(365384722) *@mybloghttp://blog.csdn.net/catoop/ *@create2016年1月20日 */ publicinterfaceStudentMapper{ //注解@TargetDataSource不可以在这里使用 List<Student>likeName(Stringname); StudentgetById(intid); StringgetNameById(intid); }
请将下面几个类放到SpringBoot项目中。
DynamicDataSource.Java
DynamicDataSourceAspect.java
DynamicDataSourceContextHolder.java
DynamicDataSourceRegister.java
TargetDataSource.java
packageorg.springboot.sample.datasource; importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** *动态数据源 * *@author单红宇(365384722) *@create2016年1月22日 */ publicclassDynamicDataSourceextendsAbstractRoutingDataSource{ @Override protectedObjectdetermineCurrentLookupKey(){ returnDynamicDataSourceContextHolder.getDataSourceType(); } }
packageorg.springboot.sample.datasource; importorg.aspectj.lang.JoinPoint; importorg.aspectj.lang.annotation.After; importorg.aspectj.lang.annotation.Aspect; importorg.aspectj.lang.annotation.Before; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.stereotype.Component; /** *切换数据源Advice * *@author单红宇(365384722) *@create2016年1月23日 */ @Aspect @Order(-1)//保证该AOP在@Transactional之前执行 @Component publicclassDynamicDataSourceAspect{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger(DynamicDataSourceAspect.class); @Before("@annotation(ds)") publicvoidchangeDataSource(JoinPointpoint,TargetDataSourceds)throwsThrowable{ StringdsId=ds.name(); if(!DynamicDataSourceContextHolder.containsDataSource(dsId)){ logger.error("数据源[{}]不存在,使用默认数据源>{}",ds.name(),point.getSignature()); }else{ logger.debug("UseDataSource:{}>{}",ds.name(),point.getSignature()); DynamicDataSourceContextHolder.setDataSourceType(ds.name()); } } @After("@annotation(ds)") publicvoidrestoreDataSource(JoinPointpoint,TargetDataSourceds){ logger.debug("RevertDataSource:{}>{}",ds.name(),point.getSignature()); DynamicDataSourceContextHolder.clearDataSourceType(); } }
packageorg.springboot.sample.datasource; importjava.util.ArrayList; importjava.util.List; publicclassDynamicDataSourceContextHolder{ privatestaticfinalThreadLocal<String>contextHolder=newThreadLocal<String>(); publicstaticList<String>dataSourceIds=newArrayList<>(); publicstaticvoidsetDataSourceType(StringdataSourceType){ contextHolder.set(dataSourceType); } publicstaticStringgetDataSourceType(){ returncontextHolder.get(); } publicstaticvoidclearDataSourceType(){ contextHolder.remove(); } /** *判断指定DataSrouce当前是否存在 * *@paramdataSourceId *@return *@authorSHANHY *@create2016年1月24日 */ publicstaticbooleancontainsDataSource(StringdataSourceId){ returndataSourceIds.contains(dataSourceId); } }
packageorg.springboot.sample.datasource; importjava.util.HashMap; importjava.util.Map; importjavax.sql.DataSource; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.beans.MutablePropertyValues; importorg.springframework.beans.PropertyValues; importorg.springframework.beans.factory.support.BeanDefinitionRegistry; importorg.springframework.beans.factory.support.GenericBeanDefinition; importorg.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; importorg.springframework.boot.bind.RelaxedDataBinder; importorg.springframework.boot.bind.RelaxedPropertyResolver; importorg.springframework.context.EnvironmentAware; importorg.springframework.context.annotation.ImportBeanDefinitionRegistrar; importorg.springframework.core.convert.ConversionService; importorg.springframework.core.convert.support.DefaultConversionService; importorg.springframework.core.env.Environment; importorg.springframework.core.type.AnnotationMetadata; /** *动态数据源注册<br/> *启动动态数据源请在启动类中(如SpringBootSampleApplication) *添加@Import(DynamicDataSourceRegister.class) * *@author单红宇(365384722) *@create2016年1月24日 */ publicclassDynamicDataSourceRegister implementsImportBeanDefinitionRegistrar,EnvironmentAware{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger(DynamicDataSourceRegister.class); privateConversionServiceconversionService=newDefaultConversionService(); privatePropertyValuesdataSourcePropertyValues; //如配置文件中未指定数据源类型,使用该默认值 privatestaticfinalObjectDATASOURCE_TYPE_DEFAULT="org.apache.tomcat.jdbc.pool.DataSource"; //privatestaticfinalObjectDATASOURCE_TYPE_DEFAULT= //"com.zaxxer.hikari.HikariDataSource"; //数据源 privateDataSourcedefaultDataSource; privateMap<String,DataSource>customDataSources=newHashMap<>(); @Override publicvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry){ Map<Object,Object>targetDataSources=newHashMap<Object,Object>(); //将主数据源添加到更多数据源中 targetDataSources.put("dataSource",defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); //添加更多数据源 targetDataSources.putAll(customDataSources); for(Stringkey:customDataSources.keySet()){ DynamicDataSourceContextHolder.dataSourceIds.add(key); } //创建DynamicDataSource GenericBeanDefinitionbeanDefinition=newGenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValuesmpv=beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource",defaultDataSource); mpv.addPropertyValue("targetDataSources",targetDataSources); registry.registerBeanDefinition("dataSource",beanDefinition); logger.info("DynamicDataSourceRegistry"); } /** *创建DataSource * *@paramtype *@paramdriverClassName *@paramurl *@paramusername *@parampassword *@return *@authorSHANHY *@create2016年1月24日 */ @SuppressWarnings("unchecked") publicDataSourcebuildDataSource(Map<String,Object>dsMap){ try{ Objecttype=dsMap.get("type"); if(type==null) type=DATASOURCE_TYPE_DEFAULT;//默认DataSource Class<?extendsDataSource>dataSourceType; dataSourceType=(Class<?extendsDataSource>)Class.forName((String)type); StringdriverClassName=dsMap.get("driver-class-name").toString(); Stringurl=dsMap.get("url").toString(); Stringusername=dsMap.get("username").toString(); Stringpassword=dsMap.get("password").toString(); DataSourceBuilderfactory=DataSourceBuilder.create().driverClassName(driverClassName).url(url) .username(username).password(password).type(dataSourceType); returnfactory.build(); }catch(ClassNotFoundExceptione){ e.printStackTrace(); } returnnull; } /** *加载多数据源配置 */ @Override publicvoidsetEnvironment(Environmentenv){ initDefaultDataSource(env); initCustomDataSources(env); } /** *初始化主数据源 * *@authorSHANHY *@create2016年1月24日 */ privatevoidinitDefaultDataSource(Environmentenv){ //读取主数据源 RelaxedPropertyResolverpropertyResolver=newRelaxedPropertyResolver(env,"spring.datasource."); Map<String,Object>dsMap=newHashMap<>(); dsMap.put("type",propertyResolver.getProperty("type")); dsMap.put("driver-class-name",propertyResolver.getProperty("driver-class-name")); dsMap.put("url",propertyResolver.getProperty("url")); dsMap.put("username",propertyResolver.getProperty("username")); dsMap.put("password",propertyResolver.getProperty("password")); defaultDataSource=buildDataSource(dsMap); dataBinder(defaultDataSource,env); } /** *为DataSource绑定更多数据 * *@paramdataSource *@paramenv *@authorSHANHY *@create2016年1月25日 */ privatevoiddataBinder(DataSourcedataSource,Environmentenv){ RelaxedDataBinderdataBinder=newRelaxedDataBinder(dataSource); //dataBinder.setValidator(newLocalValidatorFactory().run(this.applicationContext)); dataBinder.setConversionService(conversionService); dataBinder.setIgnoreNestedProperties(false);//false dataBinder.setIgnoreInvalidFields(false);//false dataBinder.setIgnoreUnknownFields(true);//true if(dataSourcePropertyValues==null){ Map<String,Object>rpr=newRelaxedPropertyResolver(env,"spring.datasource").getSubProperties("."); Map<String,Object>values=newHashMap<>(rpr); //排除已经设置的属性 values.remove("type"); values.remove("driver-class-name"); values.remove("url"); values.remove("username"); values.remove("password"); dataSourcePropertyValues=newMutablePropertyValues(values); } dataBinder.bind(dataSourcePropertyValues); } /** *初始化更多数据源 * *@authorSHANHY *@create2016年1月24日 */ privatevoidinitCustomDataSources(Environmentenv){ //读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源 RelaxedPropertyResolverpropertyResolver=newRelaxedPropertyResolver(env,"custom.datasource."); StringdsPrefixs=propertyResolver.getProperty("names"); for(StringdsPrefix:dsPrefixs.split(",")){//多个数据源 Map<String,Object>dsMap=propertyResolver.getSubProperties(dsPrefix+"."); DataSourceds=buildDataSource(dsMap); customDataSources.put(dsPrefix,ds); dataBinder(ds,env); } } }
packageorg.springboot.sample.datasource; importjava.lang.annotation.Documented; importjava.lang.annotation.ElementType; importjava.lang.annotation.Retention; importjava.lang.annotation.RetentionPolicy; importjava.lang.annotation.Target; /** *在方法上使用,用于指定使用哪个数据源 * *@author单红宇(365384722) *@create2016年1月23日 */ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public@interfaceTargetDataSource{ Stringname(); }
本文代码博主是经过测试后没有问题才发出来共享给大家的。对于连接池参数配置会应用到所有数据源上。
比如配置一个:
spring.datasource.maximum-pool-size=80
那么我们所有的数据源都会自动应用上。
补充:
如果你使用的是SpringMVC,并集成了Shiro,一般按网上的配置你可能是:
<beanclass="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"depends-on="lifecycleBeanPostProcessor"> <propertyname="proxyTargetClass"value="true"/> </bean> <beanclass="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <propertyname="securityManager"ref="securityManager"/> </bean>
那么你请不要这样做,请按下面方法配置:
<!--AOP式方法级权限检查--> <!--不要使用DefaultAdvisorAutoProxyCreator会出现二次代理的问题,这里不详述。markbyshanhy2016-05-15--> <aop:configproxy-target-class="true"/> <!--或者你使用了<aop:aspectj-autoproxyproxy-target-class="true"/>也可以。--> <beanclass="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <propertyname="securityManager"ref="securityManager"/> </bean>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。