前段時間使用spring jpa作了一個項目,因爲涉及到了多個數據庫,所以須要進行多數據源的配置。網上找了不少的資料,嘗試着配置,都以失敗了結。以後經過斷點最終完成了多數據源的配置。這篇博客主要爲了記錄下,使用SimpleJpaRepository如何配置多數據源。也但願能夠幫助到更多的人。java
java版本:8mysql
框架: spring boot(2.0.4.RELEASE)、jpaspring
數據庫:mysqlsql
<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>
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:app
@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:ide
@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 。下面就是報錯信息:spring-boot
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:ui
@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); } }
從庫的配置跟主庫的配置差很少。
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 ,就會報如下錯誤:
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; } }
上面的類中我只舉了一個方法的例子,若是你有更多的增、刪、改方法須要用,那麼都須要重寫。
至此咱們應該就能夠解決上面的問題了。