background
We generally apply to the database are "read more write less", also said that the database read data pressure is relatively large, there is a thought that the use of database cluster scheme,
One is the main library, responsible for writing data, we call it: Write library;
Others are from the library, responsible for reading the data, we call it: Read the library;
So, the requirement for us is:
1, read the library and write the data consistent with the library;
2, write data must be written to the library;
3, read the data must read the library ;
There are two solutions to the separation of Read and write: Application layer solution and middleware solution. Application Layer Resolution
Advantages:
1, multiple data source switching is convenient, automatically completed by the program;
2, do not need to introduce middleware;
3, theoretically support any database;
Disadvantages:
1, completed by the programmer, the operation of the dimension is not involved;
2, can not do dynamic increase of data source, middleware solution
Advantages:
1, the source program does not need to make any changes can be realized read and write separation;
2, dynamic Add data source does not need to restart the program;
Disadvantages:
1, the program relies on the middleware, will cause the switch database becomes difficult;
2, by the middleware to do a transit agent, performance has declined;
Related Middleware products use:
Mysql-proxy:http://hi.baidu.com/geshuai2008/item/0ded5389c685645f850fab07
Amoeba for mysql:http://www.iteye.com/topic/188598 and http://www.iteye.com/topic/1113437 using spring based on application-tier implementations principle
Before entering the service, use AOP to make judgments, whether using a write library or reading a library, judging by the method name can be judged, such as query, find, get and so on in the beginning of the daytime library, the other go to write library. Code Implementation
Dynamicdatasource
Import Org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* Defines a dynamic data source that implements the Abstractroutingdatasource provided through the integration spring, and only the Determinecurrentlookupkey method can be implemented
*
Because Dynamicdatasource is a single case, thread is unsafe, so using threadlocal to ensure thread safety, completed by Dynamicdatasourceholder. * *
* @author */public
class Dynamicdatasource extends abstractroutingdatasource{
@ Override
protected Object Determinecurrentlookupkey () {
//using Dynamicdatasourceholder to ensure thread safety, And get the data source key return
Dynamicdatasourceholder.getdatasourcekey () in the current thread
;
Dynamicdatasourceholder
/**
* * *
using threadlocal technology to record key
*
* @author * */Public
class for the data source in the current thread Dynamicdatasourceholder {
//write library corresponds to the data source key
private static final String master = "Master";
Read library corresponding data source key
private static final String SLAVE = "SLAVE";
Use ThreadLocal to record the current thread's data source key
private static final threadlocal<string> holder = new Threadlocal<string > ();
/**
* Set data source key
* @param key */public
static void Putdatasourcekey (String key) {
Holder.set ( key);
}
/**
* Get data source key
* @return/public
static String Getdatasourcekey () {return
holder.get ();
/**
* Tag Write library */public
static void Markmaster () {
putdatasourcekey (MASTER);
}
/**
* Tag Read library/public
static void Markslave () {
putdatasourcekey (SLAVE);
}
}
Datasourceaspect
Import Org.apache.commons.lang3.StringUtils;
Import Org.aspectj.lang.JoinPoint;
/**
* Defines the AOP section of the data source, judging by the method name of the service, whether it should be a daytime library or a library * * @author * * */public
class Datasourceaspect {
/**
* Performs
* @param point cut
object
/public
void before before entering the service method ( Joinpoint point) {
//get to the currently executing method name
String methodname = Point.getsignature (). GetName ();
if (Isslave (methodname)) {
//is marked as Read library
dynamicdatasourceholder.markslave ();
} else {
//marked as Write library
Dynamicdatasourceholder.markmaster ();
}
/**
* To determine whether to read library
* *
@param methodname
* @return
/Private Boolean Isslave (String MethodName) {
//method name to go from the library return
Stringutils.startswithany (methodname, "Query", "find") with query, find, and get Start method name , "get");
}
Configuration Implementation
jdbc.properties Configure 2 data sources
Jdbc.master.driver=com.mysql.jdbc.driver
jdbc.master.url=jdbc:mysql://127.0.0.1:3306/mybatis_1128? Useunicode=true&characterencoding=utf8&autoreconnect=true&allowmultiqueries=true
Jdbc.master.username=root
jdbc.master.password=123456
jdbc.slave01.driver=com.mysql.jdbc.driver
jdbc.slave01.url=jdbc:mysql://127.0.0.1:3307/mybatis_1128?useunicode=true&characterencoding=utf8& Autoreconnect=true&allowmultiqueries=true
jdbc.slave01.username=root
jdbc.slave01.password=123456
Defining connection Pooling
<!--Configure connection pooling--> <bean id= "Masterdatasource" class= "Com.jolbox.bonecp.BoneCPDataSource" destroy-method=
"Close" > <!--database driver--> <property name= "Driverclass" value= "${jdbc.master.driver}"/> <!--the corresponding driver jdbcurl--> <property name= "Jdbcurl" value= "${jdbc.master.url}"/> < users of the!--database
Name--> <property name= "username" value= "${jdbc.master.username}"/> <!--database Password--> <property name= "Password" value= "${jdbc.master.password}"/> <!--Check the time between idle connections in the database connection pool, in minutes, by default: 240, if you want to cancel Set to 0--> <property name= "idleconnectiontestperiod" value= "/>" <!--unused links in the connection pool maximum surviving time, in units of minutes, by default : 60, if you want to live forever set to 0--> <property name= "idlemaxage" value= "/>" <!--the maximum number of connections per partition--> & Lt;property name= "maxconnectionsperpartition" value= "/> <!--the minimum number of connections per partition--> <property Nam E= "MinconnectionsperpartItion "value=" 5 "/> </bean> <!--Configure connection pooling--> <bean id=" Slave01datasource "class=" com.jolbox.b ONECP. Bonecpdatasource "destroy-method= Close" > <!--database driver--> <property name= "Driverclass" Value= "${jdbc.slave01.driver}"/> <!--the corresponding drive Jdbcurl--> <property name= "Jdbcurl" value= . Slave01.url} "/> <!--database username--> <property name=" username "value=" ${jdbc.slave01.username} " /> <!--database password--> <property name= "password" value= "${jdbc.slave01.password}"/> t;!
--Check the interval of free connections in the database connection pool, in points, default: 240, if you want to cancel then set to 0--> <property name= "idleconnectiontestperiod" value= "/>"
<!--unused links in the connection pool maximum survival time, unit is divided, default value: 60, if you want to live forever set to 0--> <property name= "idlemaxage" value= "/>" <!--maximum number of connections per partition--> <property name= "maxconnectionsperpartition" value= "/>" <!-- Minimum number of connections per partition-->
<property name= "Minconnectionsperpartition" value= "5"/> </bean>
define DataSource
<!--define a data source, use your own data source-->
<bean id= "DataSource" class= "Cn.itcast.usermanage.spring.DynamicDataSource" >
<!--set up multiple data sources-->
<property name= "targetdatasources" >
<map key-type= " Java.lang.String ">
<!--This key needs to be consistent with the key in the program-->
<entry key=" master "value-ref=" Masterdatasource " />
<entry key= "slave" value-ref= "Slave01datasource"/>
</map>
</property>
<!--set the default data source, where the default-->
<property name= "Defaulttargetdatasource" ref= "
masterdatasource"/> </bean>
Configuring transaction management and dynamically switching data source slices
1. Define the transaction manager
<!--define the transaction manager-->
<bean id= "TransactionManager"
class= " Org.springframework.jdbc.datasource.DataSourceTransactionManager ">
<property name=" DataSource "ref=" DataSource "/>
</bean>
2. Define transaction strategy
<!--define transaction policy-->
<tx:advice id= "Txadvice" transaction-manager= "TransactionManager" >
<tx: Attributes>
<!--Define query methods are read-only-->
<tx:method name= "query*" read-only= "true"/>
<tx: Method Name= "find*" read-only= "true"/> <tx:method name= "get*" read-only=
"true"/>
<!--Main Library to perform operations. Transactional propagation behavior is defined as the default behavior-->
<tx:method name= "save*" propagation= "REQUIRED"/> <tx:method name= "update*"
propagation= "REQUIRED"/> <tx:method name= "delete*" propagation= "REQUIRED"/>
<!-- Other methods use the default transaction policy-->
<tx:method name= "*"/>
</tx:attributes>
</tx:advice>
3. Define the Slice
<!--define AOP slicing processor-->
<bean class= "Cn.itcast.usermanage.spring.DataSourceAspect" id= "Datasourceaspect"/ >
<aop:config>
<!--define slices, all of the service's methods-->
<aop:pointcut id= "Txpointcut" expression= "Execution (* xx.xxx.xxxxxxx.service.*.* (..))"/>
<!--apply transaction policies to service section-->
<AOP: Advisor advice-ref= "Txadvice" pointcut-ref= "Txpointcut"/>
<!--to apply the slice to a custom slice processor,-9999 to ensure the highest priority of this slice is performed-->
<aop:aspect ref= "Datasourceaspect" order= " -9999" >
<aop:before method= "before" Txpointcut "/>
</aop:aspect>
</aop:config>
improving Section 1: Improving the slice implementation, using transaction policy rules to match
The previous implementation we will match through the method name, rather than using the definition in the transaction policy, we use the rules in the transaction management policy to match.
Improved Configuration
<!--define AOP slicing processor-->
<bean class= "Cn.itcast.usermanage.spring.DataSourceAspect" id= "Datasourceaspect" >
<!--Specify a transaction policy-->
<property name= "Txadvice" ref= "Txadvice"/>
<!--Specify the prefix of the Slave method (not required)-- >
<property name= "Slavemethodstart" value= "Query,find,get"/>
</bean>
the implementation after the improvement
Import Java.lang.reflect.Field;
Import java.util.ArrayList;
Import java.util.List;
Import Java.util.Map;
Import Org.apache.commons.lang3.StringUtils;
Import Org.aspectj.lang.JoinPoint;
Import Org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
Import Org.springframework.transaction.interceptor.TransactionAttribute;
Import Org.springframework.transaction.interceptor.TransactionAttributeSource;
Import Org.springframework.transaction.interceptor.TransactionInterceptor;
Import Org.springframework.util.PatternMatchUtils;
Import Org.springframework.util.ReflectionUtils;
/** * Defines an AOP slice of a data source that controls the use of master or slave.
* * If a transaction policy is configured in transaction management, the ReadOnly method marked in the configured transaction policy is slave and other uses master.
* * If no policy is configured for transaction management, use the principle of method name matching, using slave in query, find, and get opening methods, and others with master. * * * @author * */public class Datasourceaspect {private list<string> Slavemethodpattern = new Arraylist< ;
String> (); private static final string[] Defaultslavemethodstart = new string[]{"Query"," Find "," get "};
Private string[] Slavemethodstart;
/** * Read policy in transaction management * * @param txadvice * @throws Exception/@SuppressWarnings ("unchecked")
public void Settxadvice (Transactioninterceptor txadvice) throws Exception {if (Txadvice = null) {
No transaction management policy is configured to return; ///from Txadvice get to policy configuration information Transactionattributesource Transactionattributesource = txadvice.gettransactionattr
Ibutesource (); if (!) (
Transactionattributesource instanceof Namematchtransactionattributesource)) {return; ///Using reflection technology to get the Namemap property value Namematchtransactionattributesource in the Namematchtransactionattributesource object matchTr
Ansactionattributesource = (namematchtransactionattributesource) Transactionattributesource;
Field Namemapfield = Reflectionutils.findfield (Namematchtransactionattributesource.class, "NameMap"); Namemapfield.setaccessible (TRUE); Set this field to access//get NAmemap value map<string, transactionattribute> Map = (map<string, transactionattribute>) NameMapField.get (
Matchtransactionattributesource); Traversal Namemap for (map.entry<string, transactionattribute> entry:map.entrySet ()) {if (!entry.ge
TValue (). IsReadOnly ()) {//judgment after defining the ReadOnly strategy before adding to Slavemethodpattern continue;
} slavemethodpattern.add (Entry.getkey ()); }/** * Execute * @param point section Object/public void before before entering service method (Joinpoint point)
{//Get to the currently executing method name String methodname = Point.getsignature (). GetName ();
Boolean isslave = false; if (Slavemethodpattern.isempty ()) {//Current spring container does not have a transaction policy configured, Isslave = Isslave (methodname) with method name matching
; else {//Use policy rules to match for (String Mappedname:slavemethodpattern) {if IsMatch (Me
Thodname, Mappedname)) { Isslave = true;
Break
}} if (Isslave) {//is marked as Read library Dynamicdatasourceholder.markslave ();
else {//marked as Write library Dynamicdatasourceholder.markmaster (); }/** * To determine if Read library * * @param methodname * @return/private Boolean Isslave (String MethodName) {//Method name takes the method name that starts with query, find, and get from the library return Stringutils.startswithany (methodname, Getslavemethods
Tart ());
/** * Wildcard Match * * return if the given method name matches the mapped name. * <p> * The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, as OK as direct