1. 程式人生 > >java實現mysql數據庫讀寫分離之定義多數據源方式

java實現mysql數據庫讀寫分離之定義多數據源方式

修改 protect frame auto ret 更新數據 logs cannot initial

該示例是基於spring提供的AbstractRoutingDataSource,實現了一個動態數據源的功能,在spring配置中定義多個數據庫分為主、從數據庫,實現效果為當進行保存和修改記錄時則對主表操作,查詢則對從表進行操作,從而實現對數據庫表的讀寫分離。這樣做有利於提高網站的性能,特別是在數據庫這一層。因為在實際的應用中,數據庫都是讀多寫少(讀取數據的頻率高,更新數據的頻率相對較少),而讀取數據通常耗時比較長,占用數據庫服務器的CPU較多,從而影響用戶體驗。我們通常的做法就是把查詢從主庫中抽取出來,采用多個從庫,使用負載均衡,減輕每個從庫的查詢壓力。該示例並未對數據庫同步進行說明,只對讀寫操作的分離實現:

在進行操作之前,先簡單說一下AbstractRoutingDataSource相關的東西:

技術分享
 1 AbstractRoutingDataSource繼承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子類。DataSource   是javax.sql 的數據源接口,定義如下:
 2 
 3 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean  {}
 4 
 5 public
interface DataSource extends CommonDataSource,Wrapper { 6 7 /** 8 * <p>Attempts to establish a connection with the data source that 9 * this <code>DataSource</code> object represents. 10 * 11 * @return a connection to the data source 12 * @exception SQLException if a database access error occurs
13 */ 14 Connection getConnection() throws SQLException; 15 16 /** 17 * <p>Attempts to establish a connection with the data source that 18 * this <code>DataSource</code> object represents. 19 * 20 * @param username the database user on whose behalf the connection is 21 * being made 22 * @param password the user‘s password 23 * @return a connection to the data source 24 * @exception SQLException if a database access error occurs 25 * @since 1.4 26 */ 27 Connection getConnection(String username, String password) 28 throws SQLException; 29 30 } 31 32 33 public Connection getConnection() throws SQLException { 34 return determineTargetDataSource().getConnection(); 35 } 36 37 public Connection getConnection(String username, String password) throws SQLException { 38 return determineTargetDataSource().getConnection(username, password); 39 } 40 41 protected DataSource determineTargetDataSource() { 42 Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); 43 Object lookupKey = determineCurrentLookupKey(); 44 DataSource dataSource = this.resolvedDataSources.get(lookupKey); 45 if (dataSource == null && (this.lenientFallback || lookupKey == null)) { 46 dataSource = this.resolvedDefaultDataSource; 47 } 48 if (dataSource == null) { 49 throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); 50 } 51 return dataSource; 52 }
View Code

從上面的代碼中不難看出,獲取數據源首先是通過對determineCurrentLookupKey()的調用獲取resolvedDataSources對應key的值,故執行創建一個動態數據源類繼承AbstractRoutingDataSource,復寫determineCurrentLookupKey()去自定義設置和獲取resolvedDataSources的key就可以實現了

具體步驟如下:

第一步:

技術分享
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
    default-lazy-init="true">
    <!-- 引入配置文件 -->  
    <context:component-scan base-package="com.he" />  
     <bean id="masterdataSource"
       class="org.apache.commons.dbcp.BasicDataSource"  
        destroy-method="close">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/test" />
        <property name="username" value="root" />
        <property name="password" value="111111" />
    </bean>

    <bean id="slavedataSource"
      class="org.apache.commons.dbcp.BasicDataSource"  
        destroy-method="close">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/test2" />
        <property name="username" value="root" />
        <property name="password" value="111111" />
    </bean>
    
    <bean id="dataSource" class="com.he.mysql.test.DynamicDataSource">
        <property name="targetDataSources">  
              <map key-type="java.lang.String">  
                  <!-- write -->
                 <entry key="masterdataSource" value-ref="masterdataSource"/>  
                 <!-- read -->
                 <entry key="slavedataSource" value-ref="slavedataSource"/>  
              </map>  
              
        </property>  
        <property name="defaultTargetDataSource" ref="masterdataSource"/>  
    </bean>
       
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="dataSource" />  
        <property name="mapperLocations" value="classpath:com/he/dao/*.xml"></property>  
    </bean>  

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
        <property name="basePackage" value="com.he.dao" />  
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>  
    </bean>  
    
    <bean id="transactionManager"  
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean> 
   
    <!-- 註解式事務管理,[email protected] -->
    <tx:annotation-driven transaction-manager="transactionManager" />
     
</beans>  
View Code

第二步:

技術分享
1 public class DynamicDataSource extends AbstractRoutingDataSource {
2 
3     @Override
4     protected Object determineCurrentLookupKey() {
5      
6         return DynamicDataSourceHolder.getDataSouce();
7     }
8 
9 }
創建動態數據源類繼承AbstractRoutingDataSource

第三步:

技術分享
 1 public class DynamicDataSourceHolder {
 2     public static final ThreadLocal<String> holder = new ThreadLocal<String>();
 3 
 4     public static void putDataSource(String name) {
 5         holder.set(name);
 6     }
 7 
 8     public static String getDataSouce() {
 9         return holder.get();
10     }
11 }
設置及獲取每個線程訪問的哪個數據源

第四步:

技術分享
 1 @Service("userService")  
 2 @Transactional
 3 public class UserServiceImpl implements UserService{
 4 
 5     @Autowired
 6     private UserMapper userDao;public void add(User user) {
 7         
 8         DynamicDataSourceHolder.putDataSource("masterdataSource");
 9         userDao.add(user);
10     }
11 
12     public void update(User user) {
13         
14         DynamicDataSourceHolder.putDataSource("masterdataSource");
15         userDao.updates(user);
16     
17         
18     }
19 
20     @Transactional(propagation = Propagation.NOT_SUPPORTED)
21     public List<User> query() {
22         
23         DynamicDataSourceHolder.putDataSource("slavedataSource");
24         List<User> user = userDao.query();
25         return user;
26         
27     }
28 
29     
30     
31     
32 }
對service實現層加入設置數據源代碼

上述為實現讀寫分離的關鍵部分,只是為了簡單的做一個示例,完成上面操作以後,可自行的對數據庫進行新增和查詢操作,查看效果

java實現mysql數據庫讀寫分離之定義多數據源方式