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







Attachment: SqlMapTransactionConfig.java
Description: Binary data

Attachment: DaoTransactionManager.java
Description: Binary data

Attachment: GenericDaoTransaction.java
Description: Binary data

Attachment: SqlMapDataSourceFactory.java
Description: Binary data

Attachment: SqlMapTransaction.java
Description: Binary data

Attachment: 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>

Reply via email to