SpringBoot3+JPA+MySQL实现多数据源的读写分离(基于EntityManagerFactory)

news2024/12/27 12:53:12

1、简介

在Spring Boot中配置多个数据源并实现自动切换EntityManager,这里我编写了一个RoutingEntityManagerFactory和AOP(面向切面编程)的方式来实现。

这里我配置了两个数据源:primary和secondary,其中primary主数据源用来写入数据,secondary从数据源用来读取数据。

注意1: 使用Springboot3的读写分离,首先要保证主库和从库已经配置好了 数据同步,否则会导致数据不一致。
当然如果仅仅是测试的话,不同步就不影响了

注意2: SpringBoot3的JDK不能低于17
我使用的JDK版本

openjdk version "20.0.2" 2023-07-18
OpenJDK Runtime Environment (build 20.0.2+9-78)
OpenJDK 64-Bit Server VM (build 20.0.2+9-78, mixed mode, sharing)

2、数据库说明

这里我使用了本机的同一个mysql上的两个不同的数据库,在实际环境中这两个库应该是分别处于不同的服务器上,同时应该已经配置好了主从复制或主备,保证了数据的一致性,不然读写分离就没有意义了

数据库名称JDBC-URL说明
primary_dbjdbc:mysql://localhost:3306/primary_db这个是主库,设计为写入数据库
secondary_dbjdbc:mysql://localhost:3306/secondary_db这个是从库,设计为读取数据库

提示:虽然这里我使用的是MySQL数据库,但是在实际的开发过程中,可以替换为Postgresql或oracle等其他关系型数据库,只需要做很小的改动就可以使用了

3、准备工作

3.1、添加依赖

在你的项目里添加如下依赖

<!-- Spring Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- AOP -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- JPA -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- MySQL -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.3.0</version>
</dependency>


<!-- druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.22</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

完整的pom.xml文件

下面是我在编写代码时候的完整的pom.xml文件内容

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ts</groupId>
    <artifactId>springboot3-jpa-read-write-separation-mysql2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot3-jpa-read-write-separation-mysql2</name>
    <description>springBoot3 + JPA + MySQL 实现读写分离</description>
    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Starter AOP for @Transactional annotations -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- MySQL Database -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.3.0</version>
        </dependency>


        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.22</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3.2、准备的SQL

-- 用户表
CREATE TABLE userinfo (
  id int AUTO_INCREMENT PRIMARY KEY,
  name varchar(50),
  age smallint,
  gender varchar(3),
  entry_date timestamp
);

-- 测试数据
INSERT INTO userinfo(name,age,gender,entry_date) VALUES
('刘峰',24,'男','2024-02-28 12:01:01'),
('舒航',25,'女','2024-02-28 12:01:01'),
('张明',26,'男','2024-02-29 12:01:01'),
('徐媛',28,'女','2024-02-29 12:01:01'),
('舒莱',29,'女','2023-07-30 12:01:01'),
('唐力',30,'男','2023-08-05 12:01:01'),
('唐莉',29,'女','2023-06-05 12:01:01'),
('王乐',27,'男','2023-07-01 12:01:01'),
('张萌',32,'女','2023-07-02 12:01:01'),
('程媛',25,'女','2023-08-02 12:01:01'),
('嫪玉',35,'女','2023-08-01 10:00:00'),
('贾茹',26,'女','2023-10-03 12:01:01'),
('胡安',25,'男','2023-11-09 12:01:01'),
('刘伟',27,'男','2023-07-09 12:01:01');

3.3、application.yml中定义多数据源配置

application.yml中定义每个数据源的连接信息。

spring:
  jpa:
    database: mysql
    show-sql: true

  datasource:
    #主数据源 , 写入数据库
    primary:
      url: jdbc:mysql://localhost:3306/primary_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root

    # 次数据源 , 读取数据库
    secondary:
      url: jdbc:mysql://localhost:3306/secondary_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root


4、创建动态数据源

4.1、主数据源配置

package com.ts.config;


import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
 *  主数据源配置
 * @author zhouq
 * @since 15:26 2024/03/21
 **/
@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(
        basePackages = "com.ts.service",
        entityManagerFactoryRef = "primaryEntityManagerFactory"
)
public class PrimaryDataSourceConfig {


    @Autowired
    private JpaProperties jpaProperties;



    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        //return DataSourceBuilder.create().build();
        return new DruidDataSource();
    }

    //LocalContainerEntityManagerFactoryBean
    @Bean
    public Object primaryEntityManagerFactory(DataSource primaryDataSource,
                                                                            JpaProperties primaryJpaProperties) {
        EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(primaryJpaProperties);
        return builder.dataSource(primaryDataSource).packages("com.ts.model")
                .persistenceUnit("primaryDataSource").build();
    }

    private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
        JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
        return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null);
    }

    private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
        // ... map JPA properties as needed
        return new HibernateJpaVendorAdapter();
    }


}

4.2、次要数据源配置

package com.ts.config;


import com.alibaba.druid.pool.DruidDataSource;
import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
 * 次要数据源配置
 * @author zhouq
 * @since 15:25 2024/03/21
 **/
@Configuration
@EnableJpaRepositories(
        basePackages = "com.ts.service",
        entityManagerFactoryRef = "secondaryEntityManagerFactory"
)
public class SecondaryDataSourceConfig {

    @Autowired
    private JpaProperties jpaProperties;


    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        //return DataSourceBuilder.create().build();
        return new DruidDataSource();
    }

    // LocalContainerEntityManagerFactoryBean
    @Bean
    public Object secondaryEntityManagerFactory(DataSource secondaryDataSource,
                                                                              JpaProperties secondaryJpaProperties) {
        EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(secondaryJpaProperties);
        return builder.dataSource(secondaryDataSource).packages("com.ts.model")
                .persistenceUnit("secondaryDataSource").build();
    }

    private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
        JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
        return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null);
    }

    private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
        // ... map JPA properties as needed
        return new HibernateJpaVendorAdapter();
    }
    
}

5、全局EntityManager配置

5.1、创建EntityManager线程工具类

EntityManagerContextHolder是一个用来保存当前线程的EntityManager名称的工具类。

package com.ts.config;


/**
 * 用来保存当前线程的EntityManagerFactory名称 的工具类
 * @author zhouq
 * @since 14:29 2024/03/20
 **/
public class EntityManagerContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setEntityManagerFactoryType(String entityManagerFactoryType) {
        contextHolder.set(entityManagerFactoryType);
    }

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

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

5.2、创建数RoutingEntityManagerFactory路由(管理多个EntityManager)

package com.ts.config;

import jakarta.persistence.*;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.metamodel.Metamodel;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import java.util.Map;


/**
 *  动态切换EntityManagerFactory
 *  EntityManagerFactory路由类
 * @author zhouq
 * @since 14:54 2024/03/21
 **/
public class RoutingEntityManagerFactory implements EntityManagerFactory{
    @Nullable
    private Map<String, Object> targetEntityManagerFactorys;
    @Nullable
    private Object defaultTargetEntityManagerFactory;

    @Nullable
    public Map<String, Object> getTargetEntityManagerFactorys() {
        return targetEntityManagerFactorys;
    }

    public void setTargetEntityManagerFactorys(@Nullable Map<String, Object> targetEntityManagerFactorys) {
        this.targetEntityManagerFactorys = targetEntityManagerFactorys;
    }

    @Nullable
    public Object getDefaultTargetEntityManagerFactory() {
        return defaultTargetEntityManagerFactory;
    }

    public void setDefaultTargetEntityManagerFactory(@Nullable Object defaultTargetEntityManagerFactory) {
        this.defaultTargetEntityManagerFactory = defaultTargetEntityManagerFactory;
    }

    public String determineCurrentLookupKey() {
        // 如果不使用读写分离,这里也可以做一个简单的负载均衡策略
        String entityManagerFactoryType = EntityManagerContextHolder.getEntityManagerFactoryType();
        //System.out.println("当前使用的数据源是:" + entityManagerFactoryType);
        return entityManagerFactoryType;
    }

    public EntityManagerFactory determineTargetEntityManagerFactory() {
        Assert.notNull(this.targetEntityManagerFactorys, "targetEntityManagerFactory router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        EntityManagerFactory entityManagerFactory = (EntityManagerFactory)this.targetEntityManagerFactorys.get(lookupKey);
        if (entityManagerFactory == null && lookupKey == null) {
            entityManagerFactory = (EntityManagerFactory)this.defaultTargetEntityManagerFactory;
        }

        if (entityManagerFactory == null) {
            throw new IllegalStateException("Cannot determine target EntityManagerFactory for lookup key [" + lookupKey + "]");
        } else {
            return entityManagerFactory;
        }
    }

    //---------------------下面的方法都是EntityManagerFactory接口的实现------------------------------------
    @Override
    public EntityManager createEntityManager() {
        return this.determineTargetEntityManagerFactory().createEntityManager();
    }

    @Override
    public EntityManager createEntityManager(Map map) {
        return this.determineTargetEntityManagerFactory().createEntityManager(map);
    }

    @Override
    public EntityManager createEntityManager(SynchronizationType synchronizationType) {
        return this.determineTargetEntityManagerFactory().createEntityManager(synchronizationType);
    }

    @Override
    public EntityManager createEntityManager(SynchronizationType synchronizationType, Map map) {
        return this.determineTargetEntityManagerFactory().createEntityManager(synchronizationType,map);
    }

    @Override
    public CriteriaBuilder getCriteriaBuilder() {
        return this.determineTargetEntityManagerFactory().getCriteriaBuilder();
    }

    @Override
    public Metamodel getMetamodel() {
        return this.determineTargetEntityManagerFactory().getMetamodel();
    }

    @Override
    public boolean isOpen() {
        return this.determineTargetEntityManagerFactory().isOpen();
    }

    @Override
    public void close() {
        this.determineTargetEntityManagerFactory().close();
    }

    @Override
    public Map<String, Object> getProperties() {
        return this.determineTargetEntityManagerFactory().getProperties();
    }

    @Override
    public Cache getCache() {
        return this.determineTargetEntityManagerFactory().getCache();
    }

    @Override
    public PersistenceUnitUtil getPersistenceUnitUtil() {
        return this.determineTargetEntityManagerFactory().getPersistenceUnitUtil();
    }

    @Override
    public void addNamedQuery(String s, Query query) {
        this.determineTargetEntityManagerFactory().addNamedQuery(s,query);
    }

    @Override
    public <T> T unwrap(Class<T> aClass) {
        return this.determineTargetEntityManagerFactory().unwrap(aClass);
    }

    @Override
    public <T> void addNamedEntityGraph(String s, EntityGraph<T> entityGraph) {
        this.determineTargetEntityManagerFactory().addNamedEntityGraph(s,entityGraph);
    }
}

5.3、创建EntityManagerFactory全局配置类

package com.ts.config;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 *  全局配置实体管理工厂 EntityManagerFactory
 *  配置EntityManagerFactory,确定使用的EntityManagerFactory
 * @author zhouq
 * @since 10:59 2024/03/21
 **/
@Configuration
public class EntityManagerFactoryConfig {

    @Autowired
    @Qualifier("primaryEntityManagerFactory")
    private Object primaryEntityManagerFactory;

    @Autowired
    @Qualifier("secondaryEntityManagerFactory")
    private Object secondaryEntityManagerFactory;


    @Bean
    public EntityManagerFactory entityManager(){
        String dataSourceType = EntityManagerContextHolder.getEntityManagerFactoryType();

        RoutingEntityManagerFactory routingEntityManagerFactory = new RoutingEntityManagerFactory();

        Map<String, Object> targetEntityManagerFactorys = new HashMap<String, Object>();

        targetEntityManagerFactorys.put("primary", primaryEntityManagerFactory);  //主实体管理工厂
        targetEntityManagerFactorys.put("secondary", secondaryEntityManagerFactory); //次要实体管理工厂

        routingEntityManagerFactory.setTargetEntityManagerFactorys(targetEntityManagerFactorys);// 配置实体管理工厂
        routingEntityManagerFactory.setDefaultTargetEntityManagerFactory(primaryEntityManagerFactory);// 设置默认实体管理工厂

        return routingEntityManagerFactory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager tm = new JpaTransactionManager();
        tm.setEntityManagerFactory(entityManager());
        return tm;
    }

}

6、使用AOP实现EntityManager自动切换

6.1、创建AOP切面注入EntityManager类型

通过AOP在方法执行前设置EntityManagerFactory,并在方法执行后清除。

package com.ts.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 使用AOP注入EntityManager类型
 * 通过AOP在方法执行前设置EntityManager,并在方法执行后清除。
 * @author zhouq
 * @since 14:25 2024/03/20
 **/
@Aspect
@Component
public class EntityManagerFactoryAspect {

    @Before("@annotation(dataSource)")
    public void switchDataSource(JoinPoint point, DataSourceSwitch dataSource) {
        //log.info("使用的数据源是:" + dataSource.value());
        System.out.println("使用的数据源是:" + dataSource.value());
        EntityManagerContextHolder.setEntityManagerFactoryType(dataSource.value());
    }

    @After("@annotation(dataSource)")
    public void restoreDataSource(JoinPoint point, DataSourceSwitch dataSource) {
        EntityManagerContextHolder.clearEntityManagerFactoryType();
    }
}

6.2、创建自定义注解用于标注所使用的的

DataSourceSwitch是一个自定义注解,用来标识需要切换数据源的方法。

package com.ts.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解,用来标识需要切换数据源的方法
 * @author zhouq
 * @since 14:31 2024/03/20
 **/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceSwitch {
    String value() default "primary";
}

7、创建实体类

package com.ts.model;

import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.Accessors;
import java.util.Date;


@Setter
@Getter
@Accessors(chain = true)
@AllArgsConstructor // 全参构造方法
@NoArgsConstructor // 无参构造方法
//@RequiredArgsConstructor
@ToString
@Entity
@Table(name = "userinfo")
public class Userinfo implements java.io.Serializable{

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    private String name;
    private int age;
    private String gender;
    private Date entry_date;

}

8、创建Service

创建UserService接口

package com.ts.service;

import com.ts.model.Userinfo;
import java.util.List;


public interface UserService {
    /**
     *  获取所有
     * @author zhouq
     * @since 13:33 2024/03/20
     * @return java.util.List<com.ts.model.Userinfo>
     **/
    List<Userinfo> getAll();

    /**
     * 查找包含姓名的用户
     * @author zhouq
     * @since 13:34 2024/03/20
     * @param name 姓名
     * @return java.util.List<com.ts.model.Userinfo>
     **/
    List<Userinfo> queryWithName(String name);


    /**
     *  添加用户
     * @author zhouq
     * @since 13:39 2024/03/20
     * @return java.util.List<com.ts.model.Userinfo>
     **/
    void addUser(Userinfo userinfo);

    /**
     * 按照id删除
     * @author zhouq
     * @since 13:55 2024/03/20
     **/
    void deleteUser(int id);

}

创建UserService接口实现类

package com.ts.service.impl;

import com.ts.config.DataSourceSwitch;
import com.ts.model.Userinfo;
import com.ts.service.UserService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 *  基于EntityManager的实现
 *  这里使用 primary主数据源写入数据,secondary从数据源读取数据
 * @author zhouq
 * @since 11:08 2024/03/21
 **/
@Service
public class UserServiceEntityManagerImpl implements UserService {

    @Autowired
    private EntityManager entityManager;


    /**
     * 获取所有用户
     * 这里使用   @DataSourceSwitch("secondary")  指定 读取的数据源
     * @author zhouq
     * @since 11:21 2024/03/21
     * @return java.util.List<com.ts.model.Userinfo>
     **/
    @Override
    @DataSourceSwitch("secondary")
    public List<Userinfo> getAll() {
        return  entityManager.createQuery("select u from Userinfo u").getResultList();
    }

    /**
     * 查询用户,模糊查询
     * @author zhouq
     * @since 11:20 2024/03/21
     * @return java.util.List<com.ts.model.Userinfo>
     **/
    @Override
    @DataSourceSwitch("secondary")
    public List<Userinfo> queryWithName(String name) {
        Query query = entityManager.createQuery("select u from Userinfo u where u.name like :name");
        query.setParameter("name", "%" + name +"%");

        return query.getResultList();
    }


    /**
     * 添加用户
     * 这里使用   @DataSourceSwitch("primary")  指定写入的数据源
     * @author zhouq
     * @since 11:20 2024/03/21
     **/
    @Override
    @DataSourceSwitch("primary")  //或者 @DataSourceSwitch
    @Modifying
    @Transactional
    public void addUser(Userinfo userinfo) {

        //使用本地原生SQL查询
        Query nativeQuery = entityManager.createNativeQuery("insert into Userinfo(id,name,age,gender) values(:id,:name,:age,:gender)");
        nativeQuery.setParameter("id",userinfo.getId());
        nativeQuery.setParameter("name",userinfo.getName());
        nativeQuery.setParameter("age",userinfo.getAge());
        nativeQuery.setParameter("gender",userinfo.getGender());

        //执行查询
        int i = nativeQuery.executeUpdate();
        if ( i<= 0 ) {
            throw new RuntimeException("添加用户失败!");
        }
    }

    /**
     * 删除用户
     * 这里使用   @DataSourceSwitch("primary")  指定写入的数据源
     * @author zhouq
     * @since 11:19 2024/03/21
     **/
    @Override
    @DataSourceSwitch("primary")  //或者 @DataSourceSwitch
    @Modifying
    @Transactional
    public void deleteUser(int id) {
        //使用本地原生SQL查询
        Query nativeQuery = entityManager.createNativeQuery("delete from Userinfo where id = :id");
        nativeQuery.setParameter("id",id);
        //执行
        int i = nativeQuery.executeUpdate();
        if ( i<= 0 ) {
            throw new RuntimeException("删除用户失败!");
        }
    }

}

9、编写测试类

package com.ts;

import com.ts.model.Userinfo;
import com.ts.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;

@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService service;



    // 测试使用从库读取  查询用户
    @Test
    public void testGetAll() {
        service.getAll().forEach(System.out::println);
        //System.out.println(service);
    }
    // 测试使用从库读取  查询用户
    @Test
    public void testQuery() {
        service.queryWithName("张").forEach(System.out::println);
    }

    // 测试使用主库写入  添加用户
    @Test
    public void testAddUser() {
        service.addUser(new Userinfo(100,"测试用户",25,"男",new Date()));
        System.out.println("添加用户完成");
    }

    // 测试使用主库写入  删除用户
    @Test
    public void testDelete() {
        service.deleteUser(100);
        System.out.println("删除用户完成");
    }

}

小结

关于读写分离网上的示例很多,但是都比较杂乱,而且很多的方法都是基于SpringBoot2或者SpringBoot1的,我这个是基于SpringBoot3.2.3版本实现了,

上面的代码所有的都亲自测试通过,有什么疑问可以留言评论。

几个截图:
1、查询所有
可以看到 使用的数据源是:secondary 使用的是 读 数据源
在这里插入图片描述
2、模糊查询
在这里插入图片描述

3、添加用户
看到 使用的数据源是:primary 使用的是 写 数据源
在这里插入图片描述
4、删除用户
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1544067.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity连接MySQL踩坑,问题处理记录

用的unity2021版本&#xff0c;MySQL是官方下载的最新版8.0.36. 安装MySQL时&#xff0c;过去如果安装过&#xff0c;一定要删干净&#xff0c;单纯的卸载不行&#xff0c;网上有很多教程。 MySQL安装完成后&#xff0c;将安装目录的MySql.Data.dll文件放入unity项目的Plugin…

【Leetcode每日一题】模拟 - 替换所有的问号(难度⭐)

1. 题目解析 题目链接&#xff1a;1576. 替换所有的问号 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 遍历字符串&#xff1a;从左到右逐个处理字符。 处理问号字符&#xff1a;对于每个问号字符&#xff0c;我们需…

[蓝桥杯 2020 省 AB1] 网络分析

一开始写的暴力合并 卡n^2过的不是正解 看正解是类似 虚拟点树形DP的思路 很巧妙 记录一下 #include<bits/stdc.h> using namespace std; using ll long long; using pii pair<int,int>; #define int long long const int N 3e510; const int inf 0x3f3f3f3f; …

MyBatis3源码深度解析(二十一)动态SQL实现原理(二)动态SQL解析过程、#{}和${}的区别

文章目录 前言8.5 动态SQL解析过程8.5.1 SQL配置转换为SqlSource对象8.5.2 SqlSource转换为静态SQL语句 8.6 #{}和${}的区别8.7 小结 前言 在【MyBatis3源码深度解析(二十)动态SQL实现原理(一)动态SQL的核心组件】中研究了MyBatis动态SQL相关的组件&#xff0c;如SqlSource用于…

git在单分支(自己分支)上的操作流程

文章目录 一、git命令整体操作流程&#xff08;了解&#xff09;二、idea中git操作流程&#xff08;常用-图文&#xff09;1、add2、commit&#xff0c;提交代码3、pull 拉取最新代码4、push 推送代码到远程仓库5、最后就可以在远程仓库中看你提交的代码了。 平时在idea中&…

MySQL数据库的下载及安装教程

MySQL是一个数据库管理系统&#xff0c;允许您管理关系数据库。它是Oracle支持的开源软件&#xff0c;本文介绍如何下载、安装和启动&#xff0c;便于新手快速启动学习之旅&#xff0c;具体如下&#xff1a; 一、下载MySQL 1.打开MySQL官网&#xff08;https://www.mysql.com…

第九届蓝桥杯大赛个人赛省赛(软件类)真题C 语言 A 组-第几个幸运数字

幸运数字是可以被3,5,7任一整除的数字&#xff0c;列举小明号码内的所有可能组合并计数。注意别忘了把1占的一位减去。 #include<stdio.h> typedef long long ll; int main(){long long ans 0, n 59084709587505LL;for(ll i 1; i < n; i * 3){//计算小于等于n的数…

面试笔记——Redis(分布式锁的使用场景及实现原理)

分布式锁的使用场景 资源竞争控制&#xff1a;多个客户端同时访问共享资源时&#xff0c;可以使用分布式锁来控制资源的并发访问&#xff0c;防止多个客户端同时对同一资源进行修改造成数据不一致的问题。 避免重复操作&#xff1a;在分布式环境中&#xff0c;可能会出现多个客…

rmvb是什么文件格式?rmvb格式怎么改成mp4?

RMVB&#xff0c;全称RealMedia Variable Bitrate&#xff0c;是由RealNetworks公司开发的一种视频文件格式。其产生背景可追溯至上世纪90年代&#xff0c;为了解决传输和存储上的挑战&#xff0c;RealNetworks公司致力于推出一种更为高效的解决方案。于是&#xff0c;RMVB问世…

还在用传统知识库?AI知识库才是企业的最优选择

在数字化和信息化日趋严重的时代&#xff0c;企业不仅要处理海量的数据&#xff0c;同时还要有效地管理和利用它们。这就使得知识库&#xff0c;作为一种集中存储、管理和共享知识资源的工具&#xff0c;被越来越多的企业所重视。然而&#xff0c;随着技术的快速迭代&#xff0…

Django之Celery篇(一)

一、介绍 Celery是由Python开发、简单、灵活、可靠的分布式任务队列,是一个处理异步任务的框架,其本质是生产者消费者模型,生产者发送任务到消息队列,消费者负责处理任务。 Celery侧重于实时操作,但对调度支持也很好,其每天可以处理数以百万计的任务。特点: 简单:熟悉…

ElasticSearch8 - 基本操作

前言 本文记录 ES 的一些基本操作&#xff0c;就是对官方文档的一些整理&#xff0c;按自己的习惯重新排版&#xff0c;凑合着看。官方的更详细&#xff0c;建议看官方的。 下文以 books 为索引名举例。 新增 添加单个文档 (没有索引会自动创建) POST books/_doc {"n…

消息队列八股

RabbitMQ 确保消息不丢失 重复消费问题 延迟队列 消息堆积 高可用 很少使用 Kafka 如何保证消息不丢失 回调接口保证生产者发送到brocker消息不丢失 保证消息顺序性 高可用机制 数据清理机制 实现高性能的设计

mysql - 缓存

缓存 InnoDB存储引擎在处理客户端的请求时&#xff0c;当需要访问某个页的数据时&#xff0c;就会把完整的页的数据全部加载到内存中&#xff0c;也就是说即使我们只需要访问一个页的一条记录&#xff0c;那也需要先把整个页的数据加载到内存中。将整个页加载到内存中后就可以…

学习vue3第十一节(依赖注入:provide/inject)

本机介绍&#xff1a;provide/inject 注意&#xff1a;大家在看此小节时候&#xff0c;默认大家已经了解一些组件的使用方法 1、依赖注入的用途&#xff1a; 当嵌套层级多的时候&#xff0c;某个子组件需要较远层级的父组件数据时候&#xff0c;如果我们依然使用props 传递数…

virtualbox导入vdi

新建虚拟机 点击新建 输入新建属性 配置cpu和内存 虚拟硬盘 这里选择已有的vdi文件 摘要 这里点击完成 虚拟机添加成功 点击启动&#xff0c;启动虚拟机 注意 这个时候的ip&#xff0c;还是以前镜像的ip&#xff0c;如果两个镜像一起启动&#xff0c;则需要修 改ip地…

802.1X网络访问控制协议

802.1X是一种由IEEE&#xff08;电气和电子工程师协会&#xff09;制定的网络访问控制协议&#xff0c;主要用于以太网和无线局域网&#xff08;WLAN&#xff09;中基于端口的网络接入控制。802.1X协议通过认证和授权机制&#xff0c;确保只有合法的用户和设备才能够接入网络&a…

Java中static、final关键字【详解】

文章目录 一、static关键字1.1 成员变量1.1.1 静态变量及其访问1.1.2 实例变量及其访问 1.2 成员方法1.2.1 静态方法及其访问1.2.2 实例方法及其访问 1.3 小结1.4 static应用知识 二、final关键字2.1 修饰类&#xff1a;不能被继承2.2 修饰方法2.3 修饰变量-局部变量2.3.1 局部…

背景减除(1)--bgslibrary Windows编译和使用

入侵监控领域中&#xff0c;在固定场景下&#xff0c;需要检测和监控的入侵物体种类繁多&#xff0c;无法具体穷尽。传统的CV算法提取的特征应用场景有限&#xff0c;无法完成大量物体的监控&#xff1b;深度学习目标检测方法没法收集到无穷无尽的物体种类&#xff0c;因此监督…

水牛社五大赚钱栏目概览:轻松了解项目核心与赚钱原理

很多新用户首次访问水牛社官网时&#xff0c;可能会感到有些迷茫。由于软件介绍相对较长&#xff0c;部分朋友可能缺乏耐心细读。然而&#xff0c;若您真心希望在网络上找到赚钱的机会&#xff0c;深入了解我们的发展历程将大有裨益。简而言之&#xff0c;本文旨在快速带您领略…