General Statement
Projects sometimes encounter multiple data sources, that is, in a service to operate two or more than two databases, it is necessary to timely and accurate data source switching.
In general, it is possible to set a default data source to specify a new data source where the service needs to switch the data source, but there are two disadvantages:
(1) Too many ways to specify new data sources will affect code clarity and reduce the readability of the Code;
(2) If the service has turned on the transaction, the data source switchover failed.
In addition to the methods described above, you can also dynamically switch data sources through spring's AOP.
Configuring multiple data sources
Here the case is illustrated in the context of spring and mybatis integration.
1. Configure multiple data sources in Spring-datasource.xml
<!--Firstdatasource--<bean id= "Firstdatasource" class= "Com.alibaba.druid.pool.DruidDataSource" Init-method= "Init" destroy-method= "close" > <property name= "url" value= "${firstdatasource.url}"/> < Property name= "username" value= "${firstdatasource.name}"/> <property name= "password" value= "${ Firstdatasource.password} "/> <property name=" Filters "value=" log4j "/> <property name=" maxActive "value="
${firstdatasource.maxactive} "/> <property name=" initialsize "value=" ${firstdatasource.initialisize} "/> <property name= "maxwait" value= "60000"/> <property name= "Minidle" value= "1"/> <property name= "timeBetw Eenevictionrunsmillis "value=" "/> <property name=" Minevictableidletimemillis "value=" 300000 "/> < Property Name= "Validationquery" value= "select ' X '"/> <property name= "Testwhileidle" value= "true"/> <prope Rty name= "Testonborrow" value= "false"/> <property name= "Testonreturn" value= "false"/> <property name= "poolpreparedstatements" value= "false"/> <property name= " Maxpoolpreparedstatementperconnectionsize "value="/> </bean> <!--seconddatasource---<bean id=
"Seconddatasource" class= "Com.alibaba.druid.pool.DruidDataSource" init-method= "Init" destroy-method= "Close" > <property name= "url" value= "${seconddatasource.url}"/> <property name= "username" value= "${ Seconddatasource.name} "/> <property name=" password "value=" ${seconddatasource.password} "/> <property Name= "Filters" value= "log4j"/> <property name= "maxactive" value= "${seconddatasource.maxactive}"/> < Property Name= "InitialSize" value= "${seconddatasource.initialisize}"/> <property name= "maxWait" value= "60000"
/> <property name= "Minidle" value= "1"/> <property name= "timebetweenevictionrunsmillis" value= "/>" <property name= "Minevictableidletimemillis" value= "300000"/> <property name= "ValidatiOnquery "value=" select ' X ' "/> <property name=" Testwhileidle "value=" true "/> <property name=" TestOnBorrow " Value= "false"/> <property name= "Testonreturn" value= "false"/> <property name= "poolpreparedstatements" va "False"/> <property name= "maxpoolpreparedstatementperconnectionsize" value= "lue="/> </bean>
2. Create the class Databasecontextholder, specifying that the default data source is Firstdatasource
public class Databasecontextholder {
private static final threadlocal<string> Datasource_type = new Threadlocal<string> () {
@Override
protected String initialvalue () {
return ' Firstdatasource ';
}
};
public static void Setdbtype (String datasourcetype) {
datasource_type.set (datasourcetype);
}
public static String Getdbtype () {
return datasource_type.get ();
}
}
3. Create a class Dynamicdatasource that inherits Abstractroutingdatasource and implement the Determinecurrentlookupkey method
Import Org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class Dynamicdatasource extends Abstractroutingdatasource {
@Override
protected Object Determinecurrentlookupkey () {
return databasecontextholder.getdbtype ();
}
}
4. Configure the data source in Spring-datasource.xml, the Class property value is the path to the classes Dynamicdatasource created in the previous step
<bean id= "DataSource" class= "Cn.test.model.common.datasource.DynamicDataSource" >
<property name= " Defaulttargetdatasource "ref=" Firstdatasource "/>
<property name=" targetdatasources ">
<map Key-type= "java.lang.String" >
<entry key= "Firstdatasource" value-ref= "Firstdatasource"/>
< Entry key= "Seconddatasource" value-ref= "Seconddatasource"/>
</map>
</property>
</ Bean>
5. Create the class Datasourceinterceptor and add the AOP configuration to the spring configuration file: <aop:aspectj-autoproxy expose-proxy= "true"/>
Import Org.aspectj.lang.JoinPoint;
Import Org.aspectj.lang.annotation.Aspect;
Import Org.aspectj.lang.annotation.Before;
Import Org.aspectj.lang.annotation.Pointcut;
Import Org.springframework.core.annotation.Order;
Import org.springframework.stereotype.Component;
@Aspect
@Component
@Order (0) public
class Datasourceinterceptor {
@Pointcut ("Execution (* Cn.test.model. service.impl.*firstserviceimpl.* (..)) ")
public void Firstdatasource () {
};
@Pointcut ("Execution (* Cn.test.model). service.impl.*secondserviceimpl.* (..)) ")
public void Seconddatasource () {
};
@Before ("Firstdatasource ()") Public
void Beforefirst (Joinpoint jp) {
Databasecontextholder.setdbtype (" Firstdatasource ");
}
@Before ("Seconddatasource ()") Public
void Beforesecond (Joinpoint jp) {
Databasecontextholder.setdbtype (" Seconddatasource ");
}
}
Configuring Transactions
Configure the transaction on the service's implementation class
<bean id= "TransactionManager" class= "Org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name= "DataSource" ref= "DataSource"/> </bean> <tx:annotation-driven/> <tx:advice id= " Txadvice "transaction-manager=" TransactionManager "> <tx:attributes> <tx:method name=" find* "propagation = "REQUIRED" read-only= "true"/> <tx:method name= "query*" propagation= "REQUIRED" read-only= "true"/> <tx:me Thod name= "list*" propagation= "REQUIRED" read-only= "true"/> <tx:method name= "search*" propagation= "REQUIRED" Read-only= "true"/> <tx:method name= "get*" propagation= "REQUIRED" read-only= "true"/> <tx:method name= "sav e* "propagation=" REQUIRED "/> <tx:method name=" add* "propagation=" REQUIRED "/> <tx:method name=" update* "p ropagation= "REQUIRED"/> <tx:method name= "delete*" propagation= "REQUIRED"/> <tx:method name= "create*" pro pagation= "REQUIRED"/> <tx:method name= "check* "propagation=" REQUIRED "/> <tx:method name=" sync* "propagation=" REQUIRED "/> <tx:method name=" Execu te* "propagation=" REQUIRED "/> <tx:method name=" * "propagation=" SUPPORTS "/> </tx:attributes> </tx:a dvice> <aop:config> <aop:pointcut id= "interceptorpointcuts" expression= "Execution (* Cn.test.model). service.impl.*serviceimpl.* (..)) " /> <aop:advisor advice-ref= "Txadvice" pointcut-ref= "Interceptorpointcuts" order= "1"/> </aop:config>
The switching of data sources and the opening of transactions are implemented through AOP, where the tangency point is configured at the same level as possible (class); It is important to note that the switching of the data source must precede the opening of the transaction.
Precautions
The code takes the form of inter-servic calls. When a method in *firstserviceimpl SaveData () calls the *secondservice method, the slice of the data source is first intercepted to modify the data source, and then the transaction is opened.
The problem that may arise is that when the *secondservice method finishes executing back to *firstserviceimpl, the data source will have an error if *firstserviceimpl then needs to continue firstdatasource.
This is because the slice that modifies the data source is not triggered at this point, and the data source still stays in seconddatasource, even if the method is called *firstserviceimpl itself.
How to solve this problem. You can split the code that needs to be executed into a new service, but it will break the overall structure of the code, and a better way is to extract the subsequent code as an interface to *firstservice and implement it in *firstserviceimpl. It then uses Org.springframework.aop.framework.AopContext to invoke the interface in *firstserviceimpl, such as:
((Datafirstservice) Aopcontext.currentproxy ()). UpdateData ();