SpringBoot+MyBatis+AOP实现读写分离的示例代码

这里就详细讲解一下"SpringBoot+MyBatis+AOP实现读写分离"的完整攻略。本文会介绍什么是读写分离,如何使用SpringBoot、Mybatis和AOP实现读写分离,以及两个示例说明。

什么是读写分离

首先,我们需要了解一下什么是读写分离。在高并发的系统中,读取数据库的操作通常是多余写入的操作的。因此,将查询请求分发到只读数据库,减少了对主数据库的压力,提高了系统的可扩展性,稳定性和性能。这就是所谓的读写分离。通常一个复杂的系统,前端的请求经过负载均衡后,将查询请求转发到从数据库,写操作转发到主数据库。

实现读写分离

接下来,我们来介绍一下如何使用SpringBoot、Mybatis和AOP实现读写分离。

我们先定义两个数据库,一个主数据库master,一个从数据库slave。在SpringBoot的配置文件中,我们需要配置主数据库和从数据库的数据源。在这里我们使用Druid连接池,配置如下:

spring:
  datasource:
    master:
      url: jdbc:mysql://localhost:3306/db?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave:
      url: jdbc:mysql://localhost:3307/db?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver

接下来,我们使用Mybatis作为数据源,并添加读写分离的AOP切面。

首先,我们需要定义两个数据源。在此,我们可以利用Spring Boot提供的一个注解:@Primary来标识默认数据源,如下:

@Configuration
@MapperScan(basePackages= {"com.example.test.mapper"}, sqlSessionFactoryRef = "testSqlSessionFactory")
public class DataSourceConfig {
    @Bean(name = "masterDataSource")
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource.master")
        public DataSource masterDataSource() {
            return DruidDataSourceBuilder.create().build();
        }

        @Bean(name = "slaveDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.slave")
        public DataSource slaveDataSource() {
            return DruidDataSourceBuilder.create().build();
        }
}

注:需要先在pom.xml中引入druid依赖

定义完两个数据源后,在此基础上定义SqlSessionFactory用于执行SQL语句。我们需要创建两个SqlSessionFactory,分别使用主从两个数据源。如下:

@Bean(name = "testSqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource,@Qualifier("slaveDataSource") DataSource slaveDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        MultipleDataSource multipleDataSource = new MultipleDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>(2);
        targetDataSources.put(DbType.MASTER.name(), masterDataSource);
        targetDataSources.put(DbType.SLAVE.name(), slaveDataSource);
        //设置数据源
        multipleDataSource.setTargetDataSources(targetDataSources);
        //设置默认数据源
        multipleDataSource.setDefaultTargetDataSource(masterDataSource);
        bean.setDataSource(multipleDataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));//因为同时使用了多个sqlSessionFactory,所以不加这行会出现找不到mapper文件夹的错误
        return bean.getObject();
    }

最后一步是定义AOP切面,用于动态决定到哪个数据源中进行读/写操作。我们定义一个DataSourceAspect类,利用@Around注解定义具体的切面函数,代码如下:

@Aspect
@Component
public class DataSourceAspect {
    @Around("execution(* com.example.test.mapper..*.*(..))")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) point.getSignature();

        //获取目标函数上的注解
        if (methodSignature.getMethod().isAnnotationPresent(DataSource.class)
                && methodSignature.getMethod().getDeclaringClass().isAnnotationPresent(DataSource.class)) {
            DataSource methodAnno = methodSignature.getMethod().getAnnotation(DataSource.class);
            DataSource classAnno = methodSignature.getMethod().getDeclaringClass().getAnnotation(DataSource.class);
            if (methodAnno != null)
                DynamicDataSourceContextHolder.setDataSource(methodAnno.value().name(), null);
            else if (classAnno != null)
                DynamicDataSourceContextHolder.setDataSource(classAnno.value().name(), null);
            //当前线程执行目标方法
            Object result = point.proceed();
            //情况本地数据源设置,将数据源设置为默认
            DynamicDataSourceContextHolder.clearDataSource();
            return result;
        }

        Class<?> clazz = point.getTarget().getClass();
        DataSource ds = clazz.getAnnotation(DataSource.class);
        if (ds != null) {
            DynamicDataSourceContextHolder.setDataSource(ds.value().name(), null);
        }
        Object result = point.proceed();
        DynamicDataSourceContextHolder.clearDataSource();

        return result;
    }
}

这个切面函数是基于方法上的@DataSource注解来选择数据源的。如果方法注解存在,则使用方法注解指定的数据源,否则,使用类注解指定的数据源。如果一个方法既没有注解也没有在类上注解,则使用默认数据源。

两个示例

下面,本文以添加数据和获取数据两个示例来演示读写分离。

示例1:添加数据

首先我们在mapper中定义一个添加数据的函数和一个查询数据的函数,代码如下:

public interface DemoMapper {
    int insert(Demo record);
    @DataSource(DbType.SLAVE)
    List<Demo> listDemosSelective(Demo record);
}

在上述代码中,我们为listDemosSelective函数添加了@DataSource(DbType.SLAVE)注解,用于向从库中执行查询操作。如下:

@Service
public class DemoService {
    @Autowired
    DemoMapper demoMapper;

    @DataSource(DbType.MASTER)
    public boolean insert(Demo demo){
        int result = demoMapper.insert(demo);
        if (result > 0)
            return true;
        return false;
    }
    @DataSource(DbType.SLAVE)
    public List<Demo> listDemosSelective(Demo record){
        return demoMapper.listDemosSelective(record);
    }
}

DemoService中,为了向主数据源中添加数据,我们为insert函数指定了@DataSource(DbType.MASTER)注解。同时使用了@DataSource(DbType.SLAVE)注解来向从库查询数据。

示例2:获取数据

我们还可以添加一个简单的API,用于获取数据。代码如下:

@SpringBootApplication
@MapperScan(basePackages = "com.example.test.mapper")
@RestController
public class TestApplication {
    @Autowired
    DemoService demoService;
    @GetMapping("/listDemo")
    public List<Demo> listDemo(){
        Demo demo = new Demo();
        demo.setId(1);
        return demoService.listDemosSelective(demo);
    }

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}

在上述代码中,API是使用GET方法实现的,调用了DemoService中的listDemosSelective函数,用于从从库中查询数据。

以上就是一个简单的例子,演示了Spring Boot,Mybatis和AOP实现读写分离的原理和实现方法。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot+MyBatis+AOP实现读写分离的示例代码 - Python技术站

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

相关文章

  • Spring boot security权限管理集成cas单点登录功能的实现

    关于“Spring Boot Security权限管理集成CAS单点登录功能的实现”的攻略,我从以下几个方面来讲解: 环境搭建 CAS Server的配置和部署 Spring Boot的集成与配置 认证和授权的实现 案例演示 环境搭建 这一步骤需要我们准备好以下的工具和环境: JDK 1.8 或以上版本 Maven 3.x CAS Server 5.x Sp…

    Java 2023年6月3日
    00
  • java面试题之数组中的逆序对

    当我们在面试Java开发工程师时,通常会涉及到一些算法和数据结构知识。本文针对“数组中的逆序对”这道Java面试题,提供一份详细的攻略。 什么是数组中的逆序对? 数组中的逆序对指的是数组中左边的数比右边的数大,这样的一对数称为逆序对。 比如,对于数组[2, 4, 1, 3, 5],该数组中的逆序对为(2, 1),(4, 1),(4, 3)。 如何求解数组中的…

    Java 2023年5月19日
    00
  • java 如何给对象中的包装类设置默认值

    Java中的包装类(Wrapper Class)是为了让基本数据类型(int、double、char等)具有面向对象的特性而出现的。在Java中,包装类和基本数据类型之间可以进行自动装箱和自动拆箱的转换,方便了编程的过程。在某些情况下,我们需要给对象中的包装类设置默认值,下面是详细讲解“Java 如何给对象中的包装类设置默认值”的攻略。 1. 给包装类设置初…

    Java 2023年5月26日
    00
  • JDBCTM 指南:入门3 – DriverManager

    下面是详细讲解“JDBCTM 指南:入门3 – DriverManager”的完整攻略。 JDBCTM 指南:入门3 – DriverManager 在本文中,我们将介绍JDBC中的DriverManager类,它是Java SQL API的一个基本组件,用于管理数据库驱动程序。 什么是 DriverManager DriverManager是Java提供的…

    Java 2023年6月16日
    00
  • java.lang.Runtime.exec的左膀右臂:流输入和流读取详解

    Java提供了Runtime.exec()方法来启动一个新进程。该方法可以返回Process对象,通过该对象可以控制和管理子进程的输入、输出以及错误流。这个方法中的参数是一个字符串,它描述了一个shell命令,应该如何来运行这个新的子进程。 为了更好地使用exec()方法,在使用exec()的时候,我们应该学会: 1.正确处理进程输出 2.合并输出流,正确地…

    Java 2023年5月26日
    00
  • Java代码中4种字符串拼接方式分析

    Java代码中4种字符串拼接方式分析 在Java开发中,我们经常需要进行字符串的拼接操作。本文将详细介绍Java代码中的4种字符串拼接方式,包括StringBuilder、StringBuffer、String.format()和”+”拼接方式,并分析它们之间的优缺点。 1. StringBuilder StringBuilder是一个可变的字符串类,它提供…

    Java 2023年5月26日
    00
  • 玩转SpringBoot中的那些连接池(小结)

    玩转SpringBoot中的那些连接池(小结) 连接池是每个Web应用中必不可少的组件,Spring Boot中默认提供了大量连接池实现,例如HikariCP、Tomcat JDBC、Commons DBCP2等等。本文将介绍这些连接池的优缺点,帮助开发者选择适合自己应用的连接池。 HikariCP HikariCP是Spring Boot中默认的连接池实现…

    Java 2023年5月19日
    00
  • 浅析JSP的9大内置对象和4大作用域对象

    浅析JSP的9大内置对象和4大作用域对象 JSP(JavaServer Pages)是一种基于Java语言,用于创建动态web页面的技术。当客户端请求JSP页面时,Tomcat服务器会解析JSP页面,将其中的Java代码转化为标准的Servlet,并执行Servlet,最后将生成的HTML页面返回给客户端。 在JSP页面中,有9大内置对象和4大作用域对象,这…

    Java 2023年6月15日
    00
合作推广
合作推广
分享本页
返回顶部