Spring MyBatis Multi-Data source instance detailed _java

Source: Internet
Author: User
Tags aop

The same project sometimes involves multiple databases, that is, multiple data sources. Multiple data sources can be divided into two different situations:

1 two or more databases are not relevant, independent, in fact this can be developed as two projects. For example, in the game development of a database is a platform database, and other platforms under the game corresponding database;

2 Two or more databases are master-slave relationships, such as MySQL to build a master-master, followed by a number of slave, or the use of MHA built Master-slave replication;

There are probably two ways to build a Spring multi-data source, which you can choose from multiple data sources.

1. Configure multiple data sources directly with the spring configuration file

For example, if there is no correlation for two databases, you can configure multiple data sources directly in the spring configuration file and then configure the transaction separately, as follows:

<context:component-scan base-package= "Net.aazj.service,net.aazj.aop"/> <context:component-scan Base-package= "Net.aazj.aop"/> <!--introduce a property file--> <context:property-placeholder location= "classpath:config/ Db.properties "/> <!--configuration data source--> <bean name=" DataSource "class=" Com.alibaba.druid.pool.DruidDataSource " Init-method= "Init" destroy-method= "close" > <property name= "url" value= "${jdbc_url}"/> <property name= "us Ername "value=" ${jdbc_username} "/> <property name=" password "value=" ${jdbc_password} "/> <!--initializing connection size--&
  Gt <property name= "initialsize" value= "0"/> <!--connection pool maximum number of connections--> <property name= "maxactive" value= "20"/&
  Gt <!--connection Pool maximum idle--> <property name= "Maxidle" value= ""/> <!--connection pool minimum free--> <property name= "Minidl E "value=" 0 "/> <!--get connection maximum wait time--> <property name=" maxwait "value=" 60000 "/> </bean> <bean Id= "Sqlsessionfactory" class= "Org.mybatIs.spring.SqlSessionFactoryBean "> <property name=" dataSource "ref=" DataSource "/> <property-name=" Configlocation "value=" Classpath:config/mybatis-config.xml "/> <property name=" mapperLocations "value="
Classpath*:config/mappers/**/*.xml "/> </bean> <!--Transaction Manager for a single JDBC DataSource-->
  <bean id= "TransactionManager" class= "Org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name= "DataSource" ref= "DataSource"/> </bean> <!--use annotation to define transactions--> &LT;TX: Annotation-driven transaction-manager= "TransactionManager"/> <bean class= " Org.mybatis.spring.mapper.MapperScannerConfigurer "> <property name=" basepackage "value=" Net.aazj.mapper " > <property name= "sqlsessionfactorybeanname" value= "Sqlsessionfactory"/> </bean> <!--enables the US E of the @AspectJ style of Spring AOP--> <aop:aspectj-autoproxy/>

The configuration of the second data source

<bean name= "datasource_2" class= "Com.alibaba.druid.pool.DruidDataSource" init-method= "Init" destroy-method= " Close "> <property name=" url "value=" ${jdbc_url_2} "/> <property name=" username "value=" ${jdbc_username_2} "/> <property name= password" value= "${jdbc_password_2}"/> <!--initialization connection size--> <property name= "Init Ialsize "value=" 0/> <!--connection pool maximum number of connections--> <property name= "maxactive" value= ""/> <!--connection Pool maximum idle- -> <property name= "maxidle" value= "/>" <!--connection pool minimum free--> <property name= "minidle" value= "0"/&gt
  ; <!--get connection Max wait time--> <property name= "maxwait" value= "60000"/> </bean> <bean id= "sqlsessionfactory _slave "class=" Org.mybatis.spring.SqlSessionFactoryBean "> <property name=" dataSource "ref=" datasource_2 " > <property name= "configlocation" value= "Classpath:config/mybatis-config-2.xml"/> <property name= " Mapperlocations "Value=" classpath*:config/mappers2/**/*.xml "/> </bean> <!--Transaction Manager for a single JDBC DataSource--> <bean id=" Transaction Manager_2 "class=" Org.springframework.jdbc.datasource.DataSourceTransactionManager "> <property name=" DataSource "ref=" datasource_2/> </bean> <!--use annotation to define transactions--> <tx:annotation-driven transaction-manager= "transactionmanager_2"/> <bean class= " Org.mybatis.spring.mapper.MapperScannerConfigurer "> <property name=" basepackage "value=" Net.aazj.mapper2 " > <property name= "sqlsessionfactorybeanname" value= "sqlsessionfactory_2"/> </bean>

As shown above, we've configured two DataSource, two sqlsessionfactory, two TransactionManager, and the key is the Mapperscannerconfigurer configuration-- Use the Sqlsessionfactorybeanname property to inject different sqlsessionfactory names, so that the corresponding sqlsessionfactory is injected into the mapper interface for different databases.

It should be noted that this configuration of multiple databases does not support distributed transactions, which means that multiple databases cannot be manipulated in the same transaction. The advantages of this configuration approach are simple, but inflexible. For the Master-slave type of multiple data source configuration is not very adaptable, master-slave multiple data source configuration, need to be particularly flexible, depending on the type of business needs to be carefully configured. For example, for some of the most time-consuming select statements, we want to put them on the slave, and for Update,delete, the operations must be performed only on master, and for some real-time, demanding SELECT statements, We may also need to put it on master--a scene where I go to the mall to buy a weapon, the purchase operation is very determined master, and after the purchase is completed, the need to requery the weapons and gold coins I have, then this query may also need to prevent master execution, but not in Slave up to perform, because the slave may have a delay, we do not want players to find a successful purchase, in the backpack but can not find weapons in the situation.

So for the Master-slave type of multiple data source configuration, depending on the business to make flexible configuration, which select can be placed on the slave, which select cannot be placed on the slave. So the configuration of the data source above is not very adaptable.

2. Configuration of multiple data sources based on Abstractroutingdatasource and AOP

The basic principle is that we define a DataSource class Threadlocalrountingdatasourceto inherit Abstractroutingdatasource, Then inject the master and slave data sources into the Threadlocalrountingdatasource in the configuration file, and then use AOP to flexibly configure where to select the master data source and where to select the slave data source. Here is the code implementation:

1 first define an enum to represent a different data source:

Package net.aazj.enums;
 
/**
 * Data source Category: Master/slave * * Public
enum datasources {
  master, slave
}
 

2) through theadlocal to save each thread to choose which data source of the flag (key):

Package net.aazj.util;
 
Import net.aazj.enums.DataSources;
 
public class Datasourcetypemanager {
  private static final threadlocal<datasources> datasourcetypes = new Threadlocal<datasources> () {
    @Override
    protected datasources initialvalue () {
      return Datasources.master;
    }
  ;
   
  public static datasources get () {return
    datasourcetypes.get ();
  }
   
  public static void Set (DataSources datasourcetype) {
    datasourcetypes.set (datasourcetype);
  }
   
  public static void Reset () {
    datasourcetypes.set (DATASOURCES.MASTER0);
  }
}
 

3) define Threadlocalrountingdatasource, inherit Abstractroutingdatasource:

Package net.aazj.util;
 
Import Org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
public class Threadlocalrountingdatasource extends Abstractroutingdatasource {
  @Override
  protected Object Determinecurrentlookupkey () {return
    datasourcetypemanager.get ();
  }
}

4 inject the master and slave data sources into the Threadlocalrountingdatasource in the configuration file:

<context:component-scan base-package= "Net.aazj.service,net.aazj.aop"/> <context:component-scan Base-package= "Net.aazj.aop"/> <!--introduce a property file--> <context:property-placeholder location= "classpath:config/ Db.properties "/> <!--configuration data source Master--> <bean name= datasourcemaster" class= " Com.alibaba.druid.pool.DruidDataSource "init-method=" Init "destroy-method=" close "> <property name=" url "value = "${jdbc_url}"/> <property name= "username" value= "${jdbc_username}"/> <property name= "password" value= "$ {Jdbc_password} "/> <!--initialization connection size--> <property name=" initialsize "value=" 0 "/> <!--connection pool maximum number of connections-- > <property name= "maxactive" value= "/> <!--connection Pool max idle--> <property name=" Maxidle "value=" 20 "/&
  Gt <!--connection Pool min idle--> <property name= "minidle" value= "0"/> <!--get connection maximum wait time--> <property name= "Maxw AIT "value=" 60000/> </bean> <!--configuration data source slave--> <bean NAme= "Datasourceslave" class= "Com.alibaba.druid.pool.DruidDataSource" init-method= "Init" destroy-method= "Close" > <property name= "url" value= "${jdbc_url_slave}"/> <property name= "username" value= "${jdbc_username_slav" e} "/> <property name=" Password value= "${jdbc_password_slave}"/> <!--initialization connection size--> <property name = "InitialSize" value= "0"/> <!--connection pool maximum number of connections--> <property name= "maxactive" value= "/> <!--connection pool Maximum idle--> <property name= "maxidle" value= "/>" <!--connection pool minimum free--> <property name= "minidle" value= "0 "/> <!--get connection maximum wait time--> <property name=" maxwait "value=" 60000 "/> </bean> <bean id=" Datasou Rce "class=" Net.aazj.util.ThreadLocalRountingDataSource "> <property name=" Defaulttargetdatasource "ref=" 
      Datasourcemaster "/> <property name=" targetdatasources "> <map key-type=" net.aazj.enums.DataSources "> <entry key= "MASTER" value-ref= "DatasouRcemaster "/> <entry key= SLAVE" value-ref= "Datasourceslave"/> <!--Here you can add more DataSource--> & lt;/map> </property> </bean> <bean id= "sqlsessionfactory" class= " Org.mybatis.spring.SqlSessionFactoryBean "> <property name=" dataSource "ref=" DataSource "/> <property Name= "Configlocation" value= "Classpath:config/mybatis-config.xml"/> <property name= "MapperLocations"
Classpath*:config/mappers/**/*.xml "/> </bean> <!--Transaction Manager for a single JDBC DataSource-->
  <bean id= "TransactionManager" class= "Org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name= "DataSource" ref= "DataSource"/> </bean> <!--use annotation to define transactions--> &LT;TX: Annotation-driven transaction-manager= "TransactionManager"/> <bean class= " Org.mybatis.spring.mapper.MapperScannerConfigurer "> <property name=" basepackage "value=" Net.aazj.mapper " > <!--<propErty name= "Sqlsessionfactorybeanname" value= "sqlsessionfactory"/>--> </bean>
   

In the configuration file above, we defined the Datasourcemaster and Datasourceslave two datasource for the master database and the slave database, then injected to <bean id= " DataSource "class=" Net.aazj.util.ThreadLocalRountingDataSource ">, so that our dataSource can be based on key The difference to choose Datasourcemaster and Datasourceslave.

5 Use spring AOP to specify DataSource key, so DataSource will choose Datasourcemaster and Datasourceslave according to key:

 package NET.AAZJ.AOP;
Import net.aazj.enums.DataSources;
 
Import Net.aazj.util.DataSourceTypeManager;
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.stereotype.Component; @Aspect//For AOP @Component//For Auto Scan public class Datasourceinterceptor {@Pointcut ("Execution" (Public * Net . Aazj.service.
  *.getuser (..)) ")
   
  public void Datasourceslave () {};
  @Before ("Datasourceslave ()") public void before (Joinpoint JP) {datasourcetypemanager.set (datasources.slave); 
}
  // ... ...
}

Here we define a Aspect class that we use @Before to match @Pointcut ("Execution" (public * net.aazj.service). *.getuser (..)) ") Called Datasourcetypemanager.set (Datasources.slave) sets the type of the key to Datasources.slave before the method in is called, so DataSource will be based on key= Datasources.slave Choose Datasourceslave this datasource. So the SQL statement for this method is executed on the slave database.

We can continue to expand Datasourceinterceptor this Aspect, in a variety of definitions, to a service for a method to specify the appropriate data source corresponding to the datasource.

This allows us to use the power of Spring AOP to be flexible and configurable.

6) Abstractroutingdatasource Principle Analysis

Threadlocalrountingdatasource inherits the Abstractroutingdatasource, implements its abstract method protected abstraction Object Determinecurrentlook Upkey (); Thus, the routing function of different data sources is realized. We start from the source analysis of the following principle:

Public abstract class Abstractroutingdatasource extends Abstractdatasource implements Initializingbean Abstractroutingdatasource implements Initializingbean so spring will invoke the Initializingbean interface void Afterpropertiesset () when initializing the bean. Throws Exception;  Let's see how Abstractroutingdatasource implements this interface: @Override public void Afterpropertiesset () {if this.targetdatasources
    = = null) {throw new IllegalArgumentException ("Property ' targetdatasources ' is required");
    } this.resolveddatasources = new Hashmap<object, datasource> (This.targetDataSources.size ()); For (Map.entry<object, object> entry:this.targetDataSources.entrySet ()) {Object LookupKey = Resolvespecifie
      Dlookupkey (Entry.getkey ());
      DataSource DataSource = Resolvespecifieddatasource (Entry.getvalue ());
    This.resolvedDataSources.put (LookupKey, DataSource); } if (This.defaulttargetdatasource!= null) {This.resolveddefaultdatasource = Resolvespecifieddatasource (this.d Efaulttargetdatasource);
  }
  }

Targetdatasources is the datasourcemaster and datasourceslave we injected into the XML configuration file. The Afterpropertiesset method is to use injected.

Datasourcemaster and Datasourceslave to construct a hashmap--resolveddatasources. It is convenient to obtain the corresponding datasource from the map according to the key later.

We are looking at the Connection getconnection () throws SQLException in the Abstractdatasource interface; How it is implemented:

@Override public
  Connection getconnection () throws SQLException {return
    determinetargetdatasource (). Getconnection ();
  }


The key is Determinetargetdatasource (), as you can see from the method name, where you should decide which dataSource to use:

Protected DataSource Determinetargetdatasource () {
  assert.notnull (this.resolveddatasources, "DataSource router Not initialized ");
  Object LookupKey = Determinecurrentlookupkey ();
  DataSource DataSource = This.resolvedDataSources.get (LookupKey);
  if (DataSource = = null && (This.lenientfallback | | lookupkey = null)) {
    DataSource = This.resolveddefaultdata Source;
  }
  if (DataSource = = null) {
    throw new IllegalStateException ("cannot determine target DataSource for lookup key [] + look Upkey + "]");
  }
  return dataSource;
}

Object LookupKey = Determinecurrentlookupkey (); This method is implemented in which we get the key value saved in the threadlocal. After the key is obtained, the DataSource of the key corresponds to the resolveddatasources in the map initialized from the Afterpropertiesset (). The key value saved in Threadlocal is set up in an AOP manner before invoking the relevant methods in the service. OK, here we go!

3. Summary

From this article we can experience the power and flexibility of AOP.

Above is the Sping,mybatis data source processing data collation, hope can help the friend who needs to

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.