spring多數據源的動態加載

近日來數據庫的壓力較大,因此作了讀寫分離。java

爲了儘可能少的修改已有代碼(數據庫主從庫仍是要作的),在spring的配置中增長了數據源,使用註解的方式,增長一個切面,從controller層即肯定訪問的數據源。mysql

一、在主配置文件中加入下面這行spring

<import resource="spring-mysql.xml"/>

spring的配置 spring-mysql.xmlsql

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	                           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	                           http://www.springframework.org/schema/tx
	                           http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- mysql 配置 -->
    <!-- datasource 1 -->
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close">
        <!-- 基本屬性 url、user、password -->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="20"/>
        <property name="minIdle" value="20"/>
        <property name="maxActive" value="100"/>

        <!-- 配置獲取鏈接等待超時的時間 -->
        <property name="maxWait" value="60000"/>

        <!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 -->
        <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"/>

        <!-- 打開PSCache,而且指定每一個鏈接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>

        <!-- 配置監控統計攔截的filters -->
        <property name="filters" value="stat"/>
    </bean>

    <!-- datasource 2 -->
    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本屬性 url、user、password -->
        <property name="url" value="${readDb.url}"/>
        <property name="username" value="${readDb.username}"/>
        <property name="password" value="${readDb.password}"/>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="20"/>
        <property name="minIdle" value="20"/>
        <property name="maxActive" value="100"/>

        <!-- 配置獲取鏈接等待超時的時間 -->
        <property name="maxWait" value="60000"/>

        <!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 -->
        <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"/>

        <!-- 打開PSCache,而且指定每一個鏈接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>

        <!-- 配置監控統計攔截的filters -->
        <property name="filters" value="stat"/>
    </bean>

    <!-- 編寫spring 配置文件的配置多數源映射關係 -->
    <bean class="hasoffer.core.persistence.dbm.osql.datasource.DynamicDataSource" id="dataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry value-ref="masterDataSource" key="master"></entry>
                <entry value-ref="slaveDataSource" key="slave"></entry>
                <!-- key -->
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource">
        </property>
    </bean>

    <!-- sessionFactory -->
    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="hasoffer.core.persistence.po"/>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.MySQLDialect
                hibernate.show_sql=false
                hibernate.hbm2ddl.auto=none
                hibernate.connection.autocommit=false
                hibernate.jdbc.batch_size=30
            </value>
        </property>
    </bean>

    <bean name="org.springframework.orm.hibernate4.HibernateTemplate"
          class="org.springframework.orm.hibernate4.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <!-- 事務管理器配置, Hibernate單數據源事務 -->
    <bean id="defaultTransactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <tx:annotation-driven transaction-manager="defaultTransactionManager" proxy-target-class="true"/>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 動態選擇數據源 面向切面 -->
    <bean id="dataSourceAspect" class="hasoffer.core.persistence.dbm.osql.datasource.DataSourceAspect"/>

    <aop:config>
        <!--<aop:pointcut id="transactionPointCut" expression="execution(* ppp.core.*.*.*(..))"/>-->
        <aop:pointcut id="transactionPointCut1" expression="execution(* ppp.admin.controller.*.*(..))"/>
        <aop:pointcut id="transactionPointCut2" expression="execution(* ppp.api.controller.*.*(..))"/>

        <aop:advisor pointcut-ref="transactionPointCut1" advice-ref="dataSourceAspect" order="1"/>
        <aop:advisor pointcut-ref="transactionPointCut2" advice-ref="dataSourceAspect" order="2"/>
        <!--<aop:advisor pointcut-ref="transactionPointCut" advice-ref="dataSourceExchange" order="1"/>-->
    </aop:config>
</beans>

二、其中爲了動態加載數據源,使用了數據庫

DataSourceAspect 和 DynamicDataSource

兩個類 ,一個是AOP切面編程,一個是返回當前數據源express

DataSourceAspect.java編程

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 對應 key
        //  <entry value-ref="readDataSource" key="read"></entry> <!-- key -->
//        return "read";
//        return "master";
        return DataSourceContextHolder.getDataSourceType();
    }
}

DataSourceAspect.javaapi

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class DataSourceAspect implements MethodBeforeAdvice, AfterReturningAdvice {

    private Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    @Override
    public void afterReturning(Object returnValue, Method method,
                               Object[] args, Object target) throws Throwable {
        DataSource ds = method.getAnnotation(DataSource.class);

        if (ds == null) {
            return;
        }

        DataSourceContextHolder.clearDataSourceType();
    }

    @Override
    public void before(Method method, Object[] args, Object target)
            throws Throwable {

        DataSource ds = method.getAnnotation(DataSource.class);

        if (ds == null) {
            return;
        }

        logger.debug(String.format("method : %s/%s, datasource : %s", method.getDeclaringClass().getName(), method.getName(), ds.value()));

        if (ds.value() == DataSourceType.Slave) {
            DataSourceContextHolder.setDataSourceType("slave");
        } else {
            DataSourceContextHolder.setDataSourceType("master");
        }
    }

}

三、DataSourceContextHolder.java 線程變量session

public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    /**
     * @param
     * @return String
     * @throws
     * @Description: 獲取數據源類型
     */
    public static String getDataSourceType() {
        return contextHolder.get();
    }

    /**
     * @param dataSourceType 數據庫類型
     * @return void
     * @throws
     * @Description: 設置數據源類型
     */
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    /**
     * @param
     * @return void
     * @throws
     * @Description: 清除數據源類型
     */
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

四、註解類ide

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {

    DataSourceType value() default DataSourceType.Master;

}
public enum DataSourceType {
    Master,
    Slave
}

五、在controller的方法加上註解

要求數據源使用從庫

@DataSource(value = DataSourceType.Slave)

若是使用主庫進行讀寫,則要 @DataSource(value = DataSourceType.Master)

 

六、最後

配置完後須要多測試,理解其中執行順序,什麼時候設置數據源,什麼時候JoinPoint等。

 

參考:

http://blog.csdn.net/rj042/article/details/21654627

相關文章
相關標籤/搜索