package jnpf.dmsBusinessOccupancy; import com.baomidou.dynamic.datasource.tx.ConnectionFactory; import com.baomidou.dynamic.datasource.tx.ConnectionProxy; import com.baomidou.dynamic.datasource.tx.TransactionContext; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.ExecutorType; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.SqlSessionUtils; import org.mybatis.spring.transaction.SpringManagedTransactionFactory; import org.springframework.jdbc.datasource.ConnectionHolder; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Objects; import java.util.function.Function; import static jnpf.dmsBusinessOccupancy.BusinessOccupancyException.Reason.*; /** Verifies the SAME session connection that executes the mapper; never starts a transaction. */ final class BusinessOccupancyTransactions { private final SqlSessionTemplate template; private final DataSource dataSource; BusinessOccupancyTransactions(SqlSessionTemplate template) { this.template = Objects.requireNonNull(template); var environment = template.getSqlSessionFactory().getConfiguration().getEnvironment(); if (!(environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) || template.getExecutorType() == ExecutorType.BATCH) throw new BusinessOccupancyException(UNSUPPORTED_TRANSACTION); this.dataSource = environment.getDataSource(); } BusinessFileOccupancyMapper reader() { return template.getMapper(BusinessFileOccupancyMapper.class); } T execute(Function work) { String xid = TransactionContext.getXID(); boolean spring = TransactionSynchronizationManager.isActualTransactionActive(); if (xid != null && spring) throw new BusinessOccupancyException(UNSUPPORTED_TRANSACTION); if (xid == null && !spring) throw new BusinessOccupancyException(TRANSACTION_REQUIRED); if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) throw new BusinessOccupancyException(READ_ONLY_TRANSACTION); // Check BEFORE opening a session: DataSourceUtils can otherwise enlist an unrelated // autocommit connection merely for synchronization, without starting a transaction on it. Object bound = TransactionSynchronizationManager.getResource(dataSource); if (spring && !(bound instanceof ConnectionHolder)) throw new BusinessOccupancyException(TRANSACTION_CONNECTION_MISMATCH); SqlSession session = SqlSessionUtils.getSqlSession(template.getSqlSessionFactory(), template.getExecutorType(), template.getPersistenceExceptionTranslator()); try { Connection connection = session.getConnection(); if (spring) { if (!DataSourceUtils.isConnectionTransactional(connection, dataSource)) throw new BusinessOccupancyException(TRANSACTION_CONNECTION_MISMATCH); } else { // A bare XID is not enough. The dynamic transaction must own this exact proxy. if (!(connection instanceof ConnectionProxy proxy) || ConnectionFactory.getConnection(xid, proxy.getDs()) != connection) throw new BusinessOccupancyException(TRANSACTION_CONNECTION_MISMATCH); } if (connection.getAutoCommit()) throw new BusinessOccupancyException(TRANSACTION_CONNECTION_MISMATCH); if (connection.isReadOnly()) throw new BusinessOccupancyException(READ_ONLY_TRANSACTION); if (connection.getTransactionIsolation() != Connection.TRANSACTION_READ_COMMITTED) throw new BusinessOccupancyException(UNSUPPORTED_ISOLATION); return work.apply(session.getMapper(BusinessFileOccupancyMapper.class)); } catch (SQLException e) { throw new IllegalStateException("Cannot verify business occupancy transaction connection", e); } finally { SqlSessionUtils.closeSqlSession(session, template.getSqlSessionFactory()); } } }