Using spring to implement read-write separation based on application layer

Source: Internet
Author: User
Tags aop connection pooling
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--&GT
 <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&lt ;

    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

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.