近日來數據庫的壓力較大,因此作了讀寫分離。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