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); } @SuppressWarnings("rawtypes") if (dataSource == null || dataSource.equals("")) { /** 根据数据源别名,注入实体管理器 **/ EntityManager entityMan = DefaultDBConfigContent.manangerMap.get(dataSource); Object targetClass = invocation.getThis(); if (targetClass instanceof DefaultBaseService) { //默认spring注入的bean是单例,因此需要加锁 //注意:在DefaultBaseService的子类中,只能用spring的单例模式,如果用多例模式会出现线程安全的问题 synchronized (targetClass) { DefaultBaseService baseSevice = (DefaultBaseService) targetClass; //默认spring注入的bean是单例,而不同service调用的是同一个dao,因此这里必须自己new baseSevice.setEntityManager(entityMan); /** 设置数据源对应的事务管理器和数据源对应的事务注解配置 **/ setTransactionManager(DefaultDBConfigContent.transactionManagers.get(dataSource)); setTransactionAttributeSource(DefaultDBConfigContent.transactionAttributeSourceMap.get(dataSource)); return super.invoke(invocation); } } else { /** 设置数据源对应的事务管理器和数据源对应的事务注解配置 **/ 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 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()); } //默认spring注入的bean是单例,而不同service调用的是同一个dao,因此这里必须自己new @Override public DefaultBaseDao getDao() { baseDao=new BaseDao(); 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(); } @Test public void test() throws Exception { test.test1(); } } |
- 其他
@Service public class MyTest { @Autowired private UserService userService; @Autowired private MessageService messageService; public void test() throws Exception { TestThread th1=new TestThread(userService, 0);//主库 TestThread th2=new TestThread(userService, 1);//从库 TestThread th3=new TestThread(userService, 2);//从库 th1.start(); th2.start(); th3.start(); th1.join(); th2.join(); th3.join(); } private static class TestThread extends Thread { private BaseService baseService; private int index; public TestThread(BaseService baseService, int index) { this.baseService = baseService; this.index = index; } public void run() { testthread(index, this, baseService); } } public static void testthread(int index, Thread th, BaseService baseService) { for (int i = 0; i < 20; i++) { User u1 = new User(th.getId() + "-" + index, 10 * i); if (i % 3 == 0) { u1.setDataSourceName(DefaultDBConfigContent.DB1); } else if (i % 3 == 1){ u1.setDataSourceName(TADBConfigContent.DB2); }else if (i % 3 == 2) { u1.setDataSourceName(TADBConfigContent.DB3); } u1.setName(th.getId() + "-" + i + "-" + u1.getDataSourceName()); baseService.save(u1); } } @Transactional // 故意开启一个事务 public void test1() 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(DefaultDBConfigContent.DB1); } else { u1.setDataSourceName(TADBConfigContent.DB2); } userService.save(u1); } /** 将user对象保存到第三的一个数据库中 **/ for (int i = 0; i < 10; i++) { User u1 = new User("aaa" + i, 10 * i); u1.setDataSourceName(TADBConfigContent.DB3); userService.save(u1); } /** 查询主库的user对象 **/ User u = new User(); u.setDataSourceName(DefaultDBConfigContent.DB1); Assert.assertEquals(5, userService.findAll(u).size()); /** 查询第二个库的user对象 **/ u.setDataSourceName(TADBConfigContent.DB2); Assert.assertEquals(5, userService.findAll(u).size()); /** 查询第三个库的user对象 **/ u.setDataSourceName(TADBConfigContent.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(DefaultDBConfigContent.DB1); } else { m1.setDataSourceName(TADBConfigContent.DB2); } messageService.save(m1); } /** 查询主库的Message对象 **/ Message m = new Message(); m.setDataSourceName(DefaultDBConfigContent.DB1); Assert.assertEquals(5, messageService.findAll(m).size()); /** 查询第二个库的Message对象 **/ m.setDataSourceName(TADBConfigContent.DB2); Assert.assertEquals(5, messageService.findAll(m).size()); int x=10/0; } } |
通过调用实体对象的setDataSourceName方法,设置该对象访问的数据源。
方法最后,模拟一个异常情况,因为我们在自定义事务拦截器中,重写了spring默认事务拦截器的相关行为,因此能够回滚所有数据源数据(spring默认只回滚异常发生时对应数据源的事务)。