Как нужно настроить менеджер транзакций JtaTransactionManager, чтобы делать распределенные транзакции в разные БД? Ниже привожу полоную конфигурацию, которая а) из-за неработающего менеджера транзакций не сохраняет в БД запись б) проверить работает ли rollback при неудачном insert в одну из баз я не могу, потому что не работает пункт а.
Exception не ловится, в catch код не попадает.
Подозреваю, что менеджеру транзакций надо как-то передать dataSource-ы, с которыми он будет работать.
Конфигурация:
package sp.config;
import com.atomikos.icatch.jta.J2eeUserTransaction;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
@Configuration
public class NewDbConfig {
@Bean(name="dataSourceA")
public AtomikosDataSourceBean dataSourceA() {
AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
dataSource.setUniqueResourceName("DataSourceA");
MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
mysqlXADataSource.setDatabaseName("atomikos_one");
mysqlXADataSource.setServerName("localhost");
mysqlXADataSource.setPort(3306);
mysqlXADataSource.setUser("root");
mysqlXADataSource.setPassword("mypass");
mysqlXADataSource.setUrl("jdbc:mysql://localhost:3306/atomikos_one");
dataSource.setXaDataSource(mysqlXADataSource);
return dataSource;
}
@Bean(name="dataSourceB")
public AtomikosDataSourceBean dataSourceB() {
AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
dataSource.setUniqueResourceName("DataSourceB");
MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
mysqlXADataSource.setDatabaseName("atomikos_two");
mysqlXADataSource.setServerName("localhost");
mysqlXADataSource.setPort(3306);
mysqlXADataSource.setUser("root");
mysqlXADataSource.setPassword("mypass");
mysqlXADataSource.setUrl("jdbc:mysql://localhost:3306/atomikos_two");
dataSource.setXaDataSource(mysqlXADataSource);
return dataSource;
}
@Bean(name="entityManagerFactoryA")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryA(
@Qualifier("dataSourceA") DataSource dataSource,
@Qualifier("vendorAdapterA") JpaVendorAdapter vendorAdapter
) {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setPackagesToScan("sp.model.atomikosA");
factoryBean.setDataSource(dataSource);
factoryBean.setJpaVendorAdapter(vendorAdapter);
return factoryBean;
}
@Bean(name="vendorAdapterA")
public JpaVendorAdapter hibernateJpaVendorAdapterA() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
return vendorAdapter;
}
@Bean(name="entityManagerFactoryB")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryB(
@Qualifier("dataSourceB") DataSource dataSource,
@Qualifier("vendorAdapterB") JpaVendorAdapter vendorAdapter
) {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setPackagesToScan("sp.model.atomikosB");
factoryBean.setDataSource(dataSource);
factoryBean.setJpaVendorAdapter(vendorAdapter);
return factoryBean;
}
@Bean(name="vendorAdapterB")
public JpaVendorAdapter hibernateJpaVendorAdapterB() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
return vendorAdapter;
}
@Bean(name="atomikosTransactionManager")
public UserTransactionManager userTransactionManager() {
UserTransactionManager transactionManager = new UserTransactionManager();
transactionManager.setForceShutdown(false);
return transactionManager;
}
@Bean(name="atomikosUserTransaction")
public UserTransactionImp atomikosUserTransaction() {
UserTransactionImp j2eeUserTransaction = new UserTransactionImp();
try {
j2eeUserTransaction.setTransactionTimeout(300);
} catch (Exception e) {
}
return j2eeUserTransaction;
}
@Bean(name="transactionManager")
public JtaTransactionManager jtaTransactionManager(
@Qualifier("atomikosTransactionManager") UserTransactionManager userTransactionManager,
@Qualifier("atomikosUserTransaction") UserTransactionImp j2eeUserTransaction
) {
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setTransactionManager(userTransactionManager);
jtaTransactionManager.setUserTransaction(j2eeUserTransaction);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
}
Dao 1:
package sp.dao.atomikosA;
import sp.model.atomikosA.Customer;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Service
public class CustomerADaoImpl implements CustomerADao {
@PersistenceContext(unitName="entityManagerFactoryA")
protected EntityManager em;
public void persistItem(Customer customer) {
em.persist(customer);
}
}
Dao 2:
package sp.dao.atomikosB;
import sp.model.atomikosB.Order;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Service
public class OrderBDaoImpl implements OrderBDao {
@PersistenceContext(unitName="entityManagerFactoryB")
protected EntityManager em;
public void persistItem(Order order) {
em.persist(order);
}
}
Сервис:
package sp.services;
import sp.dao.atomikosA.CustomerADao;
import sp.dao.atomikosB.OrderBDao;
import sp.model.atomikosA.Customer;
import sp.model.atomikosB.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private CustomerADao customerADao;
@Autowired
private OrderBDao orderBDao;
@Transactional(rollbackFor=Exception.class)
public void persistEmployees(Customer employeeA, Order employeeB) throws Exception {
System.out.println("Persist A");
customerADao.persistItem(employeeA);
System.out.println("Persist A OK - persist B");
orderBDao.persistItem(employeeB);
System.out.println("Persist B okk");
}
@Transactional
public void persistCustomer(Customer customer) throws Exception {
System.out.println("Persist A");
customerADao.persistItem(customer);
}
}
И сам вызов сервиса из контроллера:
@RequestMapping("/")
@ResponseBody
public String index() {
sp.model.atomikosA.Customer customer = new sp.model.atomikosA.Customer();
customer.setName("Maksat");
customer.setAge(27);
customer.setCustId(3L);
Order order = new Order();
order.setCustId(3L);
order.setCode(1);
order.setQuantity(1);
try {
employeeService.persistEmployees(customer, order);
employeeService.persistCustomer(customer);
} catch (Exception e) {
int del = 2;
}
return "success";
}
UPD:
Когда я создавал простой JpaTransactionManager:
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
То в аргумент-бин emf я предварительно сетил dataSource:
em.setDataSource(dataSource);
Таким образом мой PlatformTransactionManager знал dataSource, которым он управляет.
А при создании BitronixConfig мы не даем менеджеру транзакций никакой информации о dataSource-ах, которыми он будет управлять. Каким образом он тогда поймет какие базы у него в управлении?
@Bean
public PlatformTransactionManager bitronixTransactionManager() {
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setTransactionManager(TransactionManagerServices.getTransactionManager());
jtaTransactionManager.setUserTransaction(TransactionManagerServices.getTransactionManager());
jtaTransactionManager.setAllowCustomIsolationLevels(true);
jtaTransactionManager.setNestedTransactionAllowed(true);
jtaTransactionManager.afterPropertiesSet();
return jtaTransactionManager;
}
UPD 2:
Сделал по аналогии с https://github.com/Cepr0/dual-db-demo . Запустил транзакцию в рамках одной таблицы, первое значение валидное, второе не валидное. С транзакцией не должно записаться ничего. Но первая запись все-же добавилась, а вторая нет, т.е. транзакция не сработала в рамках одной БД. В рамках двух БД - распределенная транзакция - тоже не работает. Что я сделал в конфиге не так?
@Configuration
public class BitronixConfig {
@Bean
public PlatformTransactionManager bitronixTransactionManager() {
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setTransactionManager(TransactionManagerServices.getTransactionManager());
jtaTransactionManager.setUserTransaction(TransactionManagerServices.getTransactionManager());
jtaTransactionManager.setAllowCustomIsolationLevels(true);
jtaTransactionManager.setNestedTransactionAllowed(true);
jtaTransactionManager.afterPropertiesSet();
return jtaTransactionManager;
}
}
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(value = "sp.first.repo",
entityManagerFactoryRef = "firstEntityManagerFactory",
transactionManagerRef = "bitronixTransactionManager")
public class FirstDataSourceConfig {
@Primary
@Bean(name = "firstDataSource")
public DataSource firstDataSource() {
PoolingDataSource pds = new PoolingDataSource();
pds.setUniqueName("first");
pds.setClassName("com.mysql.cj.jdbc.MysqlXADataSource");
pds.setMaxPoolSize(16);
pds.setMinPoolSize(5);
pds.setAllowLocalTransactions(true);
pds.setIsolationLevel("READ_COMMITTED");
pds.setShareTransactionConnections(true);
pds.setEnableJdbc4ConnectionTest(true);
Properties driverProperties = pds.getDriverProperties();
driverProperties.put("user", "root");
driverProperties.put("password", "mypass");
driverProperties.put("url", "jdbc:mysql://localhost:3306/atomikos_one");
pds.init();
return pds;
}
@Primary
@Bean(name = "firstEntityManagerFactory")
@Autowired
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
@Qualifier("firstDataSource") DataSource dataSource,
@Qualifier("vendorAdapter") JpaVendorAdapter vendorAdapter
) {
Map<String, String> propertiesNew = new HashMap<>();
propertiesNew.put("hibernate.hbm2ddl.auto", "update");
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setJtaDataSource(dataSource);
bean.setPackagesToScan("sp.first.model");
bean.setPersistenceUnitName("first");
bean.setJpaPropertyMap(propertiesNew);
bean.setJpaVendorAdapter(vendorAdapter);
return bean;
}
@Bean(name="vendorAdapter")
public JpaVendorAdapter hibernateJpaVendorAdapter() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
return vendorAdapter;
}
@Bean(name="properties")
public Map<String, String> properties() {
Map<String, String> properties = new HashMap<>();
properties.put("hibernate.format_sql", "true");
properties.put("hibernate.transaction.jta.platform", "org.hibernate.engine.transaction.jta.platform.internal.BitronixJtaPlatform");
properties.put("hibernate.transaction.jta.enable", "true");
properties.put("hibernate.connection.release_mode", "after_statement");
return properties;
}
}
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(value = "sp.second.repo",
entityManagerFactoryRef = "secondEntityManagerFactory",
transactionManagerRef = "bitronixTransactionManager")
public class SecondDataSourceConfig {
@Bean(name = "secondDataSource")
public DataSource secondDataSource() {
PoolingDataSource pds = new PoolingDataSource();
pds.setUniqueName("second");
pds.setClassName("com.mysql.cj.jdbc.MysqlXADataSource");
pds.setMaxPoolSize(16);
pds.setMinPoolSize(5);
pds.setAllowLocalTransactions(true);
pds.setIsolationLevel("READ_UNCOMMITTED");
pds.setShareTransactionConnections(true);
pds.setEnableJdbc4ConnectionTest(true);
Properties driverProperties = pds.getDriverProperties();
driverProperties.put("user", "root");
driverProperties.put("password", "mypass");
driverProperties.put("url", "jdbc:mysql://localhost:3306/atomikos_two");
pds.init();
return pds;
}
@Bean(name = "secondEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
@Qualifier("secondDataSource") DataSource dataSource,
@Qualifier("vendorAdapter") JpaVendorAdapter vendorAdapter
) {
Map<String, String> propertiesNew = new HashMap<>();
propertiesNew.put("hibernate.hbm2ddl.auto", "update");
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setJtaDataSource(dataSource);
bean.setPackagesToScan("sp.second.model");
bean.setPersistenceUnitName("second");
bean.setJpaPropertyMap(propertiesNew);
bean.setJpaVendorAdapter(vendorAdapter);
return bean;
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
Есть два inline-block расположенные горизонтальноУ одго - ширина фиксированная, у другого - задана max-width
Есть определенная маска для полей с вводом телефона с кодом страны сделанная при помощи плагина maskedinputВозможно ли сделать так, чтобы код...
Затея такая: При клике на поле "Выберите город" выпадает меню, при двойном клике закрывается, при клике вне дива закрывается: В этом меню сразу...