Spring Boot Multi-data source auto-switch

Source: Internet
Author: User

To implement a case scenario:
In addition to the need to read and manage data from its own primary database, there is a part of the business involving several other databases, requiring flexibility to specify the database to be manipulated in any way.
In order to use in the simplest way in development, this article is based on annotations and AOP, in the project of Spring boot framework, after adding the code class implemented in this article, we just need to configure the data source to use the annotations directly, which is simple and convenient.
One configuration two use
1. Start the class to register the Dynamic Data source
2. Configure multiple data sources in a configuration file
3. Specify the data source using annotations on the required method
1. Add @Import in Startup class ({Dynamicdatasourceregister.class, mproxytransactionmanagementconfiguration.class})
@SpringBootApplication
@Import ({Dynamicdatasourceregister.class})//Register dynamic multi-data source
public class Springbootsampleapplication {

Omit other code
}

2. configuration file configuration content is:
(excluding other configurations in the project, this is only relevant for data sources)
# main data source, default
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

# More data sources
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. How to use
Package org.springboot.sample.service;

Import Java.sql.ResultSet;
Import java.sql.SQLException;
Import java.util.List;

Import Org.springboot.sample.datasource.TargetDataSource;
Import org.springboot.sample.entity.Student;
Import Org.springboot.sample.mapper.StudentMapper;
Import org.springframework.beans.factory.annotation.Autowired;
Import Org.springframework.jdbc.core.JdbcTemplate;
Import Org.springframework.jdbc.core.RowMapper;
Import Org.springframework.stereotype.Service;

/**
* Student Service
*
*/
@Service
public class Studentservice {

@Autowired
Private JdbcTemplate JdbcTemplate;

MyBatis The Mapper method defines the interface
@Autowired
Private Studentmapper Studentmapper;

@TargetDataSource (name= "DS2")
Public list<student> likename (String name) {
return Studentmapper.likename (name);
}

Public list<student> Likenamebydefaultdatasource (String name) {
return Studentmapper.likename (name);
}

/**
* Do not specify a data source using the default data source
*
*/
Public list<student> getList () {
String sql = "Select Id,name,score_sum,score_avg, age from STUDENT";
Return (list<student>) jdbctemplate.query (SQL, new rowmapper<student> () {

@Override
Public Student Maprow (ResultSet rs, int rowNum) throws SQLException {
Student stu = new Student ();
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"));
return Stu;
}

});
}

/**
* Specify data source
*
* @return
*/
@TargetDataSource (name= "DS1")
Public list<student> getListByDs1 () {
String sql = "Select Id,name,score_sum,score_avg, age from STUDENT";
Return (list<student>) jdbctemplate.query (SQL, new rowmapper<student> () {

@Override
Public Student Maprow (ResultSet rs, int rowNum) throws SQLException {
Student stu = new Student ();
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"));
return Stu;
}

});
}

/**
* Specify data source
*
*/
@TargetDataSource (name= "DS2")
Public list<student> getListByDs2 () {
String sql = "Select Id,name,score_sum,score_avg, age from STUDENT";
Return (list<student>) jdbctemplate.query (SQL, new rowmapper<student> () {

@Override
Public Student Maprow (ResultSet rs, int rowNum) throws SQLException {
Student stu = new Student ();
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"));
return Stu;
}

});
}
}

Note that when using MyBatis, annotations @targetdatasource cannot be used directly on the interface class mapper.
Studentmapper as the interface in the code above, the code is as follows:
Package org.springboot.sample.mapper;

Import java.util.List;

Import org.springboot.sample.entity.Student;

/**
* Studentmapper, interface for mapping SQL statements, no logical implementation
*
*/
Public interface Studentmapper {

Note @TargetDataSource can not be used here
List<student> likename (String name);

Student getById (int id);

String Getnamebyid (int id);

}
Please place the following classes in the Spring boot project.
Dynamicdatasource.java
Dynamicdatasourceaspect.java
Dynamicdatasourcecontextholder.java
Dynamicdatasourceregister.java
Targetdatasource.java
Package Org.springboot.sample.datasource;

Import Org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* Dynamic Data source
*
*/
public class Dynamicdatasource extends Abstractroutingdatasource {

@Override
Protected Object Determinecurrentlookupkey () {
return Dynamicdatasourcecontextholder.getdatasourcetype ();
}

}

Package Org.springboot.sample.datasource;

Import Org.aspectj.lang.JoinPoint;
Import Org.aspectj.lang.annotation.After;
Import Org.aspectj.lang.annotation.Aspect;
Import Org.aspectj.lang.annotation.Before;
Import Org.slf4j.Logger;
Import Org.slf4j.LoggerFactory;
Import org.springframework.stereotype.Component;

/**
* Toggle Data Source Advice
*
*/
@Aspect
@Order (-1)//Ensure that the AOP is performed before @transactional
@Component
public class Dynamicdatasourceaspect {

private static final Logger Logger = Loggerfactory.getlogger (Dynamicdatasourceaspect.class);

@Before ("@annotation (ds)")
public void Changedatasource (Joinpoint point, Targetdatasource ds) throws Throwable {
String Dsid = Ds.name ();
if (! Dynamicdatasourcecontextholder.containsdatasource (DSID)) {
Logger.error ("Data source [{}] does not exist, using default data source > {}", Ds.name (), point.getsignature ());
} else {
Logger.debug ("Use DataSource: {} > {}", Ds.name (), point.getsignature ());
Dynamicdatasourcecontextholder.setdatasourcetype (Ds.name ());
}
}

@After ("@annotation (ds)")
public void Restoredatasource (Joinpoint point, Targetdatasource DS) {
Logger.debug ("Revert DataSource: {} > {}", Ds.name (), point.getsignature ());
Dynamicdatasourcecontextholder.cleardatasourcetype ();
}

}
Package Org.springboot.sample.datasource;

Import java.util.ArrayList;
Import java.util.List;

public class Dynamicdatasourcecontextholder {

private static final threadlocal<string> Contextholder = new threadlocal<string> ();
public static list<string> Datasourceids = new arraylist<> ();

public static void Setdatasourcetype (String datasourcetype) {
Contextholder.set (DatasourceType);
}

public static String Getdatasourcetype () {
return Contextholder.get ();
}

public static void Cleardatasourcetype () {
Contextholder.remove ();
}

/**
* Determines whether the specified Datasrouce currently exists
*
* @param dataSourceId
* @return
* @author Shanhy
* @create January 24, 2016
*/
public static Boolean Containsdatasource (String dataSourceId) {
Return Datasourceids.contains (DATASOURCEID);
}
}
Package Org.springboot.sample.datasource;

Import Java.util.HashMap;
Import Java.util.Map;

Import Javax.sql.DataSource;

Import Org.slf4j.Logger;
Import org.slf4j.LoggerFactory;
Import org.springframework.beans.MutablePropertyValues;
Import org.springframework.beans.PropertyValues;
Import Org.springframework.beans.factory.support.BeanDefinitionRegistry;
Import org.springframework.beans.factory.support.GenericBeanDefinition;
Import Org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
Import Org.springframework.boot.bind.RelaxedDataBinder;
Import Org.springframework.boot.bind.RelaxedPropertyResolver;
Import Org.springframework.context.EnvironmentAware;
Import Org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
Import Org.springframework.core.convert.ConversionService;
Import Org.springframework.core.convert.support.DefaultConversionService;
Import org.springframework.core.env.Environment;
Import Org.springframework.core.type.AnnotationMetadata;

/**
* Dynamic Data Source Registration <br/>
* Start a Dynamic Data source in the startup class (e.g., springbootsampleapplication)
* Add @Import (Dynamicdatasourceregister.class)
*
*/
public class Dynamicdatasourceregister
Implements Importbeandefinitionregistrar, Environmentaware {

private static final Logger Logger = Loggerfactory.getlogger (Dynamicdatasourceregister.class);

Private Conversionservice Conversionservice = new Defaultconversionservice ();
Private Propertyvalues datasourcepropertyvalues;

Use this default value if the data source type is not specified in the configuration file
private static final Object Datasource_type_default = "Org.apache.tomcat.jdbc.pool.DataSource";
private static final Object Datasource_type_default =
"Com.zaxxer.hikari.HikariDataSource";

Data source
Private DataSource Defaultdatasource;
Private map<string, datasource> customdatasources = new hashmap<> ();

@Override
public void registerbeandefinitions (Annotationmetadata importingclassmetadata, Beandefinitionregistry registry) {
Map<object, object> targetdatasources = new Hashmap<object, Object> ( );
//Add the main data source to more data sources
Targetdatasources.put ("DataSource", Defaultdatasource);
DynamicDataSourceContextHolder.dataSourceIds.add ("DataSource");
//Add more data Sources
Targetdatasources.putall (customdatasources);
for (String Key:customDataSources.keySet ()) {
DynamicDataSourceContextHolder.dataSourceIds.add (key);
}

Create Dynamicdatasource
Genericbeandefinition beandefinition = new Genericbeandefinition ();
Beandefinition.setbeanclass (Dynamicdatasource.class);
Beandefinition.setsynthetic (TRUE);
Mutablepropertyvalues MPV = beandefinition.getpropertyvalues ();
Mpv.addpropertyvalue ("Defaulttargetdatasource", Defaultdatasource);
Mpv.addpropertyvalue ("Targetdatasources", targetdatasources);
Registry.registerbeandefinition ("DataSource", beandefinition);

Logger.info ("Dynamic DataSource Registry");
}

/**
* Create DataSource
*
* @param type
* @param driverclassname
* @param URL
* @param username
* @param password
* @return
*/
@SuppressWarnings ("Unchecked")
Public DataSource Builddatasource (map<string, object> dsMap) {
try {
Object type = Dsmap.get ("type");
if (type = = NULL)
Type = datasource_type_default;//default DATASOURCE

class<? Extends datasource> DatasourceType;
DatasourceType = (class<? extends datasource>) Class.forName ((String) type);

String driverclassname = Dsmap.get ("Driver-class-name"). ToString ();
String url = dsmap.get ("url"). toString ();
String username = dsmap.get ("username"). toString ();
String Password = dsmap.get ("password"). toString ();

Datasourcebuilder factory = Datasourcebuilder.create (). Driverclassname (driverclassname). URL (URL)
. Username (username). password (password). Type (DatasourceType);
return Factory.build ();
} catch (ClassNotFoundException e) {
E.printstacktrace ();
}
return null;
}

/**
* Load multi-data source configuration
*/
@Override
public void Setenvironment (environment env) {
Initdefaultdatasource (env);
Initcustomdatasources (env);
}

/**
* Initialize primary data source
*
*/
private void Initdefaultdatasource (Environment env) {
Reading the main data source
Relaxedpropertyresolver propertyresolver = new Relaxedpropertyresolver (env, "spring.datasource.");
map<string, object> dsMap = new hashmap<> ();
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);
}

/**
* Bind more data for DataSource
*
* @param dataSource
* @param env
*/
private void DataBinder (DataSource DataSource, Environment env) {
Relaxeddatabinder DataBinder = new Relaxeddatabinder (DataSource);
Databinder.setvalidator (New Localvalidatorfactory (). 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 = new Relaxedpropertyresolver (env, "Spring.datasource"). Getsubproperties (".");
map<string, object> values = new hashmap<> (RPR);
Exclude properties that have been set
Values.remove ("type");
Values.remove ("Driver-class-name");
Values.remove ("url");
Values.remove ("username");
Values.remove ("password");
Datasourcepropertyvalues = new Mutablepropertyvalues (values);
}
Databinder.bind (datasourcepropertyvalues);
}

/**
* Initialization of more data sources
*
*/
private void Initcustomdatasources (Environment env) {
Read the configuration file for more data sources, or you can read the database from Defaultdatasource to get more data sources
Relaxedpropertyresolver propertyresolver = new Relaxedpropertyresolver (env, "custom.datasource.");
String Dsprefixs = propertyresolver.getproperty ("names");
For (String dsPrefix:dsPrefixs.split (",")) {//Multiple data sources
map<string, object> dsMap = propertyresolver.getsubproperties (Dsprefix + ".");
DataSource ds = Builddatasource (DSMAP);
Customdatasources.put (Dsprefix, DS);
DataBinder (DS, env);
}
}

}
Package Org.springboot.sample.datasource;

Import java.lang.annotation.Documented;
Import Java.lang.annotation.ElementType;
Import java.lang.annotation.Retention;
Import Java.lang.annotation.RetentionPolicy;
Import Java.lang.annotation.Target;

/**
* Used on methods to specify which data source to use
*
*/
@Target ({elementtype.method, elementtype.type})
@Retention (Retentionpolicy.runtime)
@Documented
Public @interface Targetdatasource {
String name ();
}
This article code Bo Master is after testing no problem to send to share to everyone. The configuration for connection pool parameters is applied to all data sources.
For example, configure one:
Spring.datasource.maximum-pool-size=80
1

1
Then all of our data sources will be automatically applied.
Add:
If you are using SPRINGMVC, and have integrated Shiro, general on-line configuration you may be:
<bean class= "Org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on= " Lifecyclebeanpostprocessor ">
<property name= "Proxytargetclass" value= "true"/>
</bean>

<bean class= "Org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor" >
<property name= "SecurityManager" ref= "SecurityManager"/>
</bean>

So please do not do this, please configure it as follows:
<!--AOP Method-level permissions Check-
<!--do not use Defaultadvisorautoproxycreator will appear two times the problem of the agent, not detailed here. Mark by Shanhy 2016-05-15--
<aop:config proxy-target-class= "true"/>
<!--or you have used <aop:aspectj-autoproxy proxy-target-class= "true"/> can also. -

<bean class= "Org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor" >
<property name= "SecurityManager" ref= "SecurityManager"/>
</bean>

Spring Boot Multi-data source auto-switch

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.