使用springboot aop来实现读写分离和事物配置

yizhihongxing

首先,我们需要了解一下Spring AOP是什么,以及它是如何实现的。Spring AOP是基于JDK动态代理(基于接口)和CGLIB(基于类)实现的面向切面编程的一种框架。通过将横切逻辑与业务逻辑分离,可以更加灵活和方便地对系统进行管理,提高系统的可维护性、可扩展性和代码质量。

接下来,我们将使用Spring Boot AOP来实现读写分离和事务配置:

第一步:引入相关依赖

在pom.xml中加入以下依赖:

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

第二步:配置读写分离

在application.yml中进行如下配置:

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true
    username: root
    password: root
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      pool-name: DataCenter-HikariCP
    test:
      query: SELECT 1
  jpa:
    show-sql: false
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
        id:
          new_generator_mappings: true
        jdbc:
          fetch_size: 100
          batch_size: 50
      javax:
        persistence:
          query:
            timeout: 10000
  aop:
    proxy-target-class: true

第三步:编写切面逻辑

在Spring Boot项目中,使用@Aspect注解表明该类是切面类,然后再使用@Pointcut来定义切点,@Before、@After等注解来定义切面逻辑。

比如在我们的业务代码中,读操作以“find”开头,写操作以“save”、“delete”、“update”开头,所以我们可以使用正则表达式来匹配方法名,判断该方法是读操作还是写操作。

@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("execution(* com.example.demo.dao.*.find*(..))")
    public void readPointcut(){
    }

    @Pointcut("execution(* com.example.demo.dao.*.save*(..)) " +
            "|| execution(* com.example.demo.dao.*.delete*(..)) " +
            "|| execution(* com.example.demo.dao.*.update*(..))")
    public void writePointcut(){
    }

    @Before("readPointcut()")
    public void switchSlaveDataSource(JoinPoint point){
        //从库读取
        DBContextHolder.slave();
    }

    @Before("writePointcut()")
    public void switchMasterDataSource(JoinPoint point){
        //主库写入
        DBContextHolder.master();
    }
}

这里的DBContextHolder是根据线程来判断当前使用的是主库还是从库,我们也需要在代码中编写两个数据源的配置。

public class DBContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceKey(String dataSource){
        contextHolder.set(dataSource);
    }

    public static String getDataSourceKey(){
        return contextHolder.get();
    }

    public static void master(){
        setDataSourceKey("master");
    }

    public static void slave(){
        setDataSourceKey("slave");
    }

    public static void clear(){
        contextHolder.remove();
    }
}

第四步:配置事务管理器

在事务管理中,我们需要使用@Transactional注解来定义需要进行事务管理的方法。需要注意的是,事务管理只能应用在public方法上,并且它们必须是由其他对象调用的。

对于事务的配置,我们可以将事务管理器作为一个Bean来注入到Spring容器中。

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

    @Bean(name = "master")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource getMasterDataSource(){
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean(name = "slave")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource getSlaveDataSource(){
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean(name = "routingDataSource")
    public DataSource getRoutingDataSource(@Qualifier("master") DataSource masterDataSource,
                                            @Qualifier("slave") DataSource slaveDataSource){
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("slave", slaveDataSource);
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("routingDataSource") DataSource
                                                         routingDataSource) {
        return new DataSourceTransactionManager(routingDataSource);
    }
}

第五步:编写业务逻辑

最后,我们需要编写我们的业务逻辑代码。业务逻辑中的读操作以“find”开头,写操作以“save”、“delete”、“update”开头,方法名与切面逻辑中定义的正则表达式保持一致。

以简单的增删改查为例:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    @Override
    public void save(User user) {
        userRepository.save(user);
    }

    @Transactional
    @Override
    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }

    @Transactional
    @Override
    public void update(User user) {
        userRepository.save(user);
    }

    @Override
    public User findById(Long id) {
        return userRepository.findById(id).orElseThrow(() -> new RuntimeException("Not found!"));
    }
}

综上所述,我们通过引入相关依赖、配置读写分离和事务管理器、编写切面逻辑和业务逻辑,就可以实现Spring Boot AOP对读写分离和事务管理的配置。

示例一:实现读写分离

假设我们有一个User实体类和一个UserRepository,如下:

@Entity
@Table(name = "users")
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByUsername(String username);
}

我们可以使用以下测试类来测试读写分离是否配置成功:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class RoutingDataSourceTest {

    @Autowired
    private UserService userService;

    @Test
    public void test() {
        User user = new User();
        user.setUsername("test");
        user.setPassword("test");
        userService.save(user);
        for (int i = 0; i < 100; i++) {
            userService.findById(Long.valueOf(i+1));
        }
    }
}

在这个测试类中,我们通过在循环中调用findById方法,来检查读操作是否都是在从库上进行。

示例二:实现事务管理

我们可以通过在Service层加上@Transactional注解,来实现事务管理。比如:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    @Override
    public void save(User user) {
        userRepository.save(user);
    }
}

在这个示例中,我们对save方法加上了@Transactional注解,使其具有事务管理的功能。具体来说,当我们在执行该方法时,如果发现有任何一步操作失败,那么整个事务都会回滚。

再例如删除用户的操作:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    @Override
    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }
}

在这个示例中,我们同样使用了@Transactional注解来表明该方法是需要进行事务管理的。如果在执行删除操作时出现了任何异常,那么整个事务都会进行回滚,防止数据不一致的问题出现。

综上所述,我们通过在Service层中引入@Transactional注解,就可以将业务逻辑代码与事务管理逻辑代码分离开来,提高代码的可维护性和可扩展性。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:使用springboot aop来实现读写分离和事物配置 - Python技术站

(0)
上一篇 2023年5月22日
下一篇 2023年5月22日

相关文章

  • 简单讲解MySQL的数据库复制方法

    MySQL是一种开源关系型数据库管理系统,它的数据库复制功能可以将一个MySQL实例的数据拷贝到另外一个服务器实例上,从而对数据进行备份和灾备。 以下是MySQL数据库复制的方法: 主从复制 主从复制是MySQL中最常用的一种复制方式。它通过将一个MySQL服务器实例作为主服务器,将这个主服务器上的所有操作都复制到多个从服务器上的方式,来实现数据同步。 实现…

    database 2023年5月18日
    00
  • sqlserver关于分页存储过程的优化【让数据库按我们的意思执行查询计划】

    优化分页数目: 分页查询是非常常见的需求,对于业务需求比较频繁的分页查询操作,我们可以通过优化存储过程的方式提高查询效率。我们可以通过限制查询取值范围,并使用ROW_NUMBER函数,实现分页优化。 示例: CREATE PROCEDURE P_GetDataByPage ( @PageIndex INT = 1 , @PageSize INT = 10 )…

    database 2023年5月19日
    00
  • linux服务器中搭建redis6.0.7集群

    下面是详细的步骤: 1. 安装 Redis 6.0.7 首先,需要下载 Redis 6.0.7,可以从官网下载:https://redis.io/download 下载完成后,解压文件,然后进入解压文件目录,执行以下命令进行编译安装: make sudo make install 安装完成后,可以使用以下命令检查 Redis 是否安装成功: redis-se…

    database 2023年5月22日
    00
  • 一个php导出oracle库的php代码

    要导出Oracle库,需要使用PHP的OCI扩展。OCI扩展是Oracle提供的一个API,它允许PHP与Oracle数据库进行交互。下面是一个完整的攻略,用于编写PHP代码来导出Oracle库。 步骤一:安装OCI扩展 在使用OCI扩展之前,需要先安装它。可以通过以下几个步骤来安装OCI扩展。 下载并安装Oracle Instant Client。在安装过…

    database 2023年5月22日
    00
  • Oracle数据库恢复教程之resetlogs操作

    在这里我会给出关于 “Oracle数据库恢复教程之resetlogs操作” 的完整攻略。 1. 恢复概述 在进行resetlogs操作之前,我们需要对恢复的概念和过程有一个基本的认识。 在Oracle数据库中,恢复是指使用备份和日志文件将数据库恢复到某个时刻的过程。Oracle数据库有两种恢复方式:完全恢复和不完全恢复。完全恢复是指将数据库恢复到某个完整备份…

    database 2023年5月18日
    00
  • Go 模块在下游服务抖动恢复后CPU占用无法恢复原因

    这个问题可能与 Go 1.14 之前的阻塞调度器有关,因为该版本的调度器对于等待锁的 goroutine 并不会唤醒它们,而是使用竞争调用策略。这种现象可以导致资源瓶颈和延迟,以及对 CPU 的浪费。在 Go 1.14 中,调度器有所改进,优化了阻塞 goroutine 的唤醒逻辑,从而更好地处理资源瓶颈问题。 如果使用的是 Go 1.14 或更高版本,您可…

    database 2023年5月22日
    00
  • nodejs+socketio+redis实现前端消息实时推送

    nodejs+socketio+redis实现前端消息实时推送 1. 后端部分 发送redis消息 可以参考此篇实现(直接使用Jedis即可) http://www.cnblogs.com/binyue/p/4763352.html 2.后端部分: 接收redis消息 var redis; if(process.argv.length <= 2){ r…

    Redis 2023年4月11日
    00
  • Mysql索引面试题的小结

    如果你想学习如何回答Mysql索引面试题,那么你需要掌握什么是Mysql索引、如何创建索引、索引对查询性能的影响以及如何优化Mysql查询性能。以下是一些常见的Mysql索引面试题及其解答: 什么是Mysql索引? Mysql索引是一种特殊的数据结构,它可以帮助你快速地查找到数据库中的数据。它类似于图书馆的书目索引,当你要找到一本书时,只需要在索引中查找书名…

    database 2023年5月22日
    00
合作推广
合作推广
分享本页
返回顶部