一、SSM框架配置多數據源的原理
原理:MyBatis在創建SqlSession時,動態的使用不同的dataSource,就可以動態的使用不同的數據源。
那么,怎樣才能動態的使用不同的dataSource呢? 在Spring框架中,提供了一個類AbstractRoutingDataSource,顧名思義叫做路由選擇。該類AbstractRoutingDataSource繼承AbstractDataSource,AbstractDataSource又實現了DataSource,因此,可以使用AbstractRoutingDataSource來完成我們的需求。AbstractRoutingDataSource中有一個determineTargetDataSource()方法,該方法是用來決定目標數據源的;而determineTargetDataSource()方法中通過determineCurrentLookupKey()方法來決定lookupKey;然后使用封裝好了的map集合resolvedDataSources,通過lookupKey為key值取得dataSource。因此這里面最重要的就是determineCurrentLookupKey()方法獲取key值,在這里Spring把這個方法抽象出來,交給用戶來實現。
二、實踐:在SSM框架中,實現多數據源訪問(目錄結構如下圖)
1、第一步:搭建SSM項目
搭建步驟參考:SSM框架(1):使用Maven搭建SSM項目實踐
2、創建動態數據源代理類:DynamicDataSource 和 DynamicDataSourceHolder
- DynamicDataSource:實現Spring定義的路由選擇抽象類AbstractRoutingDataSource,用來創建動態的數據源。
- DynamicDataSourceHolder:用來保存數據源標識,從而在實例化DynamicDataSource時,來指定要使用的數據源實例
package com.newbie.util; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * DynamicDataSource由自己實現,實現AbstractRoutingDataSource,數據源由自己指定。 */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 從自定義的位置獲取數據源標識 return DynamicDataSourceHolder.getDataSourceKey(); } }
package com.newbie.util; /** * 自定義類:用來保存數據源標識,從而在實例化DynamicDataSource時來指定要使用的數據源實例 */ public class DynamicDataSourceHolder { /** * 注意:數據源標識保存在線程變量中,避免多線程操作數據源時互相干擾 */ private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<String>(); public static String getDataSourceKey(){ return DATA_SOURCE_KEY.get(); } public static void setDataSourceKey(String dataSourceKey){ DATA_SOURCE_KEY.set(dataSourceKey); } public static void clearDataSourceKey(){ DATA_SOURCE_KEY.remove(); } }
3、自定義多數據源注解@AnnotationDBSourceKey 以及 解析數據源注解的切面類DynamicDBSourceAspect
- AnnotationDBSourceKey:自定義注解類。在要動態設置數據源的方法上,使用注解來標識數據源。
- DynamicDBSourceAspect:切面類。在執行橫切的方法前,獲取方法的@AnnotationDBSourceKey注解,解析注解的值來設置DynamicDataSourceHolder類中的數據源標識,進而達到動態設置數據源的目的。
package com.newbie.util.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定義注解類型:使用注解來標識數據源 */ @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface AnnotationDBSourceKey { /** * value的值:來標識數據源 * @return */ String value(); }
package com.newbie.util.annotation; import com.newbie.util.DynamicDataSourceHolder; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * 定義切面類:在執行DAO接口的方法前,獲取方法的@AnnotationDBSourceKey注解,根據注解的值來動態設置數據源 */ @Component @Aspect public class DynamicDBSourceAspect { /** * 攔截目標方法,獲取由@DataSource指定的數據源標識,設置到線程存儲中以便切換數據源 * * @param point * @throws Exception */ @Before("execution(* com.newbie.service.*.*(..))") public void intercept(JoinPoint point) throws Exception { Class<?> target = point.getTarget().getClass(); MethodSignature signature = (MethodSignature) point.getSignature(); System.out.println("intercept()----,target:"+target+",signature:"+signature+",signature.getMethod():"+signature.getMethod()); // 默認使用目標類型的注解,如果沒有則使用其實現接口的注解 for (Class<?> clazz : target.getInterfaces()) { resolveDataSource(clazz, signature.getMethod()); } resolveDataSource(target, signature.getMethod()); } /** * 提取目標對象方法注解和類型注解中的數據源標識 * * @param clazz * @param method */ private void resolveDataSource(Class<?> clazz, Method method) { try { Class<?>[] types = method.getParameterTypes(); // 默認使用類型注解 if (clazz.isAnnotationPresent(AnnotationDBSourceKey.class)) { AnnotationDBSourceKey source = clazz.getAnnotation(AnnotationDBSourceKey.class); DynamicDataSourceHolder.setDataSourceKey(source.value()); } // 方法注解可以覆蓋類型注解 Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(AnnotationDBSourceKey.class)) { AnnotationDBSourceKey source = m.getAnnotation(AnnotationDBSourceKey.class); DynamicDataSourceHolder.setDataSourceKey(source.value()); } } catch (Exception e) { System.out.println(clazz + ":" + e.getMessage()); } } }
4、修改數據源配置:將單數據源配置改為多數據源配置
-
db.properties : 數據源靜態參數配置文件,增加主從數據源的配置信息
-
spring-mybatis.xml : 增加主從數據源的實例(master\slave1\slave2);增加DynamicDataSource的動態代理類的實例,並將DynamicDataSource實例的參數targetDataSources,設置為主從數據源的實例集合。
#db.properties配置文件內容 #配置主從數據庫的鏈接地址 #主庫 master.jdbc.url=jdbc:mysql://localhost:3306/nb_master?useUnicode=true&characterEncoding=utf8 #從庫1 slave1.jdbc.url=jdbc:mysql://localhost:3306/nb_slave1?useUnicode=true&characterEncoding=utf8 #從庫2 slave2.jdbc.url=jdbc:mysql://www.newbie.com:10127/nb_slave2?useUnicode=true&characterEncoding=utf8 #配置其他信息 jdbc.driver=com.mysql.jdbc.Driver jdbc.username=root jdbc.password=xxxxxx #最大連接數 c3p0.maxPoolSize=30 #最小連接數 c3p0.minPoolSize=10 #關閉連接后不自動commit c3p0.autoCommitOnClose=false #獲取連接超時時間 c3p0.checkoutTimeout=10000 #當獲取連接失敗重試次數 c3p0.acquireRetryAttempts=2
##### spring-mybatis.xml配置文件內容 #####
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--讀取靜態配置文件,獲取相關數據庫連接參數 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置數據源 --> <bean id="abstractDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true" destroy-method="close"> <property name="driverClass" value="${jdbc.driver}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/> <property name="minPoolSize" value="${c3p0.minPoolSize}"/> <property name="autoCommitOnClose" value="${c3p0.autoCommitOnClose}"/> <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}"/> <property name="acquireRetryAttempts" value="${c3p0.acquireRetryAttempts}"/> </bean> <!-- 配置數據源:主庫 --> <bean id="master" parent="abstractDataSource"> <property name="jdbcUrl" value="${master.jdbc.url}"/> </bean> <!-- 配置數據源:從庫1 --> <bean id="slave1" parent="abstractDataSource"> <property name="jdbcUrl" value="${slave1.jdbc.url}"/> </bean> <!-- 配置數據源:從庫2 --> <bean id="slave2" parent="abstractDataSource"> <property name="jdbcUrl" value="${slave2.jdbc.url}"/> </bean> <!-- 動態數據源配置,這個class要完成實例化 --> <bean id="dynamicDataSource" class="com.newbie.util.DynamicDataSource"> <property name="targetDataSources"> <!-- 指定lookupKey和與之對應的數據源,切換時使用的為key --> <map key-type="java.lang.String"> <entry key="master" value-ref="master"/> <entry key="slave1" value-ref="slave1"/> <entry key="slave2" value-ref="slave2"/> </map> </property> <!-- 這里可以指定默認的數據源 --> <property name="defaultTargetDataSource" ref="master"/> </bean> <!-- 配置MyBatis創建數據庫連接的工廠類(此處配置和單數據源相同,不需改變) --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--數據源指定為:動態數據源DynamicDataSource --> <property name="dataSource" ref="dynamicDataSource"/> <!-- mapper配置文件 --> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!-- 配置自動掃描DAO接口包,動態實現DAO接口實例,注入到Spring容器中進行管理(此處配置和單數據源相同,不需改變) --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 注入SqlSession工廠對象:SqlSessionFactoryBean --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!-- 指定要掃描的DAO接口所在包 --> <property name="basePackage" value="com.newbie.dao"/> </bean> </beans>
5、創建 UserController、UserService,在UserService中動態設置數據源
- UserService : 調用DynamicDataSourceHolder.setDataSourceKey(dataSourceKey)來動態設置數據源,實現多數據源的動態訪問。
- UserController : 接收客戶端請求,調用UserService的方法處理請求,然后將結果響應給客戶。
package com.newbie.service.impl; import com.newbie.dao.UserDAO; import com.newbie.domain.User; import com.newbie.service.IUserService; import com.newbie.util.DynamicDataSourceHolder; import com.newbie.util.annotation.AnnotationDBSourceKey; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; @Service public class UserService implements IUserService { @Resource private UserDAO userDAO; /** * 通過設置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,來動態設置數據源 */ public List<User> searchAllUser(String dataSourceKey) { if(dataSourceKey == null){ dataSourceKey = "master"; } DynamicDataSourceHolder.setDataSourceKey(dataSourceKey); return userDAO.selectAll(); } /* * 以下三個方法:searchMaster()、searchSlave1()、searchSlave2() * 使用自定義注解的方式,來動態設置數據源 */ @AnnotationDBSourceKey("master") public List<User> searchMaster() { return userDAO.selectAll(); } @AnnotationDBSourceKey("slave1") public List<User> searchSlave1() { return userDAO.selectAll(); } @AnnotationDBSourceKey("slave2") public List<User> searchSlave2() { return userDAO.selectAll(); } }
package com.newbie.controller; import com.newbie.domain.User; import com.newbie.service.IUserService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; import java.util.List; /** * 控制器:接收客戶端請求,處理后將結果響應給客戶 */ @Controller public class UserController { @Resource private IUserService userService; /** * 通過設置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,來動態設置數據源 */ @RequestMapping("/searchAllUser") public String searchAllUser(Model model, String dataSourceKey){ List<User> users = userService.searchAllUser(dataSourceKey); model.addAttribute("message","dataSourceKey = "+dataSourceKey); model.addAttribute("user",users.get(0)); return "showInfo"; } /* * 以下三個方法:searchMaster()、searchSlave1()、searchSlave2() * 使用自定義注解的方式,來動態設置數據源 */ @RequestMapping("/searchMaster") public String searchMaster(Model model){ List<User> users = userService.searchMaster(); model.addAttribute("message","dataSourceKey = master , method : searchMaster()"); model.addAttribute("user",users.get(0)); return "showInfo"; } @RequestMapping("/searchSlave1") public String searchSlave1(Model model){ List<User> users = userService.searchSlave1(); model.addAttribute("message","dataSourceKey = slave1 , method : searchSlave1()"); model.addAttribute("user",users.get(0)); return "showInfo"; } @RequestMapping("/searchSlave2") public String searchSlave2(Model model){ List<User> users = userService.searchSlave2(); model.addAttribute("message","dataSourceKey = slave2 , method : searchSlave2()"); model.addAttribute("user",users.get(0)); return "showInfo"; } }
6、UserDAO、UserMapper.xml保持不變(和單數據源中的配置相同)
package com.newbie.dao; import com.newbie.domain.User; import java.util.List; /** * 數據庫操作對象:用戶類 */ public interface UserDAO { List<User> selectAll(); }
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- mapper配置文件,其中namespace指定要動態實例的DAO接口全局類名稱 -->
<mapper namespace="com.newbie.dao.UserDAO">
<!-- 查詢所有用戶 -->
<select id="selectAll" resultType="com.newbie.domain.User">
select id,username,title from user
</select>
</mapper>
-- DROP TABLE user; CREATE TABLE user( id VARCHAR(25), username VARCHAR(50), title VARCHAR(255) ); insert into user(id,username,title) values('123','簡小六','主庫master'); insert into user(id,username,title) values('123','簡小六','從庫slave1'); insert into user(id,username,title) values('123','簡小六','從庫slave2');
7、創建前端jsp頁面,測試多數據源訪問結果(首先訪問index.jsp頁面,點擊<a>標簽跳轉后,在showInfo.jsp頁面中顯示結果)
#### index.jsp頁面內容 ####
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>測試多數據源模式</title> </head> <body> <h2> 通過設置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,來動態設置數據源</h2> <a href="searchAllUser?dataSourceKey=master">查詢主庫:用戶信息</a><br/><br/> <a href="searchAllUser?dataSourceKey=slave1">查詢從庫1:用戶信息</a><br/><br/> <a href="searchAllUser?dataSourceKey=slave2">查詢從庫2:用戶信息</a><br/><br/> <br/><hr/><hr/><br/> <h2> 使用自定義注解的方式,來動態設置數據源</h2> <a href="searchMaster">查詢主庫:用戶信息</a><br/><br/> <a href="searchSlave1">查詢從庫1:用戶信息</a><br/><br/> <a href="searchSlave2">查詢從庫2:用戶信息</a><br/><br/> </body> </html>
#### showInfo.jsp頁面內容 #### <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>顯示結果信息</title> </head> <body> 處理信息:${message}<br/> 處理結果:<br/> 用戶ID:${user.id} username:${user.username} title:${user.title} </body> </html>
8、程序運行結果
(1)發布項目,請求以下地址:http://localhost:8080/ssm-multi-DBSource/index.jsp
(2)index.jsp頁面顯示如下:
(3)以上六個<a>標簽,點擊后程序運行的結果分別如下圖:
參考資料:
歡迎轉載,轉載請注明出處!