当前位置: 首页 > news >正文

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

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: mysqlshow-sql: truedatasource:#主数据源 , 写入数据库primary:url: jdbc:mysql://localhost:3306/primary_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword: root# 次数据源 , 读取数据库secondary:url: jdbc:mysql://localhost:3306/secondary_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword: 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 {@Autowiredprivate JpaProperties jpaProperties;@Bean(name = "primaryDataSource")@ConfigurationProperties(prefix = "spring.datasource.primary")public DataSource primaryDataSource() {//return DataSourceBuilder.create().build();return new DruidDataSource();}//LocalContainerEntityManagerFactoryBean@Beanpublic 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 neededreturn 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 {@Autowiredprivate JpaProperties jpaProperties;@Bean(name = "secondaryDataSource")@ConfigurationProperties(prefix = "spring.datasource.secondary")public DataSource secondaryDataSource() {//return DataSourceBuilder.create().build();return new DruidDataSource();}// LocalContainerEntityManagerFactoryBean@Beanpublic 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 neededreturn 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{@Nullableprivate Map<String, Object> targetEntityManagerFactorys;@Nullableprivate Object defaultTargetEntityManagerFactory;@Nullablepublic Map<String, Object> getTargetEntityManagerFactorys() {return targetEntityManagerFactorys;}public void setTargetEntityManagerFactorys(@Nullable Map<String, Object> targetEntityManagerFactorys) {this.targetEntityManagerFactorys = targetEntityManagerFactorys;}@Nullablepublic 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接口的实现------------------------------------@Overridepublic EntityManager createEntityManager() {return this.determineTargetEntityManagerFactory().createEntityManager();}@Overridepublic EntityManager createEntityManager(Map map) {return this.determineTargetEntityManagerFactory().createEntityManager(map);}@Overridepublic EntityManager createEntityManager(SynchronizationType synchronizationType) {return this.determineTargetEntityManagerFactory().createEntityManager(synchronizationType);}@Overridepublic EntityManager createEntityManager(SynchronizationType synchronizationType, Map map) {return this.determineTargetEntityManagerFactory().createEntityManager(synchronizationType,map);}@Overridepublic CriteriaBuilder getCriteriaBuilder() {return this.determineTargetEntityManagerFactory().getCriteriaBuilder();}@Overridepublic Metamodel getMetamodel() {return this.determineTargetEntityManagerFactory().getMetamodel();}@Overridepublic boolean isOpen() {return this.determineTargetEntityManagerFactory().isOpen();}@Overridepublic void close() {this.determineTargetEntityManagerFactory().close();}@Overridepublic Map<String, Object> getProperties() {return this.determineTargetEntityManagerFactory().getProperties();}@Overridepublic Cache getCache() {return this.determineTargetEntityManagerFactory().getCache();}@Overridepublic PersistenceUnitUtil getPersistenceUnitUtil() {return this.determineTargetEntityManagerFactory().getPersistenceUnitUtil();}@Overridepublic void addNamedQuery(String s, Query query) {this.determineTargetEntityManagerFactory().addNamedQuery(s,query);}@Overridepublic <T> T unwrap(Class<T> aClass) {return this.determineTargetEntityManagerFactory().unwrap(aClass);}@Overridepublic <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;@Beanpublic 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;}@Beanpublic 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 {@Autowiredprivate 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@Transactionalpublic 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@Transactionalpublic 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 {@Autowiredprivate UserService service;// 测试使用从库读取  查询用户@Testpublic void testGetAll() {service.getAll().forEach(System.out::println);//System.out.println(service);}// 测试使用从库读取  查询用户@Testpublic void testQuery() {service.queryWithName("张").forEach(System.out::println);}// 测试使用主库写入  添加用户@Testpublic void testAddUser() {service.addUser(new Userinfo(100,"测试用户",25,"男",new Date()));System.out.println("添加用户完成");}// 测试使用主库写入  删除用户@Testpublic void testDelete() {service.deleteUser(100);System.out.println("删除用户完成");}}

小结

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

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

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

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

相关文章:

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

1、简介 在Spring Boot中配置多个数据源并实现自动切换EntityManager&#xff0c;这里我编写了一个RoutingEntityManagerFactory和AOP&#xff08;面向切面编程&#xff09;的方式来实现。 这里我配置了两个数据源&#xff1a;primary和secondary&#xff0c;其中primary主数…...

10万+SRC高质量稀有明星人脸素材唐嫣杨紫金晨倪妮蔡卓妍张雨绮合集

素材下载&#xff1a;点击下载 DFL模型训练用&#xff0c;高质量SRC素材合集&#xff1a; 【唐嫣_WF】6000张src人脸素材【金鱼基础模型库】用于补全SRC极限角度范冰冰 WF 512 苹果稀有死亡角度.zip王鸥_WF_512 多部电影 有死亡角度和张嘴的&#xff0c;5000多张.rar杨紫 108…...

目标检测中的mAP计算原理和源码实现

简介 在目标检测任务中&#xff0c;mAP&#xff08;mean Average Precision&#xff0c;平均精度均值&#xff09;是一个非常重要的评价指标&#xff0c;用于衡量模型在多个类别上的平均性能。它综合考虑了模型在不同召回率下的精确率&#xff0c;能够全面反映模型在检测任务中…...

Unity 粒子在UI中使用时需要注意的地方

最近项目中要在UI中挂载粒子特效,美术给过来的粒子直接放到UI中会有一些问题,查询一些资料后,总结了一下 一: 粒子的大小发生变化,与在预制件编辑中设计的大小不同 在预制件编辑模式下,大小正常 实际使用的时候特别大或者特别小 经过检查,发现预制件编辑模式下,默认画布的Rend…...

了解Spring:Java开发的利器

Spring是一款开源的轻量级Java开发框架&#xff0c;旨在提高开发人员的效率和系统的可维护性。本文将介绍Spring的基本概念、使用优势、设计模式以及与Spring MVC和Spring Boot的关联。 什么是Spring&#xff1f; Spring是一款开源的轻量级Java开发框架&#xff0c;它由多个模…...

【Hive】with 语法 vs cache table 语法

语法分别如下&#xff1a; cache table table_name as (select ... from ... )with table_name as (select ... from ... )需要注意&#xff0c;with语法只相当于一个视图&#xff0c;并不会将数据缓存&#xff1b;如果要将数据缓存&#xff0c;需要使用cache table语法。 参考…...

CleanMyMac X 4.15.1 for Mac 最新中文破解版 系统优化垃圾清理工具

CleanMyMac X for Mac 是一款功能更加强大的系统优化清理工具&#xff0c;相比于 CleanMyMac 4.15.1来说&#xff0c;功能增加了不少&#xff0c;此版本为4.15.1官方最新中英文正式破解版本&#xff0c;永久使用&#xff0c;解决了打开软件崩溃问题&#xff0c;最新版4.15.1版本…...

查看Linux系统重启的四种基本命令

目录 前言1. last2. uptime3. journalctl4. dmesg 前言 对于排查其原因推荐阅读&#xff1a;详细分析服务器自动重启原因&#xff08;涉及Linux、Window&#xff09; 在Linux中&#xff0c;有多种命令可以查看系统重启的信息 以下是其中一些常用的命令及其解释&#xff1a; …...

I/O多路复用:select/poll/epoll

最基本的 Socket 模型 要想客户端和服务器能在网络中通信&#xff0c;那必须得使用 Socket 编程&#xff0c;它是进程间通信里比较特别的方式&#xff0c;特别之处在于它是可以跨主机间通信。 Socket 的中文名叫作插口&#xff0c;咋一看还挺迷惑的。事实上&#xff0c;双方要…...

使用ansible批量修改操作系统管理员账号密码

一、ansible server端配置 1、对于Linux主机配置免密登录ssh-copy-id -i ~/.ssh/id_rsa.pub rootremote_ip 2、在/etc/ansible/hosts文件中添加相应主机IP 3、对于Windows主机需要在/etc/ansible/hosts文件中进行以下配置 192.168.83.132 ansible_ssh_useradministrator an…...

webpack5零基础入门-13生产模式

1.生产模式介绍 生产模式是开发完成代码后&#xff0c;我们需要得到代码将来部署上线。 这个模式下我们主要对代码进行优化&#xff0c;让其运行性能更好。 优化主要从两个角度出发: 优化代码运行性能优化代码打包速度 2.生产模式准备 我们分别准备两个配置文件来放不同的…...

一篇复现Docker镜像操作与容器操作

华子目录 Docker镜像操作创建镜像方式1docker commit示例 方式2docker import示例1&#xff1a;从本地文件系统导入示例2&#xff1a;从远程URL导入注意事项 方式3docker build示例1&#xff1a;构建镜像并指定名称和标签示例2&#xff1a;使用自定义的 Dockerfile 路径构建镜像…...

【LevelDB】memtable、immutable memtable的切换源码

本文主要分析leveldb项目的MakeRoomForWrite方法及延伸出的相关方法。 努力弄清memtable 和 immutable memtable的切换过程细节&#xff0c; 背景总结&#xff1a; LevelDB 是一个基于 Log-Structured Merge-Tree (LSM Tree) 的高性能键值存储系统。 在 LevelDB 中&#xff0…...

力扣面试150 x 的平方根 二分 换底法 牛顿迭代法 一题多解

Problem: 69. x 的平方根 思路 &#x1f468;‍&#x1f3eb; 参考题解 &#x1f496; 袖珍计算器算法 class Solution {public int mySqrt(int x){if (x 0)return 0; // Math.exp(3)&#xff1a;e的三次方int ans (int) Math.exp(0.5 * Math.log(x));return (long) (an…...

【JavaScript】JavaScript 程序流程控制 ⑤ ( 嵌套 for 循环 | 嵌套 for 循环概念 | 嵌套 for 循环语法结构 )

文章目录 一、嵌套 for 循环1、嵌套 for 循环概念2、嵌套 for 循环语法结构 二、嵌套 for 循环案例1、打印三角形2、打印乘法表 一、嵌套 for 循环 1、嵌套 for 循环概念 嵌套 for 循环 是一个 嵌套的 循环结构 , 其中一个 for 循环 位于另一个 for 循环的内部 , 分别是 外层 f…...

情感计算:大模型在情感识别与交互优化中的作用

情感计算&#xff1a;大模型在情感识别与交互优化中的作用 1. 背景介绍 情感计算&#xff08;Affective Computing&#xff09;是人工智能领域的一个重要分支&#xff0c;它致力于使计算机能够识别、理解、处理和模拟人类的情感。随着深度学习、大数据和计算能力的飞速发展&a…...

集合系列(十四) -ConcurrentHashMap详解

一、摘要 在之前的集合文章中&#xff0c;我们了解到 HashMap 在多线程环境下操作可能会导致程序死循环的线上故障&#xff01; 既然在多线程环境下不能使用 HashMap&#xff0c;那如果我们想在多线程环境下操作 map&#xff0c;该怎么操作呢&#xff1f; 想必阅读过小编之前…...

数据结构面试题

1、数据结构三要素&#xff1f; 逻辑结构、物理结构、数据运算 2、数组和链表的区别&#xff1f; 数组的特点&#xff1a; 数组是将元素在内存中连续存放&#xff0c;由于每个元素占用内存相同&#xff0c;可以通过下标迅速访问数组中任何元素。数组的插入数据和删除数据效率低…...

python爬虫之xpath入门

文章目录 一、前言参考文档&#xff1a; 二、xpath语法-基础语法常用路径表达式举例说明 三、xpath语法-谓语表达式举例注意 四、xpath语法-通配符语法实例 五、选取多个路径实例 六、Xpath Helper安装使用说明例子&#xff1a; 七、python中 xpath 的使用安装xpath 的依赖包xm…...

TikTok云手机是什么原理?

社交媒体的快速发展和普及&#xff0c;TikTok已成为全球最受欢迎的短视频平台之一&#xff0c;吸引了数以亿计的用户。在TikTok上&#xff0c;许多用户和内容创作者都希望能够更灵活地管理和运营多个账号&#xff0c;这就需要借助云手机技术。那么&#xff0c;TikTok云手机究竟…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

基于单片机的宠物屋智能系统设计与实现(论文+源码)

本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢&#xff0c;连接红外测温传感器&#xff0c;可实时精准捕捉宠物体温变化&#xff0c;以便及时发现健康异常&#xff1b;水位检测传感器时刻监测饮用水余量&#xff0c;防止宠物…...

TJCTF 2025

还以为是天津的。这个比较容易&#xff0c;虽然绕了点弯&#xff0c;可还是把CP AK了&#xff0c;不过我会的别人也会&#xff0c;还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...

Android屏幕刷新率与FPS(Frames Per Second) 120hz

Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数&#xff0c;单位是赫兹&#xff08;Hz&#xff09;。 60Hz 屏幕&#xff1a;每秒刷新 60 次&#xff0c;每次刷新间隔约 16.67ms 90Hz 屏幕&#xff1a;每秒刷新 90 次&#xff0c;…...