前言
前段时间使用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; } }
上面的类中我只举了一个方法的例子,如果你有更多的增、删、改方法需要用,那么都需要重写。
至此我们应该就可以解决上面的问题了。