springboot+jpa配置多資料來源
功能情況:
實現系統對多資料來源的操作。
實現系統對多資料來源的分散式事務管理,包括事務的提交和回滾。
1、建立資料庫配置檔案
#預設資料庫,必須配置,且字首必須為spring.datasource.primary spring.datasource.primary.url=jdbc:mysql://localhost:3306/h1?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8 spring.datasource.primary.username=root spring.datasource.primary.password=123456 spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver #第二個資料庫 spring.datasource.secondary.url=jdbc:mysql://localhost:3306/h2?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8 spring.datasource.secondary.username=root spring.datasource.secondary.password=123456 spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver #第三個資料庫 spring.datasource.three.url=jdbc:mysql://localhost:3306/h3?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8 spring.datasource.three.username=root spring.datasource.three.password=123456 spring.datasource.three.driver-class-name=com.mysql.jdbc.Driver |
假設系統需要多個數據庫進行操作,如h1、h2、h3,預設h1為主庫。
2、資料來源及其事務相關配置
因為是用springboot實現,所有這部分配置為java程式碼。
為了儘量遵循開閉原則,定義資料來源介面DataSourceConfig和抽象類DBConfig以及配置環境類IDBConfigContent。
- 資料來源介面和配置抽象類:
public interface DataSourceConfig { public DataSource initDataSource(); } |
public abstract class DBConfig { @Autowired protected JpaProperties jpaProperties; protected Map<String, String> getVendorProperties(DataSource dataSource) { return jpaProperties.getHibernateProperties(dataSource); } public abstract EntityManager entityManager(EntityManagerFactoryBuilder builder); public abstract LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder); public abstract PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder); public abstract TransactionAttributeSource transactionAttributeSource(); } |
public interface IDBConfigContent { public void init(); } |
- 實現主庫(預設庫)資料來源配置:
@Configuration @EnableTransactionManagement//開啟自動事務管理 @EnableJpaRepositories( entityManagerFactoryRef = "entityManagerFactoryPrimary", //實體類管理工廠 transactionManagerRef = "transactionManagerPrimary", //對應庫的事務管理器 basePackages = {"net.thinktrader.fundta" }// 設定Repository所在位置,必須設定 ) @Component("primaryConfig") public class PrimaryDataSourceConfig extends DBConfig implements TADataSourceConfig { /* * (non-Javadoc) * @see * net.thinktrader.fundta.test.dao2.datasource.TADataSourceConfig#initDataSource * () */ @Bean(name = "primaryDataSource") // 資料來源bean及其名稱 @Qualifier("primaryDataSource") // 更加名稱進行注入 // 對應配置檔案中字首為spring.datasource.primary的配置項 @ConfigurationProperties(prefix = "spring.datasource.primary") @Primary // 多資料來源時,必須配置一個主資料來源 public DataSource initDataSource() { return DataSourceBuilder.create().build(); } @Autowired @Qualifier("primaryDataSource") // 對應的資料來源 private DataSource primaryDataSource; @Primary // 多資料來源時,需要一個預設的主資料來源 @Bean(name = "entityManagerPrimary") // 註冊資料來源的實體mananger public EntityManager entityManager(EntityManagerFactoryBuilder builder) { return entityManagerFactory(builder).getObject().createEntityManager(); } @Primary @Bean(name = "entityManagerFactoryPrimary") // 註冊資料來源的實體mananger工廠 public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) { return builder.dataSource(primaryDataSource) .properties(getVendorProperties(primaryDataSource)) .packages("net.thinktrader.fundta") // 設定實體類所在位置,必須設定 .persistenceUnit("primaryPersistenceUnit")// 這個必須設定且不能重複 .build(); } @Primary @Bean(name = "transactionManagerPrimary") // 資料來源對應的事務管理器 public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(entityManagerFactory(builder).getObject()); } @Bean("transactionManagerPrimarySource") @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { //"transactionManagerPrimary"必須和對應的是否管理器對應 TATransactionAnnotationParser myParser = new TATransactionAnnotationParser("transactionManagerPrimary"); return new AnnotationTransactionAttributeSource(myParser); } } |
- 配置環境類DefaultDBConfigContent
預設配置環境類DefaultDBConfigContent,這裡只初始化了預設資料庫的資料,如果要配置其他資料庫,則需要編寫一個類去繼承DefaultDBConfigContent,並重寫init方法。
@Component public class DefaultDBConfigContent implements IDBConfigContent { /** * 資料來源對應的實體管理器 */ public static Map<String, EntityManager> manangerMap = new HashMap<>(); /** * 資料來源對應的事務管理器 */ public static Map<String, PlatformTransactionManager> transactionManagers = new HashMap<>(); /** * 資料來源對應的事務配置資訊,即@Transactional中的配置資訊, * 如: @Transactional(value="transactionManagerPrimary",propagation=Propagation.REQUIRES_NEW) */ public static Map<String, TransactionAttributeSource> transactionAttributeSourceMap = new HashMap<>(); // 主資料來源別名 public final static String DB1 = "db1"; // 預設資料來源別名 public final static String DEFAULT_DB = DB1; // 主庫持久環境註冊 @PersistenceContext(unitName = "primaryPersistenceUnit") private EntityManager entityManagerPrimary; // 主庫事務管理器 @Resource private PlatformTransactionManager transactionManagerPrimary; @Resource protected DBConfig primaryConfig; @PostConstruct public void init() { manangerMap.put(DB1, entityManagerPrimary); transactionManagers.put(DB1, transactionManagerPrimary); transactionAttributeSourceMap.put(DB1, primaryConfig.transactionAttributeSource()); } } |
這個類主要是快取相關物件的類,如資料來源對應的實體管理器、 資料來源對應的事務管理器、資料來源對應的事務配置資訊、資料來源別名等資訊。
3、系統全域性配置
- 重寫spring事務註解解析器
/** * @author zhaodingcheng * @since 2018年10月12日 at 上午10:40:42 * 自定義transaction註解解析類,將transaction註解中的value屬性進行重新設定, * 設定為對應的事務管理器;如果想自定義註解替換spring的transaction註解,也需要這個類 */ public class DefaultTransactionAnnotationParser extends SpringTransactionAnnotationParser { private static final long serialVersionUID = 1L; private String transactionManager; public DefaultTransactionAnnotationParser(String transactionManager) { this.transactionManager = transactionManager; } protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) { RuleBasedTransactionAttribute rbta = (RuleBasedTransactionAttribute) super.parseTransactionAnnotation( attributes); rbta.setQualifier(transactionManager); return rbta; } } |
rbta.setQualifier(transactionManager);這句話是重點。spring預設讀取類或者方法上面的@Transactional註解,但因為要靈活切換資料來源,必須在切換資料來源時,動態修改@Transactional註解裡的value屬性值。這裡的transactionManager成員變數即為事務管理器的名稱。
- 重寫spring事務處理攔截器DefaultTransactionInterceptor:
在啟動系統完成後,每次執行被@Transactional註解修飾的類或方法時,都會進入到事務處理攔截器DefaultTransactionInterceptor中。
如果在多資料來源的情況下,還有一個問題,就是當某個資料來源發生異常,需要回滾所有資料來源的資料。
spring預設的事務行為只能回滾異常當前資料來源事務,如果需要回滾所有資料來源,則需要修改spring預設事務行為。事務預設行為是在事務攔截器中,執行完一個數據源的事務後,就提交該資料來源的事務,即spring預設事務行為是按照資料來源分組提交事務。為了讓異常回滾所有資料來源事務,需要在當前執行緒所屬第一個事務完成後,在提交事務,如果出現異常,則回滾所有資料來源事務。具體程式碼及其解釋見自定義事務攔截器程式碼。
public class DefaultTransactionInterceptor extends TransactionInterceptor { private static final long serialVersionUID = 1L; // 儲存當前執行緒建立的事務資訊集合 private static ThreadLocal<List<TransactionInfo>> transactionInfoList = new ThreadLocal<>(); /** * 為當前執行緒新增事務 * * @param txInfo */ private void addTransactionInfo(TransactionInfo txInfo) { List<TransactionInfo> trans = transactionInfoList.get(); if (null == trans) { trans = new ArrayList<>(); } trans.add(txInfo); transactionInfoList.set(trans); } /** * 清空當前執行緒的所有事務資訊 */ private void clearTransactionInfos() { transactionInfoList.set(null); } /** * 這裡有個假設: 假設所有需要訪問資料庫的方法都有一個BasePo型別的引數,如果沒有引數,那麼只能操作預設資料庫 */ @Override public Object invoke(final MethodInvocation invocation) throws Throwable { String dataSource = DefaultDBConfigContent.DEFAULT_DB; /** 從BasePo中獲取要訪問的資料來源別名 **/ Object[] ags = invocation.getArguments(); if (ags.length > 0) { for (Object obj : ags) { if (obj instanceof DefaultBasePo) { DefaultBasePo po = (DefaultBasePo) obj; dataSource = po.getDataSourceName(); break; } } } if (dataSource == null || dataSource.equals("")) { dataSource = DefaultDBConfigContent.DEFAULT_DB; } /** 根據資料來源別名,注入實體管理器 **/ EntityManager entityMan = DefaultDBConfigContent.manangerMap.get(dataSource); Object targetClass = invocation.getThis(); if (targetClass instanceof DefaultBaseService) { DefaultBaseService baseSevice = (DefaultBaseService) targetClass; baseSevice.setEntityManager(entityMan); } /** 設定資料來源對應的事務管理器和資料來源對應的事務註解配置 **/ setTransactionManager(DefaultDBConfigContent.transactionManagers.get(dataSource)); setTransactionAttributeSource(DefaultDBConfigContent.transactionAttributeSourceMap.get(dataSource)); return super.invoke(invocation); } /** * 這個類在父類中為私有方法,而本類需要呼叫,則需要重寫,即將父類的此方法拷貝過來即可 * * @param method * @param targetClass * @param txAttr * @return */ private String methodIdentification(Method method, Class<?> targetClass, TransactionAttribute txAttr) { String methodIdentification = methodIdentification(method, targetClass); if (methodIdentification == null) { if (txAttr instanceof DefaultTransactionAttribute) { methodIdentification = ((DefaultTransactionAttribute) txAttr).getDescriptor(); } if (methodIdentification == null) { methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); } } return methodIdentification; } /** * 重寫父類的執行事務的方法,原則是拷貝父類對應的方法程式碼,並新增自己需要的邏輯 */ @Override protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback // calls. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); // 自己新增的邏輯,將事務資訊新增到執行緒相關的集合中 addTransactionInfo(txInfo); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } // 不知道這段程式碼是什麼意思,直接拷貝父類的,並修改相關程式碼讓其編譯通過 else { // It's a CallbackPreferringPlatformTransactionManager: pass a // TransactionCallback in. try { Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); try { return invocation.proceedWithInvocation(); } catch (Throwable ex) { if (txAttr.rollbackOn(ex)) { // A RuntimeException: will lead to a rollback. if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new RuntimeException(ex); } } else { // A normal return value: will lead to a commit. return new Exception(ex); } } finally { cleanupTransactionInfo(txInfo); } } }); // Check result: It might indicate a Throwable to rethrow. if (result instanceof Exception) { throw new Exception("資料儲存異常"); } else { return result; } } catch (Exception ex) { throw ex.getCause(); } } } /** * 改變spring事務的預設行為 在父類中,這個方法會清空當前事務,並將指標指向前一個事務 */ protected void cleanupTransactionInfo(TransactionInfo txInfo1) { } /** * 提交事務 首先從執行緒相關靜態變數中獲取出所有事務資訊 然後判斷當前事務資訊是否是當前執行緒相關的第一個事務 * 如果是當前執行緒的第一個事務,則依次從最後一個事務到第一個事務進行提交 最後清空當前執行緒關聯的所有事務資訊 */ protected void commitTransactionAfterReturning(TransactionInfo txInfo1) { List<TransactionInfo> trans = transactionInfoList.get(); if (trans != null && trans.size() > 0 && txInfo1 == trans.get(0)) { for (int i = trans.size() - 1; i >= 0; i--) { TransactionInfo txInfo = trans.get(i); if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); } txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } clearTransactionInfos(); } } /** * 回滾事務 首先從執行緒相關靜態變數中獲取出所有事務資訊 然後依次從最後一個事務到第一個事務進行回滾 最後清空當前執行緒關聯的所有事務資訊 */ protected void completeTransactionAfterThrowing(TransactionInfo txInfo1, Throwable ex) { List<TransactionInfo> trans = transactionInfoList.get(); if (trans != null && trans.size() > 0) { for (int i = trans.size() - 1; i >= 0; i--) { TransactionInfo txInfo = trans.get(i); if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex); } if (txInfo.getTransactionAttribute().rollbackOn(ex)) { try { txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by rollback exception", ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException ex2) { logger.error("Application exception overridden by rollback exception", ex); throw ex2; } catch (Error err) { logger.error("Application exception overridden by rollback error", ex); throw err; } } // 這塊程式碼不知道是在幹什麼,和父類的一樣 else { // We don't roll back on this exception. // Will still roll back if TransactionStatus.isRollbackOnly() is true. try { txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by commit exception", ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException ex2) { logger.error("Application exception overridden by commit exception", ex); throw ex2; } catch (Error err) { logger.error("Application exception overridden by commit error", ex); throw err; } } } clearTransactionInfos(); } } } |
- 全域性配置類
不管是上面的自定義事務攔截器或者spring事務註解解析器,都需要進行設定。
@Configuration public class DefaultTransactionConfig { @Resource private DBConfig primaryConfig; @Resource private TransactionInterceptor transactionInterceptor; @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(primaryConfig.transactionAttributeSource());// 預設庫事務配置 advisor.setAdvice(transactionInterceptor);// 自定義攔截器替換spring預設的事務攔截器 advisor.setOrder(Ordered.LOWEST_PRECEDENCE); return advisor; } /** * 該方法將自定義的事務攔截器替換spring的事務攔截器 * * @return */ @Bean("transactionInterceptor") @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionInterceptor transactionInterceptor() { TransactionInterceptor interceptor = new DefaultTransactionInterceptor(); interceptor.setTransactionAttributeSource(primaryConfig.transactionAttributeSource()); interceptor.setTransactionManager(DefaultDBConfigContent.transactionManagers.get(DefaultDBConfigContent.DEFAULT_DB)); return interceptor; } } |
4、其他輔助資訊
- DefaultBasePo類
dataSourceName主要是讓使用者設定該資料是操作那個資料來源的,如果未設定,則只操作預設資料來源。
public class DefaultBasePo { @Transient private String dataSourceName; public String getDataSourceName() { return dataSourceName; } public void setDataSourceName(String dataSourceName) { this.dataSourceName = dataSourceName; } } |
- DefaultBaseDao類
entityManager需要動態注入,它需要和資料來源進行關聯。
public class DefaultBaseDao{ private EntityManager entityManager; public void setEntityManager(EntityManager entityManagerPrimary) { this.entityManager = entityManagerPrimary; } } |
- DefaultBaseService類
在DefaultBaseService類中,DefaultBaseDao需要子類注入,但在類DefaultBaseService中可以自動注入DefaultBaseDao中的實體管理器,因為只有在有事務的類或方法中,才能得到事務對應的資料來源,才能進行注入。而這個自動注入工作在自定義事務攔截器中進行設定的。
public abstract class DefaultBaseService { public void setEntityManager(EntityManager entityManager) { getDao().setEntityManager(entityManager); } public abstract void setDao(DefaultBaseDao dao); public abstract DefaultBaseDao getDao(); } |
到目前為止,系統框架性核心功能已經提供出來了,即上述程式碼可以打成一個jar包了,但如果需要真正執行起來,還需要結合專案實際程式碼才行,因為目前提供的dao和service並沒有提供增刪改查相關功能。
- 專案實際程式碼
@Repository("baseDao") public class BaseDao extends DefaultBaseDao{ public void save(final Object entity) { entityManager.persist(entity); } public List<?> findAll(Class<?> entityClass) { String sql="select po from " + entityClass.getSimpleName() + " po where 1=1 order by id desc"; Query query = entityManager.createQuery(sql); List<?> tmpList = query.getResultList(); return tmpList; } } |
@Transactional public class BaseService<V, P> extends DefaultBaseService { private BaseDao baseDao; public void save(V vo) { baseDao.save(vo); } @SuppressWarnings("unchecked") public List<V> findAll(DefaultBasePo po) { return (List<V>) baseDao.findAll(po.getClass()); } @Override @Resource public void setDao(DefaultBaseDao dao) { baseDao = (BaseDao) dao; } @Override public DefaultBaseDao getDao() { return baseDao; } } |
到此為止,單資料來源配置完成,此時可以寫測試程式碼向預設資料來源進行增刪改查操作了。
- 多資料來源實現時的補充配置程式碼
為了實現對多資料來源的操作,需要對系統增加相關資料來源的配置。
- 在資料庫配置檔案中新增第二第三資料來源配置。
- 新增第二第三資料來源配置
/** * 第二資料來源配置 */ @Configuration @EnableTransactionManagement @EnableJpaRepositories( entityManagerFactoryRef = "entityManagerFactorySecondary", transactionManagerRef = "transactionManagerSecondary", basePackages = {"net.thinktrader.fundta" }// 設定Repository所在位置 ) @Component("secondaryConfig") public class SecondDataSourceConfig extends DBConfig implements DataSourceConfig { @Bean(name = "secondaryDataSource") @Qualifier("secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource initDataSource() { return DataSourceBuilder.create().build(); } @Autowired @Qualifier("secondaryDataSource") private DataSource secondaryDataSource; @Bean(name = "entityManagerSecondary") public EntityManager entityManager(EntityManagerFactoryBuilder builder) { return entityManagerFactory(builder).getObject().createEntityManager(); } @Bean(name = "entityManagerFactorySecondary") public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) { return builder.dataSource(secondaryDataSource) .properties(getVendorProperties(secondaryDataSource)) .packages("net.thinktrader.fundta") // 設定實體類所在位置 .persistenceUnit("secondaryPersistenceUnit").build(); } @Bean(name = "transactionManagerSecondary") public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(entityManagerFactory(builder).getObject()); } @Bean("transactionManagerSecondarySource") @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { DefaultTransactionAnnotationParser myParser = new DefaultTransactionAnnotationParser("transactionManagerSecondary"); return new AnnotationTransactionAttributeSource(myParser); } } /** * 第三資料來源配置 */ @Configuration @EnableTransactionManagement @EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactorySecondary", transactionManagerRef = "transactionManagerSecondary", basePackages = { "net.thinktrader.fundta" }) // 設定Repository所在位置 @Component("threeConfig") public class ThreeDataSourceConfig extends DBConfig implements DataSourceConfig { /* * (non-Javadoc) * * @see * net.thinktrader.fundta.test.dao2.datasource.TADataSourceConfig#initDataSource * () */ @Bean(name = "threeDataSource") @Qualifier("threeDataSource") @ConfigurationProperties(prefix = "spring.datasource.three") // 對應配置檔案中字首為spring.datasource.secondary的配置項 public DataSource initDataSource() { return DataSourceBuilder.create().build(); } @Autowired @Qualifier("threeDataSource") private DataSource threeDataSource; @Bean(name = "entityManagerThree") public EntityManager entityManager(EntityManagerFactoryBuilder builder) { return entityManagerFactory(builder).getObject().createEntityManager(); } @Bean(name = "entityManagerFactoryThree") public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) { return builder.dataSource(threeDataSource).properties(getVendorProperties(threeDataSource)) .packages("net.thinktrader.fundta") // 設定實體類所在位置 .persistenceUnit("threePersistenceUnit").build(); } @Bean(name = "transactionManagerThree") public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(entityManagerFactory(builder).getObject()); } @Bean("transactionManagerThreeSource") @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { DefaultTransactionAnnotationParser myParser = new DefaultTransactionAnnotationParser("transactionManagerThree"); return new AnnotationTransactionAttributeSource(myParser); } } |
- 設定第二第三資料來源的執行環境
@Component public class TADBConfigContent extends DefaultDBConfigContent { // 其他資料來源別名 public final static String DB2 = "db2"; public final static String DB3 = "db3"; // 其他資料來源持久環境註冊 @PersistenceContext(unitName = "secondaryPersistenceUnit") private EntityManager entityManagerSecondary; // 其他資料來源持久環境註冊 @PersistenceContext(unitName = "threePersistenceUnit") private EntityManager entityManagerThree; // 其他資料來源事務管理器 @Resource private PlatformTransactionManager transactionManagerSecondary; // 其他資料來源事務管理器 @Resource private PlatformTransactionManager transactionManagerThree; @Resource private DBConfig secondaryConfig; @Resource private DBConfig threeConfig; @PostConstruct public void init() { super.init();//主資料來源環境配置 manangerMap.put(DB2, entityManagerSecondary); manangerMap.put(DB3, entityManagerThree); transactionManagers.put(DB2, transactionManagerSecondary); transactionManagers.put(DB3, transactionManagerThree); transactionAttributeSourceMap.put(DB2, secondaryConfig.transactionAttributeSource()); transactionAttributeSourceMap.put(DB3, threeConfig.transactionAttributeSource()); } } |
到此為止,預設但屬於或多資料來源的相關配置已經完成,剩下的就是進行測試。
5、測試多資料來源
- User實體類和Message實體類
@Entity public class User extends DefaultBasePo{ @Id @GeneratedValue private Long id; @Column(nullable = false) private String name; @Column(nullable = false) private Integer age; public User() { } public User(String name, Integer age) { this.name = name; this.age = age; } //get set方法 } @Entity public class Message extends DefaultBasePo{ @Id @GeneratedValue private Long id; @Column(nullable = false) private String name; @Column(nullable = false) private String content; public Message() { } public Message(String name, String content) { this.name = name; this.content = content; } // 省略getter、setter } |
- UserService和MessageService類
@Service("userService") public class UserService extends BaseService<User, User>{} @Service("messageService") public class MessageService extends BaseService<Message, Message>{} |
- 系統啟動類
@ServletComponentScan @SpringBootApplication @EnableScheduling @EnableTransactionManagement public class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } } |
- 單元測試類
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest @EnableTransactionManagement public class ApplicationTests { @Autowired private MyTest test; @Test public void test() throws Exception { test.test(); } } |
- 其他
@Service public class MyTest { @Autowired private UserService userService; @Autowired private MessageService messageService; @Transactional //故意開啟一個事務 public void test() throws Exception { /**將user物件儲存到資料庫中,如果i為偶數,儲存到主庫,為奇數,儲存到從庫**/ for(int i=0;i<10;i++) { User u1=new User("aaa"+i, 10*i); if(i%2==0) { u1.setDataSourceName(DBConfigContent.DB1); }else { u1.setDataSourceName(DBConfigContent.DB2); } userService.save(u1); } /**將user物件儲存到第三的一個數據庫中**/ for(int i=0;i<10;i++) { User u1=new User("aaa"+i, 10*i); u1.setDataSourceName(DBConfigContent.DB3); userService.save(u1); } /**查詢主庫的user物件**/ User u=new User(); u.setDataSourceName(DBConfigContent.DB1); Assert.assertEquals(5, userService.findAll(u).size()); /**查詢第二個庫的user物件**/ u.setDataSourceName(DBConfigContent.DB2); Assert.assertEquals(5, userService.findAll(u).size()); /**查詢第三個庫的user物件**/ u.setDataSourceName(DBConfigContent.DB3); Assert.assertEquals(10, userService.findAll(u).size()); /**將Message物件儲存到資料庫中,如果i為偶數,儲存到主庫,為奇數,儲存到從庫**/ for(int i=0;i<10;i++) { Message m1=new Message("o1"+i, "aaaaaaaaaa"+i); if(i%2==0) { m1.setDataSourceName(DBConfigContent.DB1); }else { m1.setDataSourceName(DBConfigContent.DB2); } messageService.save(m1); } /**查詢主庫的Message物件**/ Message m=new Message(); m.setDataSourceName(DBConfigContent.DB1); Assert.assertEquals(5, messageService.findAll(m).size()); /**查詢第二個庫的Message物件**/ m.setDataSourceName(DBConfigContent.DB2); Assert.assertEquals(5, messageService.findAll(m).size()); int x=10/0;//丟擲異常 } } |
通過呼叫實體物件的setDataSourceName方法,設定該物件訪問的資料來源。
方法最後,模擬一個異常情況,因為我們在自定義事務攔截器中,重寫了spring預設事務攔截器的相關行為,因此能夠回滾所有資料來源資料(spring預設只回滾異常發生時對應資料來源的事務)。