大家好,又见面了,我是你们的朋友全栈君。
1、修改Dbconfig.properties数据库配置文件:
注意:从库属性的名字要与主库的属性名字区分开,属性名将会在后面的配置文件中用到。
#数据库配置 主库-写入库
#MySQL
hibernate.dialect=org.hibernate.dialect.MySQLDialect
validationQuery.sqlserver=SELECT 1
jdbc.url=jdbc\:mysql\://127.0.0.1\:3306/database001?useUnicode\=true&characterEncoding\=UTF-8
jdbc.username=root
jdbc.password=root
jdbc.dbType=mysql
#数据库配置 从库-读库
#MySQL
jdbc.url.slave=jdbc\:mysql\://127.0.0.1\:3306/database002?useUnicode\=true&characterEncoding\=UTF-8
jdbc.username.slave=root
jdbc.password.slave=root
jdbc.dbType.slave=mysql
#更新|创建|验证数据库表结构|不作改变 默认update(create,validate,none)
hibernate.hbm2ddl.auto=none
2、修改spring-mvc-hibernate.xml配置文件
2.1、配置数据源2:复制原有的数据源配置,做以下修改:
1) 数据源的名称name需要重新命名;
2) 将数据库的链接属性设定为Dbconfig.properties中数据源2的属性值。
<!-- 配置数据源2 -->
<bean name="dataSource_slave" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url.jeewx.slave}" />
<property name="username" value="${jdbc.username.jeewx.slave}" />
<property name="password" value="${jdbc.password.jeewx.slave}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="250" />
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="5" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<!-- <property name="poolPreparedStatements" value="true" /> <property
name="maxPoolPreparedStatementPerConnectionSize" value="33" /> -->
<property name="validationQuery" value="${validationQuery.sqlserver}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 开启Druid的监控统计功能 -->
<property name="filters" value="stat" />
<!--<property name="filters" value="mergeStat" /> -->
<!-- Oracle连接是获取字段注释 -->
<property name="connectProperties">
<props>
<prop key="remarksReporting">true</prop>
</props>
</property>
</bean>
2.2、配置数据源集:
<!-- 数据源集合 -->
<bean id="dataSource"
class="org.jeecgframework.core.extend.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="org.jeecgframework.core.extend.datasource.DataSourceType">
<entry key="dataSource_jeecg" value-ref="dataSource_jeecg" />
<entry key="dataSource_slave" value-ref="dataSource_slave" />
<!-- <entry key="mapdataSource" value-ref="mapdataSource" /> -->
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource_jeecg" />
</bean>
2.3、定义AOP的切面处理器,关于AOP的语法自行百度查阅,其中重点对以下做说明:
(1) org.jeecgframework.core.interceptors.DataSourceAspect类需要实现,该类主要用来对数据库进行动态切换;
(2) 此处的表达式设定的主要是是针对该包下的CommonService的所有方法进行切面处理;
(3) DataSourceAspect类中必须实现before方法,具体实现对数据库的动态切换就在此控制。
<!-- 定义AOP切面处理器 -->
<bean class="org.jeecgframework.core.interceptors.DataSourceAspect" id="dataSourceAspect" />
<aop:config>
<!-- 定义切面,CommonService的所有方法 -->
<aop:pointcut id="txPointcut" expression="execution(* org.jeecgframework.core.common.service.*.*(..))" />
<!-- 将切面应用到自定义的切面处理器上,-9999保证该切面优先级最高执行 -->
<aop:aspect ref="dataSourceAspect" order="-9999">
<aop:before method="before" pointcut-ref="txPointcut" />
</aop:aspect>
</aop:config>
3、实现类org.jeecgframework.core.interceptors.DataSourceAspect:
package org.jeecgframework.core.interceptors;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.jeecgframework.core.extend.datasource.DataSourceContextHolder;
/**
* 定义数据源的AOP切面,通过该Service的方法名判断是应该走读库还是写库
*
* @author yaoxy
*
*/
public class DataSourceAspect {
/**
* 在进入Service方法之前执行
*
* @param point 切面对象
*/
public void before(JoinPoint point) {
// 获取到当前执行的方法名
String methodName = point.getSignature().getName();
if (isSlave(methodName)) {
// 标记为读库
DataSourceContextHolder.markSlave();
} else {
// 标记为写库
DataSourceContextHolder.markMaster();
}
}
/**
* 判断是否为读库
*
* @param methodName
* @return
*/
private Boolean isSlave(String methodName) {
// 方法名以query、find、get开头的方法名走从库
return StringUtils.startsWithAny(methodName, "query","find","get");
}
}
实现原理:
1) 判断AOP切面处理的CommonService类中被调用的方法名开头中是否包含查询的关键字;
2) 如果CommonService被调用的方法中包含查询的关键字,调用DataSourceContextHolder将库切换到从库进行读取操作,否则切换到主库进行写入操作。
4、DataSourceContextHolder中追加主从切换的方法
ackage org.jeecgframework.core.extend.datasource;
/**
*类名:DataSourceContextHolder.java
*功能:获得和设置上下文环境的类,主要负责改变上下文数据源的名称
*/
public class DataSourceContextHolder {
//读库对应的数据源key
private static final ThreadLocal contextHolder=new ThreadLocal();
public static void setDataSourceType(DataSourceType dataSourceType){
contextHolder.set(dataSourceType);
}
public static DataSourceType getDataSourceType(){
return (DataSourceType) contextHolder.get();
}
public static void clearDataSourceType(){
contextHolder.remove();
}
/**
* 标记写库
*/
public static void markMaster(){
setDataSourceType(DataSourceType.dataSource_jeecg);
}
/**
* 标记读库
*/
public static void markSlave(){
setDataSourceType(DataSourceType.dataSource_slave);
}
}
5、数据源的枚举类DataSourcetype中追加新增的从库数据源名称
package org.jeecgframework.core.extend.datasource;
public enum DataSourceType {
dataSource_jeecg,dataSource_enter,dataSource4,mapdataSource,dataSource_slave
//dataSource_jeecg:主库名,dataSource_slave:从库名。
}
总结:
应用层的读写分析涉及到以下5个文件,详见文件夹【应用层读写分离设定文件】:
实现的原理:利用Spring AOP的切面处理原理,在对数据库进行操作的方法被执行之前根据方法的名字判断是读还是写,进行主从/读写数据库的切换。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/127994.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...