An overview of the

I recently developed a workflow (Activiti) project, which wanted to separate the Activiti database from the business database for easy expansion. The data sources need to be defined separately for both databases in the project. Note: Atomikos below can only solve the transaction problems of workflow projects when applying a single service node. Other distributed management frameworks, such as SEATA, need to be used in microservices architecture, and then revert to the most basic ‘configure multiple data sources’ approach described below.

Configuring multiple data sources

Yml configures two data sources, ACT and Business:

datasource: act: jdbcUrl: jdbc:mysql://localhost:3306/lmwy_product_act? useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driverClassName: com.mysql.jdbc.Driver business: jdbc-url: jdbc:mysql://localhost:3306/lmwy_product? useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.DriverCopy the code

Configuration corresponding to two data sources:

package com.zhirui.lmwy.flow.config; import org.activiti.spring.boot.AbstractProcessEngineAutoConfiguration; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; Activiti database connection pool * default, we can't modify the source code so default * 2. BusinessDataSource */ @Configuration Public Class FlowDatasourceConfig extends AbstractProcessEngineAutoConfiguration { @Bean @Primary @ConfigurationProperties(prefix ="spring.datasource.act")
    @Qualifier("activitiDataSource")
    public DataSource activitiDataSource() {
        DataSource build = DataSourceBuilder.create().build();
        return build;
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.business")
    @Qualifier("businessDataSource")
    public DataSource businessDataSource() {
        DataSource build = DataSourceBuilder.create().build();
        returnbuild; }}Copy the code

Springdata configuration and transaction manager configuration for the business

package com.zhirui.lmwy.flow.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.util.HashMap; /** * you need to add @EnableJpaRepositories(basePackages={"Package path corresponding to DAO layer"}), so the DAO layer of JPA is injected. When spring Boot is started, the system fails to managetypeError: class ******, missing jPA Entity path configuration, add tag to the header of configuration class: @entityscan ("Package path for entity")
 */
@Configuration
@EnableJpaRepositories(
        basePackages = {"com.zhirui.lmwy.flow.dao"},// entityManagerFactoryRef ="flowEntityManager",
        transactionManagerRef = "flowTransactionManager"
)
public class JpaRepositoriesConfig {

    @Autowired
    private Environment env;

    @Autowired
    @Qualifier("businessDataSource") private DataSource businessDataSource; / * * * * / create entityManagerFactory factory @ Bean @ Primary public LocalContainerEntityManagerFactoryBeanflowEntityManager() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(businessDataSource); // Configure the scanned entity class package, otherwise error: No persistence units parsed from {classpath*:META-INF/persistence.xml} em.setPackagesToScan(new String[]{"com.zhirui.lmwy.flow.entity"}); HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); HashMap<String, Object> properties = new HashMap<>(); // application. Yaml configuration file ddl-auto value // properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.show_sql"."true");
        properties.put("hibernate.format_sql"."true"); // application. Yaml configuration file database-platform value // properties."hibernate.dialect"."org.hibernate.dialect.MySQLDialect");
        properties.put("hibernate.implicit_naming_strategy"."org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
        properties.put("hibernate.physical_naming_strategy"."org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        em.setJpaPropertyMap(properties);
        returnem; } / * * * * / create a transaction manager @ Primary @ Bean public PlatformTransactionManagerflowTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(flowEntityManager().getObject());
        returntransactionManager; }}Copy the code

Act data sources are used in Activiti

package com.zhirui.lmwy.flow.config; import lombok.AllArgsConstructor; import org.activiti.spring.SpringProcessEngineConfiguration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; /** * Activiti config */ @configuration@allargsconstructor Public class ActivitiConfig {private final DataSource dataSource; @Autowired @Qualifier("activitiDataSource")
    private DataSource activitiDataSource;

    @Bean
    public SpringProcessEngineConfiguration getProcessEngineConfiguration() {
        SpringProcessEngineConfiguration config =
                new SpringProcessEngineConfiguration();
        config.setDataSource(activitiDataSource);
        config.setTransactionManager(activitiTransactionManager());
        return config;
    }

    @Bean
    public DataSourceTransactionManager activitiTransactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(activitiDataSource);
        returntransactionManager; }}Copy the code

Transaction management problem

The transaction manager for Business is “flowTransactionManager” and the @primary component is configured, so the data source used in the following methods is “flowTransactionManager”. Operations against the act data source cannot be rolled back.

@Override @Transactional(rollbackFor = Exception.class) public void commit(FlowTaskInstance flowTaskInstance, String tenantId) throws Exception {Operation on data source ACT operation on data source Business...Copy the code

You can specify a transaction manager name for @transactional (rollbackFor = exception.class) :

@Transactional(rollbackFor = Exception.class, transactionManager = "activitiTransactionManager")Copy the code

Methods used things manager for “activitiTransactionManager”, but in view of the data source of business operation and will not be able to roll back.

We need a distributed transaction manager.

* If it is a multi-data source transaction with single application and single node service, atomikos in the afternoon can be used to realize the solution of distributed party committee.

* If it’s a microservices architecture, then just integrate SeATA on top of it!

Atomikos implements distributed transactions

Take a look at the transaction manager in Spring

PlatformTransactionManager top interface defines the core transaction management method, the following is a layer of AbstractPlatformTransactionManager abstract class, Implements the PlatformTransactionManager interface method and defines some abstract method, for the subclass. At the bottom layer are the two classic transaction managers:

1. DataSourceTransactionmanager: local resources transaction manager, is also the spring the default transaction manager.

2.JtaTransactionManager: Multi-resource transaction manager (also known as distributed transaction manager), which implements THE JTA specification and uses XA protocol for two-phase commit.

3. Atomikos is a specific technology of JTA specification, which is relatively hot and popular.

Pom configuration

<! Distributed transaction Management https://blog.csdn.net/qq_35387940/article/details/103474353 --> <dependency> <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>Copy the code

Yaml configuration

#datasource
spring:
  jta:
    enabled: trueatomikos: datasource: act: xa-properties.url: jdbc:mysql://localhost:3306/lmwy_product_act? useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&pinGlobalTxToPhysicalConnection=truexa-properties.user: root xa-properties.password: 123456 xa-data-source-class-name: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource unique-resource-name: act max-pool-size: 10 min-pool-size: 1 max-lifetime: 10000 borrow-connection-timeout: 10000 business: xa-properties.url: jdbc:mysql://localhost:3306/lmwy_product? useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&pinGlobalTxToPhysicalConnection=true
          xa-properties.user: root
          xa-properties.password: 123456
          xa-data-source-class-name: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
          unique-resource-name: business
          max-pool-size: 10
          min-pool-size: 1
          max-lifetime: 10000
          borrow-connection-timeout: 10000Copy the code

Configure multiple data sources and transaction managers

package com.zhirui.lmwy.flow.config; import com.atomikos.icatch.jta.UserTransactionImp; import com.atomikos.icatch.jta.UserTransactionManager; import org.activiti.spring.boot.AbstractProcessEngineAutoConfiguration; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.transaction.jta.JtaTransactionManager; import javax.sql.DataSource; import javax.transaction.SystemException; import javax.transaction.UserTransaction; /** * Multi-data source configuration class * 1. Activiti database connection pool * default * 2. Workflow Business database connection pool * specifies businessDataSource */ @Configuration Public class AtomikosConfig extends AbstractProcessEngineAutoConfiguration { @Primary @Bean(name ="actDatasource")
    @Qualifier("actDatasource")
    @ConfigurationProperties(prefix="spring.jta.atomikos.datasource.act")
    public DataSource actDatasource() {
        return new AtomikosDataSourceBean();
    }

    @Bean(name = "businessDatasource")
    @Qualifier("businessDatasource")
    @ConfigurationProperties(prefix="spring.jta.atomikos.datasource.business")
    public DataSource businessDatasource() {
        return new AtomikosDataSourceBean();
    }

    @Bean("jtaTransactionManager")
    @Primary
    public JtaTransactionManager activitiTransactionManager() throws SystemException {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        returnnew JtaTransactionManager(userTransaction, userTransactionManager); }}Copy the code

Business JPA binds data sources

package com.zhirui.lmwy.flow.config; import org.hibernate.engine.transaction.jta.platform.internal.AtomikosJtaPlatform; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; import java.util.HashMap; import java.util.Map; /** * you need to add @EnableJpaRepositories(basePackages={"Package path corresponding to DAO layer"}), so the DAO layer of JPA is injected. When spring Boot is started, the system fails to managetypeError: class ******, missing jPA Entity path configuration, add tag to the header of configuration class: @entityscan ("Package path for entity")
 */
@Configuration
@EnableJpaRepositories(
        basePackages = {"com.zhirui.lmwy.flow.dao"},// entityManagerFactoryRef ="flowEntityManager",
        transactionManagerRef = "jtaTransactionManager"Public class JpaRepositoriesConfig {@autoWired private Environment env; @Autowired @Qualifier("businessDatasource") private DataSource businessDataSource; / * * * * / create entityManagerFactory factory @ Bean / / @ Primary public LocalContainerEntityManagerFactoryBeanflowEntityManager() {/ / * * * * * * / jta transaction management/AtomikosJtaPlatform setTransactionManager (transactionManager); // AtomikosJtaPlatform.setUserTransaction(userTransaction); LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); // *** jta datasource *** em.setJtaDataSource(businessDataSource); // em.setDataSource(businessDataSource); // Configure the scanned entity class package, otherwise error: No persistence units parsed from {classpath*:META-INF/persistence.xml} em.setPackagesToScan(new String[]{"com.zhirui.lmwy.flow.entity"});
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap<String, Object> properties = new HashMap<>();

        // *** jta datasource ***
        properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
        properties.put("javax.persistence.transactionType"."JTA"); // application. Yaml configuration file ddl-auto value // properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.show_sql"."true");
        properties.put("hibernate.format_sql"."true"); // application. Yaml configuration file database-platform value // properties."hibernate.dialect"."org.hibernate.dialect.MySQLDialect");
        properties.put("hibernate.implicit_naming_strategy"."org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
        properties.put("hibernate.physical_naming_strategy"."org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        em.setJpaPropertyMap(properties);
        returnem; }}Copy the code

Activiti binds data sources

package com.zhirui.lmwy.flow.config; import lombok.AllArgsConstructor; import org.activiti.spring.SpringProcessEngineConfiguration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.transaction.jta.JtaTransactionManager; import javax.sql.DataSource; /** * Activiti Configuration */ @configuration@allargsconstructor Public class ActivitiConfig {@autowired @qualifier ("actDatasource")
    private DataSource activitiDataSource;

    @Bean
    @Primary
    public SpringProcessEngineConfiguration getProcessEngineConfiguration(JtaTransactionManager jtaTransactionManager) {
        SpringProcessEngineConfiguration config =
                new SpringProcessEngineConfiguration();
        config.setDataSource(activitiDataSource);
        config.setTransactionManager(jtaTransactionManager);
        returnconfig; }}Copy the code

Testing:

Execute the task submission method, and rollback will be performed after an error is reported

public void commit(){// Action 1: activiti executes the task using the act data source taskService.plete (task.getid ()); . // Operation 2: Update the business process object, save the business object, use the business data source flowinstancedao. save(flowInstance); . int a = 100 / 0; }Copy the code

Reference:

www.manongjc.com/detail/6-an…

www.cnblogs.com/xkzhangsanx…

www.jianshu.com/p/099c0850b…