SpringAOP实现Mysql读写分离超详细

图解读写分离

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

资源及解决方案来自b站黑马程序员

视频链接地址:
https://www.bilibili.com/video/BV1UQ4y1P7Xr?p=117&spm_id_from=pageDriver

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Binary H.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值