118分布式电商项目 - 读写分离(使用Spring基于应用层实现)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 118分布式电商项目 - 读写分离(使用Spring基于应用层实现)

原理

在进入Service之前,使用AOP来做出判断,是使用写库还是读库,判断依据可以根据方法名判断,比如说以query、find、get等开头的就走读库,其他的走写库。

DynamicDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可
 * 
 * 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。
 * 
 * @author yangbingwen
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
    @Override
    protected Object determineCurrentLookupKey() {
        // 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
        return DynamicDataSourceHolder.getDataSourceKey();
    }
}

DynamicDataSourceHolder

/**
 * 
 * 使用ThreadLocal技术来记录当前线程中的数据源的key
 * 
 * @author yangbingwen
 *
 */
public class DynamicDataSourceHolder {
    //写库对应的数据源key
    private static final String MASTER = "master";
    //读库对应的数据源key
    private static final String SLAVE = "slave";
    //使用ThreadLocal记录当前线程的数据源key
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();
    /**
     * 设置数据源key
     * @param key
     */
    public static void putDataSourceKey(String key) {
        holder.set(key);
    }
    /**
     * 获取数据源key
     * @return
     */
    public static String getDataSourceKey() {
        return holder.get();
    }
    /**
     * 标记写库
     */
    public static void markMaster(){
        putDataSourceKey(MASTER);
    }
    /**
     * 标记读库
     */
    public static void markSlave(){
        putDataSourceKey(SLAVE);
    }
}

DataSourceAspect

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
/**
 * 定义数据源的AOP切面,通过该Service的方法名判断是应该走读库还是写库
 * 
 * @author yangbingwen
 *
 */
public class DataSourceAspect {
    /**
     * 在进入Service方法之前执行
     * 
     * @param point 切面对象
     */
    public void before(JoinPoint point) {
        // 获取到当前执行的方法名
        String methodName = point.getSignature().getName();
        if (isSlave(methodName)) {
            // 标记为读库
            DynamicDataSourceHolder.markSlave();
        } else {
            // 标记为写库
            DynamicDataSourceHolder.markMaster();
        }
    }
    /**
     * 判断是否为读库
     * 
     * @param methodName
     * @return
     */
    private Boolean isSlave(String methodName) {
        // 方法名以query、find、get开头的方法名走从库
        return StringUtils.startsWithAny(methodName, "query", "find", "get");
    }
}

配置2个数据源

1.jdbc.properties
jdbc.master.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://127.0.0.1:3306/mybatis_1128?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.master.username=root
jdbc.master.password=123456
jdbc.slave01.driver=com.mysql.jdbc.Driver
jdbc.slave01.url=jdbc:mysql://127.0.0.1:3307/mybatis_1128?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.slave01.username=root
jdbc.slave01.password=123456
定义连接池
<!-- 配置连接池 -->
  <bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource"
    destroy-method="close">
    <!-- 数据库驱动 -->
    <property name="driverClass" value="${jdbc.master.driver}" />
    <!-- 相应驱动的jdbcUrl -->
    <property name="jdbcUrl" value="${jdbc.master.url}" />
    <!-- 数据库的用户名 -->
    <property name="username" value="${jdbc.master.username}" />
    <!-- 数据库的密码 -->
    <property name="password" value="${jdbc.master.password}" />
    <!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->
    <property name="idleConnectionTestPeriod" value="60" />
    <!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->
    <property name="idleMaxAge" value="30" />
    <!-- 每个分区最大的连接数 -->
    <property name="maxConnectionsPerPartition" value="150" />
    <!-- 每个分区最小的连接数 -->
    <property name="minConnectionsPerPartition" value="5" />
  </bean>
  <!-- 配置连接池 -->
  <bean id="slave01DataSource" class="com.jolbox.bonecp.BoneCPDataSource"
    destroy-method="close">
    <!-- 数据库驱动 -->
    <property name="driverClass" value="${jdbc.slave01.driver}" />
    <!-- 相应驱动的jdbcUrl -->
    <property name="jdbcUrl" value="${jdbc.slave01.url}" />
    <!-- 数据库的用户名 -->
    <property name="username" value="${jdbc.slave01.username}" />
    <!-- 数据库的密码 -->
    <property name="password" value="${jdbc.slave01.password}" />
    <!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->
    <property name="idleConnectionTestPeriod" value="60" />
    <!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->
    <property name="idleMaxAge" value="30" />
    <!-- 每个分区最大的连接数 -->
    <property name="maxConnectionsPerPartition" value="150" />
    <!-- 每个分区最小的连接数 -->
    <property name="minConnectionsPerPartition" value="5" />
  </bean>
定义DataSource
<!-- 定义数据源,使用自己实现的数据源 -->
  <bean id="dataSource" class="cn.itcast.usermanage.spring.DynamicDataSource">
    <!-- 设置多个数据源 -->
    <property name="targetDataSources">
      <map key-type="java.lang.String">
        <!-- 这个key需要和程序中的key一致 -->
        <entry key="master" value-ref="masterDataSource"/>
        <entry key="slave" value-ref="slave01DataSource"/>
      </map>
    </property>
    <!-- 设置默认的数据源,这里默认走写库 -->
    <property name="defaultTargetDataSource" ref="masterDataSource"/>
  </bean>

配置事务管理以及动态切换数据源切面

定义事务管理器
<!-- 定义事务管理器 -->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>
定义事务策略
<!-- 定义事务策略 -->
  <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
      <!--定义查询方法都是只读的 -->
      <tx:method name="query*" read-only="true" />
      <tx:method name="find*" read-only="true" />
      <tx:method name="get*" read-only="true" />
      <!-- 主库执行操作,事务传播行为定义为默认行为 -->
      <tx:method name="save*" propagation="REQUIRED" />
      <tx:method name="update*" propagation="REQUIRED" />
      <tx:method name="delete*" propagation="REQUIRED" />
      <!--其他方法使用默认事务策略 -->
      <tx:method name="*" />
    </tx:attributes>
  </tx:advice>
定义切面
<!-- 定义AOP切面处理器 -->
  <bean class="cn.itcast.usermanage.spring.DataSourceAspect" id="dataSourceAspect" />
  <aop:config>
    <!-- 定义切面,所有的service的所有方法 -->
    <aop:pointcut id="txPointcut" expression="execution(* xx.xxx.xxxxxxx.service.*.*(..))" />
    <!-- 应用事务策略到Service切面 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    <!-- 将切面应用到自定义的切面处理器上,-9999保证该切面优先级最高执行 -->
    <aop:aspect ref="dataSourceAspect" order="-9999">
      <aop:before method="before" pointcut-ref="txPointcut" />
    </aop:aspect>
  </aop:config>


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6天前
|
XML Java 数据格式
Spring 项目如何使用AOP
Spring 项目如何使用AOP
19 2
|
6天前
|
Java Spring
Spring boot项目如何发送邮件
Spring boot项目如何发送邮件
16 2
|
12天前
|
消息中间件 Java 关系型数据库
Spring事务与分布式事务
这篇文档介绍了事务的概念和数据库事务的ACID特性:原子性、一致性、隔离性和持久性。在并发环境下,事务可能出现更新丢失、脏读和不可重复读等问题,这些问题通过设置事务隔离级别(如读未提交、读已提交、可重复读和序列化)来解决。Spring事务传播行为有七种模式,影响嵌套事务的执行方式。`@Transactional`注解用于管理事务,其属性包括传播行为、隔离级别、超时和只读等。最后提到了分布式事务,分为跨库和跨服务两种情况,跨服务的分布式事务通常通过最终一致性策略,如消息队列实现。
|
12天前
|
Java API 数据安全/隐私保护
【亮剑】如何在Java项目中结合Spring框架实现邮件发送功能
【4月更文挑战第30天】本文介绍了如何在Java项目中结合Spring框架实现邮件发送功能。首先,需在`pom.xml`添加Spring和JavaMail依赖。然后,在`applicationContext.xml`配置邮件发送器,包括SMTP服务器信息。接着,创建一个使用依赖注入的`EmailService`类,通过`JavaMailSender`发送邮件。最后,调用`EmailService`的`sendSimpleEmail`方法即可发送邮件。最佳实践包括:使用配置管理敏感信息,利用`MimeMessage`构造复杂邮件,异常处理和日志记录,以及在大量发送时考虑使用邮件队列。
|
16天前
|
Java Maven Docker
0.07 秒启动一个 SpringBoot 项目!Spring Native 很强!!
0.07 秒启动一个 SpringBoot 项目!Spring Native 很强!!
26 2
|
16天前
|
消息中间件 人工智能 Java
Spring Boot+RocketMQ 实现多实例分布式环境下的事件驱动
Spring Boot+RocketMQ 实现多实例分布式环境下的事件驱动
26 1
|
18天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
23天前
|
Java Linux 虚拟化
Docker 部署spring-boot项目(超详细 包括Docker详解、Docker常用指令整理等)
Docker 部署spring-boot项目(超详细 包括Docker详解、Docker常用指令整理等)
56 1
|
24天前
|
存储 Java Maven
江帅帅:Spring Boot 底层级探索系列 01- 搭建项目
江帅帅:Spring Boot 底层级探索系列 01- 搭建项目
35 0
江帅帅:Spring Boot 底层级探索系列 01- 搭建项目
|
28天前
|
缓存 Java Spring
单体项目中资源管理模块集成Spring Cache
该内容是关于将Spring Cache集成到资源管理模块以实现缓存同步的说明。主要策略包括:查询时添加到缓存,增删改时删除相关缓存。示例代码展示了@Service类中使用@Transactional和@Cacheable注解进行缓存操作,以及在RedisTemplate中处理缓存的示例。
24 5