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…