First:看完还有任何问题可以私信我呗! 求三连大佬们
读写分离:分散主数据库的压力。例如2个服务器,主服务器的数据库用户承担写操作。从服务器的数据库用于承担写操作
spring的主XML配置文件:applicationContext-common.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置 spring 创建容器时要扫描的包 -->
<context:component-scan base-package="cn.itcast">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter>
</context:component-scan>
<!-- 配置 MyBatis 的 Session 工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="cn.itcast.pojo"/>
</bean>
<!-- 配置数据源 -->
<!--
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
-->
<!-- 配置 Mapper 扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.itcast.mapper"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务的注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager" order="1000000"></tx:annotation-driven>
</beans>
Spring的数据源XML配置文件:applicationContext-datasource.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 读数据源的相关参数 -->
<bean id="readDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.read.driver}"></property>
<property name="jdbcUrl" value="${jdbc.read.url}"></property>
<property name="user" value="${jdbc.read.username}"></property>
<property name="password" value="${jdbc.read.password}"></property>
</bean>
<!-- 写数据源的相关参数 -->
<bean id="writeDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.write.driver}"></property>
<property name="jdbcUrl" value="${jdbc.write.url}"></property>
<property name="user" value="${jdbc.write.username}"></property>
<property name="password" value="${jdbc.write.password}"></property>
</bean>
<!-- 继承自 AbstractRoutingDataSource 这个类是Spring框架封装好的 用户数据源选择的类 -->
<bean id="dataSource" class="cn.itcast.aop.datasource.ChooseDataSource">
<!-- targetDataSources这个属性和 AbstractRoutingDataSource类中的 setTargetDataSources对应 -->
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<entry key="write" value-ref="writeDataSource"/>
<entry key="read" value-ref="readDataSource"/>
</map>
</property>
<!-- defaultTargetDataSource这个属性和 AbstractRoutingDataSource类中的 setDefaultTargetDataSource对应 -->
<property name="defaultTargetDataSource" ref="writeDataSource"/>
<!-- methodType这个属性和 ChooseDataSource类中的 setMethodType对应 -->
<property name="methodType">
<map key-type="java.lang.String">
<entry key="read" value=",get,select,count,list,query,find"/>
<entry key="write" value=",add,create,update,delete,remove,insert"/>
</map>
</property>
</bean>
</beans>
AOP的切面类:DataSourceAspect
package cn.itcast.aop.datasource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@EnableAspectJAutoProxy // 开启自动代理
// 这里设置其加载优先级为最高,原因是他要在common.xml的事务管理器前,选择出要使用哪个数据源。
@Order(-9999)
public class DataSourceAspect {
// 前置通知 绑定service层下的所有方法
@Before("execution(* cn.itcast.service.*.*(..))")
public void beforeExecute(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println("------> 拦截的方法名 : " + name);
for (String key : ChooseDataSource.METHOD_TYPE_MAP.keySet()) {
for (String type : ChooseDataSource.METHOD_TYPE_MAP.get(key)) {
// 这里根据拦截到的方法头,来选择数据源
if(name.startsWith(type)){
DataSourceHandler.putDataSource(key);
System.out.println("---------> 获取当前使用的数据库连接池 : " + key);
break;
}
}
}
}
}
数据源处理类,用于存放选择出的数据源:DataSourceHandler
其中使用到了ThreadLocal类
package cn.itcast.aop.datasource;
public class DataSourceHandler {
// 数据源名称
// ThreadLocal类会为每个线程单独开辟一个空间, 用于存储线程需要的值。可以保证线程间互不干扰
// ThreadLocalMap中的Entry用于存储值,其中的key是弱引用,value是强引用————了解即可
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
/**
* 从数据源AOP中,根据拦截的方法,来判断使用的数据源的名称,然后存储到ThreadLocal里面
*/
public static void putDataSource(String datasource) {
holder.set(datasource);
}
/**
* 从holer中获取数据源字符串
*/
public static String getDataSource() {
return holder.get();
}
}
数据源选择类:ChooseDataSource
该类继承自AbstractRoutingDataSource。
重写其中的抽象方法:determineCurrentLookupKey——获取当前使用的数据源的名称
setMethodType方法中的参数,通过datasource.xml被注入进来
package cn.itcast.aop.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 选择数据源
*/
public class ChooseDataSource extends AbstractRoutingDataSource {
// 这个静态的Map变量存储 以xxx为开头的方法,对应的数据源
public static Map<String, List<String>> METHOD_TYPE_MAP = new HashMap<String, List<String>>();
/**
* 实现父类中的抽象方法,获取数据源名称
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHandler.getDataSource();
}
// 设置方法名前缀对应的数据源
// 这里的 map的参数,在datasource.xml中,被注入进来
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.isEmpty(type)) {
v.add(type);
}
}
METHOD_TYPE_MAP.put(key, v);
//key -- read , write ; value --> [get,select,count,list,query,find]
}
System.out.println("METHOD_TYPE_MAP : "+METHOD_TYPE_MAP);
}
}
这里如果还看不懂的话,可以下载文章下方的源码,使用Ctrl+B源码追踪进行理解
链接:https://pan.baidu.com/s/1pV-r3ukA-FLmHPshkZpp9g
提取码:kvdz
视频链接地址:
https://www.bilibili.com/video/BV1UQ4y1P7Xr?p=117&spm_id_from=pageDriver