Spring integration with iBATIS 3
Creating SqlSessionFactory
We want an SqlSessionFactory that can handle Spring's transactions.
SqlSessionFactoryBean
package ca.qc.ircm.mapper; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.sql.Connection; import java.sql.SQLException; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.sql.DataSource; import org.apache.ibatis.builder.xml.XMLConfigBuilder; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.NestedIOException; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; /** * SQL session factory that uses spring. * * @author poitrac */ public class SqlSessionFactoryBean implements SqlSessionFactory { private final Logger logger = LoggerFactory.getLogger(SqlSessionFactoryBean.class); /** * iBATIS environment. */ private Environment environment; /** * Location of iBATIS configuration files. */ private org.springframework.core.io.Resource configLocation; /** * SQL session factory for iBATIS. */ private SqlSessionFactory sqlSessionFactory; @PostConstruct public void buildFactory() throws IOException { logger.debug("Configuring ibatis with file: {}", configLocation); try { Reader reader = new InputStreamReader(configLocation.getInputStream()); XMLConfigBuilder parser = new XMLConfigBuilder(reader); Configuration configuration = parser.parse(); configuration.setEnvironment(environment); sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); } catch (IOException e) { throw new NestedIOException("Failed to parse config resource: " + configLocation, e); } } @PreDestroy public void cleanFactory() { sqlSessionFactory = null; } public void setConfigLocation(org.springframework.core.io.Resource configLocation) { this.configLocation = configLocation; } public void setEnvironment(Environment environment) { this.environment = environment; } public Configuration getConfiguration() { return sqlSessionFactory.getConfiguration(); } public SqlSession openSession() { return openSession(this.getConfiguration().getDefaultExecutorType()); } public SqlSession openSession(boolean autoCommit) { return openSession(this.getConfiguration().getDefaultExecutorType(), autoCommit); } public SqlSession openSession(Connection connection) { return openSession(this.getConfiguration().getDefaultExecutorType(), connection); } public SqlSession openSession(ExecutorType execType) { DataSource dataSource = environment.getDataSource(); Connection connection; try { if (dataSource instanceof TransactionAwareDataSourceProxy) { connection = dataSource.getConnection(); } else { connection = DataSourceUtils.doGetConnection(dataSource); } return sqlSessionFactory.openSession(execType, connection); } catch (SQLException ex) { throw (new SQLErrorCodeSQLExceptionTranslator(dataSource)).translate("iBATIS operation", null, ex); } } public SqlSession openSession(ExecutorType execType, boolean autoCommit) { return openSession(execType); } public SqlSession openSession(ExecutorType execType, Connection connection) { return sqlSessionFactory.openSession(execType, connection); } }
This SqlSessionFactory can supply a connection that is inside a transaction, but it cannot close it by itself. For this, we need a custom implementation of Transaction that closes Spring's transaction when appropriate.
SpringTransaction
package ca.qc.ircm.mapper; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.ibatis.transaction.Transaction; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; /** * iBATIS 3 transaction that is managed by Spring. * * @author poitrac */ public class SpringTransaction implements Transaction { private final DataSource dataSource; private final Connection connection; SpringTransaction(DataSource dataSource, Connection connection) { this.connection = connection; this.dataSource = dataSource; } public void close() throws SQLException { if (dataSource instanceof TransactionAwareDataSourceProxy) { connection.close(); } else { DataSourceUtils.doReleaseConnection(connection, dataSource); } } public void commit() throws SQLException { // Commit is done elsewhere. } public Connection getConnection() { return connection; } public void rollback() throws SQLException { // Rollback is done elsewhere. } }
We also need to supply a TransactionFactory that iBATIS will use to get our Transaction implementation.
SpringTransactionFactory
package ca.qc.ircm.mapper; import java.sql.Connection; import java.util.Properties; import javax.sql.DataSource; import org.apache.ibatis.transaction.Transaction; import org.apache.ibatis.transaction.TransactionFactory; /** * Factory for Spring transactions. * * @author poitrac */ public class SpringTransactionFactory implements TransactionFactory { /** * Connection's data source. */ private final DataSource dataSource; SpringTransactionFactory(DataSource dataSource) { this.dataSource = dataSource; } public Transaction newTransaction(Connection conn, boolean autoCommit) { return new SpringTransaction(dataSource, conn); } public void setProperties(Properties props) { } }
Now the spring.xml to glue everything together.
spring.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> <!-- Initialize iBATIS --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> <property name="validationQuery" value="${pingquery}"/> <property name="poolPreparedStatements" value="true"/> </bean> <!-- iBATIS 3 --> <bean id="transactionFactory" class="ca.qc.ircm.mapper.SpringTransactionFactory"> <constructor-arg index="0" ref="dataSource"/> </bean> <bean id="environment" class="org.apache.ibatis.mapping.Environment"> <constructor-arg index="0" value="default"/> <constructor-arg index="1" ref="transactionFactory"/> <constructor-arg index="2" ref="dataSource"/> </bean> <bean id="sqlSessionFactory" class="ca.qc.ircm.mapper.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:ca/qc/ircm/mapper/ibatisConfig.xml"/> <property name="environment" ref="environment"/> </bean> </beans>
The iBATIS 3 configuration file is quite simple.
ibatisConfig.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE configuration PUBLIC "-//ibatis.apache.org//DTD Config 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-config.dtd"> <configuration> <!-- Type aliases --> <!-- Mappers --> <mappers> <mapper resource="ca/qc/ircm/mapper/SomeMapper.xml"/> </mappers> </configuration>
