1. 程式人生 > >springboot+jpa配置多資料來源

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。

  1. 資料來源介面和配置抽象類:
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(); }
  1. 實現主庫(預設庫)資料來源配置:
@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);     } }
  1. 配置環境類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、系統全域性配置

  1. 重寫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成員變數即為事務管理器的名稱。

  1. 重寫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();         }     } }
  1. 全域性配置類

不管是上面的自定義事務攔截器或者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、其他輔助資訊

  1. DefaultBasePo類

dataSourceName主要是讓使用者設定該資料是操作那個資料來源的,如果未設定,則只操作預設資料來源。

public class DefaultBasePo {     @Transient     private String dataSourceName;     public String getDataSourceName() {         return dataSourceName;     }     public void setDataSourceName(String dataSourceName) {         this.dataSourceName = dataSourceName;     } }
  1. DefaultBaseDao類

entityManager需要動態注入,它需要和資料來源進行關聯。

public class DefaultBaseDao{     private EntityManager entityManager;         public void setEntityManager(EntityManager entityManagerPrimary) {         this.entityManager = entityManagerPrimary;     } }
  1. 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並沒有提供增刪改查相關功能。

  1. 專案實際程式碼
@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;     } }

到此為止,單資料來源配置完成,此時可以寫測試程式碼向預設資料來源進行增刪改查操作了。

  1. 多資料來源實現時的補充配置程式碼

為了實現對多資料來源的操作,需要對系統增加相關資料來源的配置。

  1. 在資料庫配置檔案中新增第二第三資料來源配置。
  2. 新增第二第三資料來源配置
/**  * 第二資料來源配置 */ @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);     } }
  1. 設定第二第三資料來源的執行環境
@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、測試多資料來源

  1. 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 }
  1. UserService和MessageService類
@Service("userService") public class UserService extends BaseService<User, User>{} @Service("messageService") public class MessageService extends BaseService<Message, Message>{}
  1. 系統啟動類
@ServletComponentScan @SpringBootApplication @EnableScheduling @EnableTransactionManagement public class Application {     public static void main(String[] args) throws Exception     {         SpringApplication.run(Application.class, args);      } }
  1. 單元測試類
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest @EnableTransactionManagement public class ApplicationTests {     @Autowired     private MyTest test;     @Test     public void test() throws Exception {         test.test();     } }
  1. 其他
@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預設只回滾異常發生時對應資料來源的事務)。