Hi, We have been looking to use IBATIS for our DAO layer, and have feedback on one or two design aspects. I will try to explain this as best I can - but it will probably take a few messages before I can get this across - so please bear with me.
Like many other folks, we are building an application framework that allows us to build our application in layers. Broadly speaking, we have: Presentation layer based upon Struts - I will call this the Client tier. Application Logic (Business Logic) layer. Database Layer - this is where we are planning to use IBATIS. We want the Client tier to know nothing about the DAO tier. Only the application tier knows about the DAO tier. The reason I use the term "Client tier" is that, instead of the Presentation tier, a client may also be a command line program, or a Job run by a Scheduler. The important thing for us is to ensure that the framework can work with or without a J2EE server. We also want to be able to run distributed transactions across two or more databases - without making any changes to the application or DAO layer. The idea is that the DAO layer deals with JDBC connections - and does not know or care how the JDBC connections are obtained. A JDBC connection may be from an ordinary datasource or from an XA datasource - it should not matter to the DAO. So far it must appear that there is no issue here - for IBATIS is designed to handle this requirement. However, this is not exactly the case. We think that the problem in IBATIS is that it mixes the "Transaction" pattern and the "Connection" pattern into one single abstraction. A Transaction in IBATIS is not only a mechanism for committing data, but also for managing the Connection object. Let me present a scenario that illustrates the kind of problem this creates. Let us say that we have two DAO classes - A and B. We have configured our system so that A accesses a different database from B. Now, we have two application level components - X and Y. X obtains an object of type A, whereas Y obtains and object of type B. A user of X or Y does not know that there are two databases involved. It is not even aware of how the DAO layer works. Within X, we have code that looks like this: DaoManager.startTransaction(); A.dowork(); DaoManager.commitTransaction(); DaoManager.endTransaction(); The code for Y looks similar, but uses B. >From a Client tier - the code that invokes X and Y may look like this: X x = new X; Y y = new Y; x.doWork(); y.doWork(); So far the code looks similar to the JPetStore example - our X and Y classes are like the "Service" classes in JPetStore. The problem starts if we do not want X and Y to commit or rollback - because we need the Client tier to decide when to commit or rollback. Remember that the Client tier knows nothing about the DAO layer. To commit therefore the Client tier must use a UserTransaction object like this: UserTransaction ut = new UserTransaction(); ut.begin(); X x = get new X; Y y = get new Y; x.doWork(); y.doWork(); ut.commit; But this won't work because IBATIS will attempt to commit inside DaoManager.commitTransaction(). We think that when a Global Transaction (JTA) is being used, IBATIS should only manage Connections and not attempt to perform transactions. The whole idea of JTA is that it is the "Client" that decides when to commit - the application tier cannot make that decision. Since all access to DaoManager is within the application tier - it follows from this that DaoManager must not attempt to manipulate a UserTransaction object. IBATIS treats JTA and JDBC as separate mechanisms - but the fact is that even with JTA, JDBC Connections are used. Hence, from the DAO perspective, the code for DAO that uses JDBC looks the same as that which uses JTA. We therefore think that this separation is incorrect - we only need one kind of DaoManager - a JDBC one. To support Global Transactions, all that is required is a flag that says - do not commit or rollback using the connection object - assume commits ae handled elsewhere. We have worked around this problem by replacing the IBATIS transaction classes with our own. In the example configuration and classes I am attaching, you will see that the same DAO classes are switched from JDBC to JTA/XA without any change in the type of Transaction Manager. Only the DataSource is changed, and a flag (globalCommit) is set. The other change we have made is that we do not assume that a DataSource will be obtained from JNDI in a JTA environment - instead we use a Factory class which is responsible for creating a DataSource. The factory class may or may not obtain the datasource from JNDI. I do not know if I have been able to explain the "problem" or the "solution". To summarise, what I am saying is: a) IBATIS should not manipulate UserTransaction objects. b) The difference between JTA and JDBC is a spurious one - only one type is required. Only the type of DataSource needs to be changed to switch from one to the other, and some intelligence that switches "off" local commits/rollbacks. In our case, we use a flag called "globalCommit" - if set to "true" (in JTA mode), this means that the DAO layer must not commit or rollback. You will also notice in the example that we use SqlMap in one DAO Context and plain JDBC in the other. For SqlMap implementation, we would have liked to push the DataSource/globalCommit settings from dao-config.xml - but IBATIS doesn't allow that. So we have had to setup these in the SqlMap config file. I should like to hear your views. Thanks and Regards Dibyendu
SqlMapTransactionConfig.java
Description: Binary data
DaoTransactionManager.java
Description: Binary data
GenericDaoTransaction.java
Description: Binary data
SqlMapDataSourceFactory.java
Description: Binary data
SqlMapTransaction.java
Description: Binary data
Constants.java
Description: Binary data
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE daoConfig
PUBLIC "-//iBATIS.com//DTD DAO Configuration 2.0//EN"
"http://www.ibatis.com/dtd/dao-2.dtd">
<daoConfig>
<context>
<transactionManager type="SQLMAP">
<property name="SqlMapConfigResource" value="resources/ibatis-sqlmaps/org/dm/js/dao/sql-map-config-nonxa.xml"/>
</transactionManager>
<dao interface="org.dm.js.dao.JobDAO" implementation="org.dm.js.dao.impl.ibatis.JobDAOImpl" />
</context>
<context>
<transactionManager type="com.ponl.framework.dao.ibatis.DaoTransactionManager">
<property name="dataSourceFactory" value="com.ponl.framework.sql.DBCPDataSourceFactory"/>
<property name="DataSource.driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="DataSource.url" value="jdbc:oracle:thin:@x.y.z.a:1521:db"/>
<property name="DataSource.user" value="scott"/>
<property name="DataSource.password" value="tiger"/>
<property name="globalCommit" value="false" />
</transactionManager>
<dao interface="org.dm.test.ibatis.DeptDao" implementation="org.dm.test.ibatis.DeptDaoImpl" />
</context>
</daoConfig>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE daoConfig
PUBLIC "-//iBATIS.com//DTD DAO Configuration 2.0//EN"
"http://www.ibatis.com/dtd/dao-2.dtd">
<daoConfig>
<context>
<transactionManager type="SQLMAP">
<property name="SqlMapConfigResource" value="resources/ibatis-sqlmaps/org/dm/js/dao/sql-map-config-xa.xml"/>
</transactionManager>
<dao interface="org.dm.js.dao.JobDAO" implementation="org.dm.js.dao.impl.ibatis.JobDAOImpl" />
</context>
<context>
<transactionManager type="com.ponl.framework.dao.ibatis.DaoTransactionManager">
<property name="dataSourceFactory" value="com.ponl.framework.tm.SimpleOracleXADataSourceFactory"/>
<property name="DataSource.driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="DataSource.url" value="jdbc:oracle:thin:@x.y.z.a:1521:db"/>
<property name="DataSource.user" value="scott"/>
<property name="DataSource.password" value="tiger"/>
<property name="globalCommit" value="true" />
</transactionManager>
<dao interface="org.dm.test.ibatis.DeptDao" implementation="org.dm.test.ibatis.DeptDaoImpl" />
</context>
</daoConfig>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<properties resource="resources/dao.properties" />
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
useStatementNamespaces="false"
/>
<transactionManager type="com.ponl.framework.dao.ibatis.SqlMapTransactionConfig" >
<property name="globalCommit" value="false" />
<dataSource type="com.ponl.framework.dao.ibatis.SqlMapDataSourceFactory">
<property name="dataSourceFactory" value="com.ponl.framework.sql.DBCPDataSourceFactory"/>
<property name="DataSource.driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="DataSource.url" value="jdbc:oracle:thin:@a.b.c.d:1521:db2"/>
<property name="DataSource.user" value="joe"/>
<property name="DataSource.password" value="blog"/>
</dataSource>
</transactionManager>
<sqlMap resource="resources/ibatis-sqlmaps/org/dm/js/dao/job.xml" />
</sqlMapConfig>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<properties resource="resources/dao.properties" />
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
useStatementNamespaces="false"
/>
<transactionManager type="com.ponl.framework.dao.ibatis.SqlMapTransactionConfig" >
<property name="globalCommit" value="true" />
<dataSource type="com.ponl.framework.dao.ibatis.SqlMapDataSourceFactory">
<property name="dataSourceFactory" value="com.ponl.framework.tm.SimpleOracleXADataSourceFactory"/>
<property name="DataSource.driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="DataSource.url" value="jdbc:oracle:thin:@a.b.c.d:1521:db2"/>
<property name="DataSource.user" value="joe"/>
<property name="DataSource.password" value="blogg"/>
</dataSource>
</transactionManager>
<sqlMap resource="resources/ibatis-sqlmaps/org/dm/js/dao/job.xml" />
</sqlMapConfig>

