前言

  前段时间使用spring jpa做了一个项目,由于涉及到了多个数据库,因此需要进行多数据源的配置。网上找了很多的资料,尝试着配置,都以失败告终。之后通过断点最终完成了多数据源的配置。这篇博客主要为了记录下,使用SimpleJpaRepository如何配置多数据源。也希望可以帮助到更多的人。

环境

java版本:8

框架: spring boot(2.0.4.RELEASE)、jpa

数据库:mysql

配置步骤

  • 目录结果

  • pom文件
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.19</version>
</dependency>
</dependencies>
  • application.yml
spring:
application:
name: multi-database
datasource:
main:
url: jdbc:mysql://localhost:3306/test_main?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: ***
slave:
url: jdbc:mysql://localhost:3306/test_slave?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: ***
jpa:
properties:
hibernate:
ddl-auto: update
naming:
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
generate-ddl: true
show-sql: true
database: mysql
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

配置文件中我配置了两个数据源,一个是 main ,一个是 slave 。

  • 数据连接池配置

DruidDBConfig.java:

@Configuration
public class DruidDBConfig { @Value("${spring.datasource.main.url}")
private String mainDBUrl; @Value("${spring.datasource.main.username}")
private String mainUsername; @Value("${spring.datasource.main.password}")
private String mainPassword; @Value("${spring.datasource.slave.url}")
private String slaveDBUrl; @Value("${spring.datasource.slave.username}")
private String slaveUsername; @Value("${spring.datasource.slave.password}")
private String slavePassword; @Value("com.mysql.jdbc.Driver")
private String driverClassName; @Value("5")
private int initialSize; @Value("5")
private int minIdle; @Value("20")
private int maxActive; @Value("60000")
private int maxWait; /**
* 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
*/
@Value("60000")
private int timeBetweenEvictionRunsMillis;
/**
* 配置一个连接在池中最小生存的时间,单位是毫秒
*/
@Value("300000")
private int minEvictableIdleTimeMillis; @Value("SELECT 1 FROM DUAL")
private String validationQuery; @Value("true")
private boolean testWhileIdle; @Value("false")
private boolean testOnBorrow; @Value("false")
private boolean testOnReturn; /**
* 打开PSCache,并且指定每个连接上PSCache的大小
*/
@Value("true")
private boolean poolPreparedStatements; @Value("20")
private int maxPoolPreparedStatementPerConnectionSize; @Value("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500")
private String connectionProperties; @Bean(name = "mainDataSource")
@Qualifier("mainDataSource")
@Primary // 主数据源,如果有两个数据源,没有指定数据源则默认为该数据源
public DataSource mainDataSource() {
return getDruidDataSource(mainUsername, mainPassword, mainDBUrl);
} @Bean(name = "slaveDataSource")
@Qualifier("slaveDataSource")
public DataSource slaveDataSource() {
return getDruidDataSource(slaveUsername, slavePassword, slaveDBUrl);
} private DruidDataSource getDruidDataSource(String username, String password, String url) {
DruidDataSource datasource = new DruidDataSource(); datasource.setUrl(url);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName); //configuration
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
datasource.setConnectionProperties(connectionProperties); return datasource;
}
}

在这里统一管理两个数据源。

  • 主数据源的配置

MainDataSourceConfig.java:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactoryMain"
, transactionManagerRef = "transactionManagerMain"
, basePackages = {"com.yun.demo.dao.main"})// dao所在的位置
public class MainDataSourceConfig {
@Autowired
@Qualifier("mainDataSource")
private DataSource mainDataSource; @Autowired
private JpaProperties jpaProperties; @Primary
@Bean(name = "entityManagerMain")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactoryMain(builder).getObject()); // 这里比较关键,这里我们要创建一个SharedEntityManager,不然无法在SimpleJpa上使用多个数据源
}
@Primary
@Bean(name = "entityManagerFactoryMain")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryMain (EntityManagerFactoryBuilder builder) {
return builder
.dataSource(mainDataSource)
.properties(getVendorProperties())
.packages("com.yun.demo.entity.main") //设置实体类所在位置
.persistenceUnit("mainPersistenceUnit")
.build();
} @Primary
@Bean(name = "transactionManagerMain")
public PlatformTransactionManager transactionManagerMain(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryMain(builder).getObject());
} private Map<String, Object> getVendorProperties() {
HibernateSettings hibernateSettings = new HibernateSettings();
return jpaProperties.getHibernateProperties(hibernateSettings);
}
}

由于 SimpleJpaRepository 使用的是 SharedEntityManager 去管理的,而网上大部分帖子都不是使用它,因此如果你的类继承了 SimpleJpaRepository ,而没有有配置 SimpleJpaRepository ,就会报错 no transaction is in progress 。下面就是报错信息:

Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3505)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1427)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350)
at com.sun.proxy.$Proxy71.flush(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:534)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:505)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository$$FastClassBySpringCGLIB$$31f56960.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 37 more
  • 从数据源的配置

SlaveDataSourceConfig.java:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactorySlave"
, transactionManagerRef = "transactionManagerSlave"
, basePackages = {"com.yun.demo.dao.slave"})
public class SlaveDataSourceConfig {
@Autowired
@Qualifier("slaveDataSource")
private DataSource slaveDataSource; @Autowired
private JpaProperties jpaProperties; @Bean(name = "entityManagerSlave")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactorySlave(builder).getObject());
}
@Bean(name = "entityManagerFactorySlave")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySlave (EntityManagerFactoryBuilder builder) {
return builder
.dataSource(slaveDataSource)
.properties(getVendorProperties())
.packages("com.yun.demo.entity.slave") //设置实体类所在位置
.persistenceUnit("slavePersistenceUnit")
.build();
} @Bean(name = "transactionManagerSlave")
public PlatformTransactionManager transactionManagerSlave(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactorySlave(builder).getObject());
} private Map<String, Object> getVendorProperties() {
HibernateSettings hibernateSettings = new HibernateSettings();
return jpaProperties.getHibernateProperties(hibernateSettings);
}
}

从库的配置跟主库的配置差不多。

  • 告诉dao这里我们需要使用从库的数据源

SlaveDao.java:

package com.yun.demo.dao.slave;  //这里是配置从库的数据源的basePackages地址

import com.yun.demo.entity.slave.Address;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; @Repository
public class AddressDao extends MySimpleJpaRepository<Address, Long> { @Autowired //这里需要使用@Qualifier("entityManagerSlave")将数据源配置到从库
public AddressDao(@Qualifier("entityManagerSlave") EntityManager entityManager) {
super(JpaEntityInformationSupport.getEntityInformation(Address.class, entityManager), entityManager);
} }
  • 修改从库 SimpleJpaRepository 的事务管理器

由于 SimpleJpaRepository  的事务管理器是默认没有使用从库的事务,因此从库如果使用 SimpleJpaRepository ,就会报以下错误:

Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3505)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1427)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350)
at com.sun.proxy.$Proxy71.flush(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:305)
at com.sun.proxy.$Proxy71.flush(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:534)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:505)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository$$FastClassBySpringCGLIB$$31f56960.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 37 more

解决上面的问题,需要重写 SimpleJpaRepository 类。不多说,上代码:

package com.yun.demo.dao.slave;

import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; public class MySimpleJpaRepository<T, ID> extends SimpleJpaRepository<T, ID> {
public MySimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
} @Override
@Transactional(transactionManager = "transactionManagerSlave") // 在这里我们告诉这个方法,我们需要使用从库的事务管理器
public <S extends T> S saveAndFlush(S entity) {
S result = this.save(entity);
this.flush();
return result;
}
}

上面的类中我只举了一个方法的例子,如果你有更多的增、删、改方法需要用,那么都需要重写。

至此我们应该就可以解决上面的问题了。

[spring jpa] 解决SimpleJpaRepository的多数据源配置问题的更多相关文章

  1. 在使用 Spring Boot 和 MyBatis 动态切换数据源时遇到的问题以及解决方法

    相关项目地址:https://github.com/helloworlde/SpringBoot-DynamicDataSource 1. org.apache.ibatis.binding.Bind ...

  2. springboot 2 Hikari 多数据源配置问题(dataSourceClassName or jdbcUrl is required)

    springboot 2 Hikari 多数据源配置问题(dataSourceClassName or jdbcUrl is required) 最近在项目中想试一下使用 Hikari 连接池,以前用 ...

  3. spring jpa 实体互相引用返回restful数据循环引用报错的问题

    spring jpa 实体互相引用返回restful数据循环引用报错的问题 Java实体里两个对象有关联关系,互相引用,比如,在一对多的关联关系里 Problem对象,引用了标签列表ProblemLa ...

  4. 使用Spring Boot快速构建基于SQLite数据源的应用

    为了提供一个单包易部署的服务器应用,考虑使用Spring Boot,因为其集成了Apache Tomcat,易于运行,免去绝大部分了服务器配置的步骤. 项目初始化 首先从mvn archetype:g ...

  5. Hibernate | Spring JPA | MySQL 使用过程遇到的一些问题

    1. 使用过程 2. 背景 3. 遇到问题 3.1 不指定Hibernate数据库方言,默认SQL生成方式 3.2 抛出异常Hibernate加入了@Transactional事务不会回滚 3.3 H ...

  6. 【bug记录】jpa 解决org.hibernate.lazyinitializationexception could not initialize proxy - no session

    前言 最近开发项目比较忙,Spring Cloud的笔记得稍稍放放了,下午出来个bug,恶心的不行,功能很简单,也没有什么级联或复杂的映射关系,就是一直在报三个异常 Caused by: com.fa ...

  7. spring jpa和mybatis整合

    spring jpa和mybatis整合 前一阵子接手了一个使用SpringBoot 和spring-data-jpa开发的项目 后期新加入一个小伙伴,表示jpa相比mybatis太难用,多表联合的查 ...

  8. Spring boot 解决 hibernate no session异常

    启动类中加入 @Beanpublic OpenEntityManagerInViewFilter openEntityManagerInViewFilter(){ return new OpenEnt ...

  9. springmvc+spring+jpa(hibernate)+redis+maven配置

    废话不多少 项目结构 pom.xml配置例如以下 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=& ...

随机推荐

  1. C++继承中的同名覆盖

    1,父子间的冲突是由继承带来的,两个类之间存在了继承的关系,必然的会带来一 些问题,本文要讨论的是父子之间成员变量或成员函数的命名问题: 2,思考: 1,子类中是否可以定义父类中的同名成员? 1,可以 ...

  2. PHP生成excle文件

    excle_word.php /*** * 生成excle文件 ***/ $time = time(); $filename = date("Y年m月d日h点m分s秒", $tim ...

  3. 详解vue全局组件与局部组件使用方法

    这篇文章主要为大家详细介绍了vue全局组件与局部组件的使用方法,具有一定的参考价值,对此有需要的朋友可以参考学习下.如有不足之处,欢迎批评指正. vue全局/局部注册,以及一些混淆的组件main.js ...

  4. MUI沉浸式代码

    var ben_immer = (function() { var immersed = 0; var ms = (/Html5Plus\/.+\s\(.*(Immersed\/(\d+\.?\d*) ...

  5. luogu P1587 [NOI2016]循环之美

    传送门 首先要知道什么样的数才是"纯循环数".打表可以发现,这样的数当且仅当分母和\(k\)互质,这是因为,首先考虑除法过程,每次先给当前余数\(*k\),然后对分母做带余除法,那 ...

  6. MySQL索引优化 笔记

    少取字段,建立合理的索引 表优化: 1 定长与变长分离 如果都是定长 查询比较快 因为每一行的字节都是固定的 fixed 2 常用字段和不常用字段要分离 用户表 常用 放主表 个人介绍不常用 还比较长 ...

  7. decode与case when 函数

    百度百科: DECODE函数,是ORACLE公司的SQL软件ORACLE PL/SQL所提供的特有函数计算方式,以其简洁的运算方式,可控的数据模型和灵活的格式转换而闻名. DECODE 中的if-th ...

  8. 9.css3动画-2D/3D变形--trasform

    transform: None不转换. Translate(x,y)通过设置X轴的值进行移动. translateY(y)通过设置Y轴的值进行移动. Scale(x,y)定义2D缩放. ScaleX( ...

  9. atxserver2-rethinkdb的一些基础操作

    因为我只需要一些基础操作就好,所以在web端的 Data Explorer 操作,首先启动  rethinkdb.exe,然后登陆网页端, 下面是一些基础操作的语句, 一.创建表r.db(‘atxse ...

  10. ubuntu apache 一个ip绑定多个域名,发布目录

    1.将www.aaa.com 与 www.bbb.com 的DNS解析到你的服务器上 2.添加两个发布目录 /var/www/html/aaa  /var/www/html/bbb 3.修改配置文件. ...