springboot+jpa(hibernate)配置多数据源 分布式事务

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);
    }

   

@SuppressWarnings("rawtypes")
    private String getDataSource(Object[] ags) {
        String dataSource = DefaultDBConfigContent.DEFAULT_DB;
        /** 从BasePo中获取要访问的数据源别名 **/
        if (ags.length > 0) {
            for (Object obj : ags) {
                if (obj instanceof DefaultBasePo) {
                    DefaultBasePo po = (DefaultBasePo) obj;
                    dataSource = po.getDataSourceName();
                    break;
                }
                boolean flag=false;
                if(obj instanceof Collection) {
                    Collection c=(Collection) obj;
                    for(Object o:c) {
                        if (o instanceof DefaultBasePo) {
                            DefaultBasePo po = (DefaultBasePo) o;
                            dataSource = po.getDataSourceName();
                            flag=true;
                            break;
                        }
                    }
                }
                
                if(flag) {
                    break;
                }
            }
        }

        if (dataSource == null || dataSource.equals("")) {
            dataSource = DefaultDBConfigContent.DEFAULT_DB;
        }
        
        return dataSource;
    }
    /**
     * 这里有个假设: 假设所有需要访问数据库的方法都有一个BasePo类型的参数,如果没有参数,那么只能操作默认数据库
     */
    @Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Object[] ags = invocation.getArguments();
        String dataSource=getDataSource(ags);



        /** 根据数据源别名,注入实体管理器 **/
        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();
        }
    }
}
  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 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());
    }

   //默认spring注入的bean是单例,而不同service调用的是同一个dao,因此这里必须自己new
    @Override
    public DefaultBaseDao getDao() {
        baseDao=new BaseDao();
        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();
    }

@Test
    public void test() throws Exception {
        test.test1();
    }

}
  1. 其他
@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默认只回滚异常发生时对应数据源的事务)。

本文源码

猜你喜欢

转载自blog.csdn.net/zhaocuit/article/details/83092746
今日推荐