Распределенная транзакция jta в spring boot

205
09 июня 2018, 09:20

Как нужно настроить менеджер транзакций 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;
    }
}
READ ALSO
inline-block с max-width переносится на новую строку

inline-block с max-width переносится на новую строку

Есть два inline-block расположенные горизонтальноУ одго - ширина фиксированная, у другого - задана max-width

220
Плагин maskedinput

Плагин maskedinput

Есть определенная маска для полей с вводом телефона с кодом страны сделанная при помощи плагина maskedinputВозможно ли сделать так, чтобы код...

320
Выпадающее меню с поиском и списком городов!

Выпадающее меню с поиском и списком городов!

Затея такая: При клике на поле "Выберите город" выпадает меню, при двойном клике закрывается, при клике вне дива закрывается: В этом меню сразу...

212