天天看點

使用Spring AOP實作MySQL資料庫讀寫分離案例分析一、前言二、實作讀寫分離的兩種方法三、Aop實作主從資料庫的讀寫分離案例

一、前言

分布式環境下資料庫的讀寫分離政策是解決資料庫讀寫性能瓶頸的一個關鍵解決方案,更是最大限度了提高了應用中讀取 (Read)資料的速度和并發量。

在進行資料庫讀寫分離的時候,我們首先要進行資料庫的主從配置,最簡單的是一台Master和一台Slave(大型網站系統的話,當然會很複雜,這裡隻是分析了最簡單的情況)。通過主從配置主從資料庫保持了相同的資料,我們在進行讀操作的時候通路從資料庫Slave,在進行寫操作的時候通路主資料庫Master。這樣的話就減輕了一台伺服器的壓力。

在進行讀寫分離案例分析的時候。首先,配置資料庫的主從複制,可以選擇下面的這個方法:

MySQL5.6 資料庫主從(Master/Slave)同步安裝與配置詳解

當然,隻是簡單的為了看一下如何用代碼的方式實作資料庫的讀寫分離,完全不必要去配置主從資料庫,隻需要兩台安裝了 相同資料庫的機器就可以了。

二、實作讀寫分離的兩種方法

具體到開發中,實作讀寫分離常用的有兩種方式:

  • 第一種方式是我們最常用的方式,就是定義2個資料庫連接配接,一個是MasterDataSource,另一個是SlaveDataSource。更新資料時我們讀取MasterDataSource,查詢資料時我們讀取SlaveDataSource。這種方式很簡單,我就不贅述了。
  • 第二種方式動态資料源切換,就是在程式運作時,把資料源動态織入到程式中,進而選擇讀取主庫還是從庫。主要使用的技術是:Annotation,Spring AOP ,反射。

下面會詳細的介紹實作方式。

三、Aop實作主從資料庫的讀寫分離案例

1、項目代碼位址

目前該Demo的項目位址在開源中國 碼雲 上邊:http://git.oschina.net/xuliugen/aop-choose-db-demo

使用Spring AOP實作MySQL資料庫讀寫分離案例分析一、前言二、實作讀寫分離的兩種方法三、Aop實作主從資料庫的讀寫分離案例

2、項目結構

使用Spring AOP實作MySQL資料庫讀寫分離案例分析一、前言二、實作讀寫分離的兩種方法三、Aop實作主從資料庫的讀寫分離案例

上圖中,除了标記的代碼,其他的主要是配置代碼和業務代碼。

3、具體分析

該項目是SSM架構的一個demo,Spring、Spring MVC和MyBatis,具體的配置檔案不在過多介紹。

(1)UserContoller模拟讀寫資料

<pre style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; word-break: break-word;">

/**
 * 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";
 }
}           

模拟讀寫資料,調用IUserService 。

(2)spring-db.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: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>
 <!-- 資料庫連接配接 -->
 <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}"/>
 <!-- 省略部分内容 -->
 </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}"/>
 <!-- 省略部分内容 -->
 </bean>
 <!-- 配置動态配置設定的讀寫 資料源 -->
 <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兩個資料源,但是交給SqlSessionFactoryBean進行管理的隻有dataSource,其中使用到了:com.xuliugen.choosedb.demo.aspect.ChooseDataSource 這個是進行資料庫選擇的。

<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>
           

配置了資料庫具體的那些是讀哪些是寫的字首關鍵字。ChooseDataSource的具體代碼如下:

(3)ChooseDataSource

/**
 * 擷取資料源,用于動态切換資料源
 */
public class ChooseDataSource extends AbstractRoutingDataSource {
 public static Map<String, List<String>> METHOD_TYPE_MAP = new HashMap<String, List<String>>();
 /**
 * 實作父類中的抽象方法,擷取資料源名稱
 * @return
 */
 protected Object determineCurrentLookupKey() {
 return DataSourceHandler.getDataSource();
 }
 // 設定方法名字首對應的資料源
 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進行具體方法的AOP攔截

/**
 * 切換資料源(不同方法調用不同資料源)
 */
@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()上注冊的切入點
 */
 @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();
 }
 }
}
           
package com.xuliugen.choosedb.demo.aspect;
/**
 * 資料源的Handler類
 */
public class DataSourceHandler {
 // 資料源名稱線程池
 public static final ThreadLocal<String> holder = new ThreadLocal<String>();
 /**
 * 在項目啟動的時候将配置的讀、寫資料源加到holder中
 */
 public static void putDataSource(String datasource) {
 holder.set(datasource);
 }
 /**
 * 從holer中擷取資料源字元串
 */
 public static String getDataSource() {
 return holder.get();
 }
}