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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。