使用Spring AOP實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)讀寫分離案例分析(附demo)
一、前言
分布式環(huán)境下數(shù)據(jù)庫(kù)的讀寫分離策略是解決數(shù)據(jù)庫(kù)讀寫性能瓶頸的一個(gè)關(guān)鍵解決方案,更是最大限度了提高了應(yīng)用中讀取 (Read)數(shù)據(jù)的速度和并發(fā)量。
在進(jìn)行數(shù)據(jù)庫(kù)讀寫分離的時(shí)候,我們首先要進(jìn)行數(shù)據(jù)庫(kù)的主從配置,最簡(jiǎn)單的是一臺(tái)Master和一臺(tái)Slave(大型網(wǎng)站系統(tǒng)的話,當(dāng)然會(huì)很復(fù)雜,這里只是分析了最簡(jiǎn)單的情況)。通過(guò)主從配置主從數(shù)據(jù)庫(kù)保持了相同的數(shù)據(jù),我們?cè)谶M(jìn)行讀操作的時(shí)候訪問(wèn)從數(shù)據(jù)庫(kù)Slave,在進(jìn)行寫操作的時(shí)候訪問(wèn)主數(shù)據(jù)庫(kù)Master。這樣的話就減輕了一臺(tái)服務(wù)器的壓力。
在進(jìn)行讀寫分離案例分析的時(shí)候。首先,配置數(shù)據(jù)庫(kù)的主從復(fù)制,MySQL5.6 數(shù)據(jù)庫(kù)主從(Master/Slave)同步安裝與配置詳解
當(dāng)然,只是簡(jiǎn)單的為了看一下如何用代碼的方式實(shí)現(xiàn)數(shù)據(jù)庫(kù)的讀寫分離,完全不必要去配置主從數(shù)據(jù)庫(kù),只需要兩臺(tái)安裝了 相同數(shù)據(jù)庫(kù)的機(jī)器就可以了。
二、實(shí)現(xiàn)讀寫分離的兩種方法
具體到開發(fā)中,實(shí)現(xiàn)讀寫分離常用的有兩種方式:
1、第一種方式是我們最常用的方式,就是定義2個(gè)數(shù)據(jù)庫(kù)連接,一個(gè)是MasterDataSource,另一個(gè)是SlaveDataSource。更新數(shù)據(jù)時(shí)我們讀取MasterDataSource,查詢數(shù)據(jù)時(shí)我們讀取SlaveDataSource。這種方式很簡(jiǎn)單,我就不贅述了。
2、第二種方式動(dòng)態(tài)數(shù)據(jù)源切換,就是在程序運(yùn)行時(shí),把數(shù)據(jù)源動(dòng)態(tài)織入到程序中,從而選擇讀取主庫(kù)還是從庫(kù)。主要使用的技術(shù)是:Annotation,spring AOP ,反射。
下面會(huì)詳細(xì)的介紹實(shí)現(xiàn)方式。
三、Aop實(shí)現(xiàn)主從數(shù)據(jù)庫(kù)的讀寫分離案例
1、項(xiàng)目代碼地址
目前該Demo的項(xiàng)目地址:demo
2、項(xiàng)目結(jié)構(gòu)
上圖中,除了標(biāo)記的代碼,其他的主要是配置代碼和業(yè)務(wù)代碼。
3、具體分析
該項(xiàng)目是SSM框架的一個(gè)demo,Spring、Spring MVC和MyBatis,具體的配置文件不在過(guò)多介紹。
(1)UserContoller模擬讀寫數(shù)據(jù)
/** * Created by xuliugen on 2016/5/4. */ @Controller @RequestMapping(value = "/user", produces = {"application/json;charset=UTF-8"}) public class UserController { @Inject private IUserService userService; //http://localhost:8080/user/select.do @ResponseBody @RequestMapping(value = "/select.do", method = RequestMethod.GET) public String select() { User user = userService.selectUserById(123); return user.toString(); } //http://localhost:8080/user/add.do @ResponseBody @RequestMapping(value = "/add.do", method = RequestMethod.GET) public String add() { boolean isOk = userService.addUser(new User("333", "444")); return isOk == true ? "shibai" : "chenggong"; } }
模擬讀寫數(shù)據(jù),調(diào)用IUserService 。
(2)spring-db.xml讀寫數(shù)據(jù)源配置
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="statFilter" class="com.alibaba.druid.filter.stat.StatFilter" lazy-init="true"> <property name="logSlowSql" value="true"/> <property name="mergeSql" value="true"/> </bean> <!-- 數(shù)據(jù)庫(kù)連接 --> <bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init" lazy-init="true"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url1}"/> <property name="username" value="root"/> <property name="password" value="${password}"/> <!-- 省略部分內(nèi)容 --> </bean> <bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init" lazy-init="true"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="root"/> <property name="password" value="${password}"/> <!-- 省略部分內(nèi)容 --> </bean> <!-- 配置動(dòng)態(tài)分配的讀寫 數(shù)據(jù)源 --> <bean id="dataSource" class="com.xuliugen.choosedb.demo.aspect.ChooseDataSource" lazy-init="true"> <property name="targetDataSources"> <map key-type="java.lang.String" value-type="javax.sql.DataSource"> <!-- write --> <entry key="write" value-ref="writeDataSource"/> <!-- read --> <entry key="read" value-ref="readDataSource"/> </map> </property> <property name="defaultTargetDataSource" ref="writeDataSource"/> <property name="methodType"> <map key-type="java.lang.String"> <!-- read --> <entry key="read" value=",get,select,count,list,query"/> <!-- write --> <entry key="write" value=",add,create,update,delete,remove,"/> </map> </property> </bean> </beans>
上述配置中,配置了readDataSource和writeDataSource兩個(gè)數(shù)據(jù)源,但是交給SqlSessionFactoryBean進(jìn)行管理的只有dataSource,其中使用到了:com.xuliugen.choosedb.demo.aspect.ChooseDataSource 這個(gè)是進(jìn)行數(shù)據(jù)庫(kù)選擇的。
<property name="methodType"> <map key-type="java.lang.String"> <!-- read --> <entry key="read" value=",get,select,count,list,query"/> <!-- write --> <entry key="write" value=",add,create,update,delete,remove,"/> </map> </property>
配置了數(shù)據(jù)庫(kù)具體的那些是讀哪些是寫的前綴關(guān)鍵字。ChooseDataSource的具體代碼如下:
(3)ChooseDataSource
/** * 獲取數(shù)據(jù)源,用于動(dòng)態(tài)切換數(shù)據(jù)源 */ public class ChooseDataSource extends AbstractRoutingDataSource { public static Map<String, List<String>> METHOD_TYPE_MAP = new HashMap<String, List<String>>(); /** * 實(shí)現(xiàn)父類中的抽象方法,獲取數(shù)據(jù)源名稱 * @return */ protected Object determineCurrentLookupKey() { return DataSourceHandler.getDataSource(); } // 設(shè)置方法名前綴對(duì)應(yīng)的數(shù)據(jù)源 public void setMethodType(Map<String, String> map) { for (String key : map.keySet()) { List<String> v = new ArrayList<String>(); String[] types = map.get(key).split(","); for (String type : types) { if (StringUtils.isNotBlank(type)) { v.add(type); } } METHOD_TYPE_MAP.put(key, v); } } }
(4)DataSourceAspect進(jìn)行具體方法的AOP攔截
/** * 切換數(shù)據(jù)源(不同方法調(diào)用不同數(shù)據(jù)源) */ @Aspect @Component @EnableAspectJAutoProxy(proxyTargetClass = true) public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(* com.xuliugen.choosedb.demo.mybatis.dao.*.*(..))") public void aspect() { } /** * 配置前置通知,使用在方法aspect()上注冊(cè)的切入點(diǎn) */ @Before("aspect()") public void before(JoinPoint point) { String className = point.getTarget().getClass().getName(); String method = point.getSignature().getName(); logger.info(className + "." + method + "(" + StringUtils.join(point.getArgs(), ",") + ")"); try { for (String key : ChooseDataSource.METHOD_TYPE_MAP.keySet()) { for (String type : ChooseDataSource.METHOD_TYPE_MAP.get(key)) { if (method.startsWith(type)) { DataSourceHandler.putDataSource(key); } } } } catch (Exception e) { e.printStackTrace(); } } }
(5)DataSourceHandler,數(shù)據(jù)源的Handler類
package com.xuliugen.choosedb.demo.aspect; /** * 數(shù)據(jù)源的Handler類 */ public class DataSourceHandler { // 數(shù)據(jù)源名稱線程池 public static final ThreadLocal<String> holder = new ThreadLocal<String>(); /** * 在項(xiàng)目啟動(dòng)的時(shí)候?qū)⑴渲玫淖x、寫數(shù)據(jù)源加到holder中 */ public static void putDataSource(String datasource) { holder.set(datasource); } /** * 從holer中獲取數(shù)據(jù)源字符串 */ public static String getDataSource() { return holder.get(); } }
主要代碼,如上所述。
本文代碼:demo
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java對(duì)象與json對(duì)象間的相互轉(zhuǎn)換的方法
本篇文章主要介紹了java對(duì)象與json對(duì)象間的相互轉(zhuǎn)換的方法,詳細(xì)介紹了json字符串和java對(duì)象相互轉(zhuǎn)換,有興趣的可以了解一下2017-01-01關(guān)于HashMap的put方法執(zhí)行全過(guò)程
這篇文章主要介紹了關(guān)于HashMap的put方法執(zhí)行全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06Spring中Bean對(duì)象的定義、注冊(cè)和獲取流程分析
這篇文章主要介紹了Spring中Bean對(duì)象的定義、注冊(cè)和獲取流程分析,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06SpringBoot增強(qiáng)Controller方法@ControllerAdvice注解的使用詳解
這篇文章主要介紹了SpringBoot增強(qiáng)Controller方法@ControllerAdvice注解的使用詳解,@ControllerAdvice,是Spring3.2提供的新注解,它是一個(gè)Controller增強(qiáng)器,可對(duì)controller進(jìn)行增強(qiáng)處理,需要的朋友可以參考下2023-10-10Java開發(fā)中讀取XML與properties配置文件的方法
這篇文章主要介紹了Java開發(fā)中讀取XML與properties配置文件的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01javaweb圖書商城設(shè)計(jì)之用戶模塊(1)
這篇文章主要介紹了javaweb圖書商城設(shè)計(jì)之用戶模塊的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11java實(shí)現(xiàn)優(yōu)酷視頻地址解析示例代碼分享
最近做了一個(gè)在線視頻的下載器,需要解析youku的視頻,獲得真正的視頻地址,現(xiàn)在把解析過(guò)程記錄下來(lái)以供參考2014-01-01Springboot實(shí)現(xiàn)ModbusTCP通信的示例詳解
ModbusTCP協(xié)議是Modbus由MODICON公司于1979年開發(fā),是一種工業(yè)現(xiàn)場(chǎng)總線協(xié)議標(biāo)準(zhǔn),本文主要介紹了Springboot實(shí)現(xiàn)ModbusTCP通信的相關(guān)知識(shí),需要的可以參考下2023-12-12