欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring动态数据源实现读写分离详解

程序员文章站 2023-11-17 10:17:28
一、创建基于threadlocal的动态数据源容器,保证数据源的线程安全性 package com.bounter.mybatis.extension; /...

一、创建基于threadlocal的动态数据源容器,保证数据源的线程安全性

package com.bounter.mybatis.extension;

/**
 * 基于threadlocal实现的动态数据源容器,保证dynamicdatasource的线程安全性
 * @author simon
 *
 */
public class dynamicdatasourceholder {

 private static final threadlocal<string> datasourceholder = new threadlocal<>();

 public static void setdatasource(string datasourcekey) {
 datasourceholder.set(datasourcekey);
 }

 public static string getdatasource() {
 return datasourceholder.get();
 }

 public static void cleardatasource() {
 datasourceholder.remove();
 }
}

二、定义spring动态数据源扩展类,用来实现master、slave数据源动态切换

package com.bounter.mybatis.extension;

import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource;

/**
 * 自定义的spring 动态数据源扩展类,用来实现master、slave数据源动态切换
 * @author simon
 *
 */
public class dynamicdatasource extends abstractroutingdatasource {

 @override
 protected object determinecurrentlookupkey() {
 //使用dynamicdatasourceholder保证线程安全
 return dynamicdatasourceholder.getdatasource();
 }

}

三、配置master、slave数据源

1. db.properties配置master、slave数据信息

# master db
db.master.url=jdbc:mysql://192.168.168.110:3306/bounter?useunicode=true&characterencoding=utf8&zerodatetimebehavior=converttonull&allowmultiqueries=true&servertimezone=prc&usessl=false
db.master.username=bounter
# aes encrypt,base64 encode
db.master.password=znhnejauk3peczxxs84ofa==

# slave db
db.slave.url=jdbc:mysql://192.168.168.111:3306/database?useunicode=true&characterencoding=utf8&zerodatetimebehavior=converttonull&allowmultiqueries=true&servertimezone=prc&usessl=false
db.slave.username=bounter
# aes encrypt,base64 encode
db.slave.password=jfymt2f57rhhzitydhwisa==
 

2. spring 配置文件配置master、slave连接池,动态数据源

<!-- master数据源 -->
<bean id="masterdatasource" class="com.alibaba.druid.pool.druiddatasource"
 init-method="init" destroy-method="close">
 <!-- 基本属性 url、user、password -->
 <property name="url" value="${db.master.url}" />
 <property name="username" value="${db.master.username}" />
 <property name="password" value="${db.master.password}" />
 <!-- 配置初始化大小、最小、最大 -->
 <property name="initialsize" value="20" />
 <property name="minidle" value="1" />
 <property name="maxactive" value="40" />
 <!-- 配置获取连接等待超时的时间 -->
 <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" />
 <!-- 配置监控统计拦截的filters -->
 <property name="filters" value="stat" />
</bean>

<!-- slave数据源 -->
<bean id="slavedatasource" class="com.alibaba.druid.pool.druiddatasource"
 init-method="init" destroy-method="close">
 <!-- 基本属性 url、user、password -->
 <property name="url" value="${db.slave.url}" />
 <property name="username" value="${db.slave.username}" />
 <property name="password" value="${db.slave.password}" />
 <!-- 配置初始化大小、最小、最大 -->
 <property name="initialsize" value="20" />
 <property name="minidle" value="1" />
 <property name="maxactive" value="40" />
 <!-- 配置获取连接等待超时的时间 -->
 <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" />
 <!-- 配置监控统计拦截的filters -->
 <property name="filters" value="stat" />
</bean>

<!-- 自定义动态数据源 -->
 <bean id="datasource" class="com.bounter.mybatis.extension.dynamicdatasource">
 <property name="targetdatasources">
  <map key-type="java.lang.string">
  <!-- 配置读写数据源 -->
  <entry value-ref="masterdatasource" key="write"></entry>
  <entry value-ref="slavedatasource" key="read"></entry>
  </map>
 </property>
 <property name="defaulttargetdatasource" ref="masterdatasource"></property>
 </bean>

四、创建数据源切面,通过aop实现根据dao层方法前缀动态选取读、写数据源

package com.bounter.mybatis.aop;

import org.aspectj.lang.joinpoint;
import org.aspectj.lang.annotation.after;
import org.aspectj.lang.annotation.aspect;
import org.aspectj.lang.annotation.before;
import org.springframework.context.annotation.enableaspectjautoproxy;
import org.springframework.stereotype.component;

import com.bounter.mybatis.extension.dynamicdatasourceholder;

/**
 * 数据源切面,通过dao方法前缀决定访问读、写数据源
 * @author simon
 *
 */
@component
@aspect
@enableaspectjautoproxy(proxytargetclass = true)
public class datasourceaspect {

 //读库数据源key
 private static final string datasource_key_read = "read";
 //查询方法清单
 string[] querymethods = {"find","get","query","count","select"};

 /**
 * dao层方法执行前选择数据源
 * @param point
 */
 @before("execution(* com.bounter.mybatis.dao..*.*(..))")
 public void before(joinpoint point) {
 // 获取到当前执行的方法名
 string methodname = point.getsignature().getname();
 //匹配查询方法
 for(string querymethod : querymethods) {
  if(methodname.startswith(querymethod)) {
  //查询方法设置数据源为读库
  dynamicdatasourceholder.setdatasource(datasource_key_read);
  break;
  }
 }
 }

 /**
 * dao层方法执行完后清空数据源选择
 * @param point
 */
 @after("execution(* com.bounter.mybatis.dao..*.*(..))")
 public void after(joinpoint point) {
 dynamicdatasourceholder.cleardatasource();
 }
}


github源码地址:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。