JBoss下的JTA使用理解
先给出通过JBoss容器获取JTA事务的方法
private UserTransaction getUserTransaction() throws ServletException {
UserTransaction ut;
try {
InitialContext ic = new InitialContext();
//comp java:/UserTransaction
ut = (UserTransaction) ic.lookup("java:comp/UserTransaction");
} catch(Exception e) {
throw new ServletException(e);
}
return ut;
}
经测试使用串“java:comp/UserTransaction”从JNDI获取JTA事务。启动后发现抛类型转换异常
org.jboss.tm.usertx.client.ServerVMClientUserTransaction cannot be cast to javax.transaction.UserTransaction
应用中多余引入了jta.jar导致。该jar已由JBoss提供,应用中删除即可.
另外有一点注意,如果是通过spring来获取事务管理器,上述异常出现时,只记录debug级别日志,不抛出异常,这点很变态,不知道写spring那群人怎么想的,spring取得TM代码如下:
protected UserTransaction findUserTransaction() { String jndiName = DEFAULT_USER_TRANSACTION_NAME; try { UserTransaction ut = getJndiTemplate().lookup(jndiName, UserTransaction.class); if (logger.isDebugEnabled()) { logger.debug("JTA UserTransaction found at default JNDI location [" + jndiName + "]"); } this.userTransactionObtainedFromJndi = true; return ut; } catch (NamingException ex) { if (logger.isDebugEnabled()) { logger.debug("No JTA UserTransaction found at default JNDI location [" + jndiName + "]", ex); } return null; } }
JTA属于分布式事务,如何结合JDBC事务呢,即在JTA事务内,对数据库的操作也具有事务特性。
通常在JBoss容器中使用数据库时,一般会在jboss-4.0.5.GA\server\default\deploy目录下面配置数据源,本地事务配置如下:
<datasources>
<local-tx-datasource>
<jndi-name>java:mysqlDS</jndi-name>
<connection-url>jdbc:mysql://192.168.0.10:1521/test</connection-url>
<driver-class>com.mysql.Driver</driver-class>
<user-name>root</user-name>
<password>root</password>
<min-pool-size>5</min-pool-size>
<max-pool-size>100</max-pool-size>
</local-tx-datasource>
</datasources>
在JBoss应用中通过JNDI获取该数据源时,JTA中对数据库的操作是不能回滚的,需要配置成分布式事务的形式,即:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<xa-datasource>
<jndi-name>mySQLDataSource</jndi-name>
<track-connection-by-tx>true</track-connection-by-tx>
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
<new-connection-sql>set autocommit=1</new-connection-sql>
<no-tx-separate-pools>true</no-tx-separate-pools>
<xa-datasource-property name="Url">jdbc:mysql://127.0.0.1:3306/mytest?autoReconnect=true</xa-datasource-property>
<xa-datasource-property name="User">root</xa-datasource-property>
<xa-datasource-property name="Password">root</xa-datasource-property>
<transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
<exception-sorter-class-name>
org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
</exception-sorter-class-name>
<type-mapping>mySQL</type-mapping>
<!--pooling parameters-->
<min-pool-size>5</min-pool-size>
<max-pool-size>100</max-pool-size>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>15</idle-timeout-minutes>
</xa-datasource>
</datasources>
转自:http://www.jdon.com/jivejdon/thread/30264 的一段说明。
View Code
通过对Mysql日志进行分析,发现在一次用户删除操作中并不是单纯的使用单个数据库连接来完成。通过进一步的跟踪发现,在I2SS中每一次调用数据库操作的方法总会试图获取一个新的connection。而且还有一个严重的问题就是我们的程序中利用UserTransaction 对事务进行控制(注 此API 仅用于控制JTA事务),而我们的数据源配置确是<local-tx-datasource>。如果利用JTA控制事务,而数据源是本地事务数据源的,两者搭配的情况是事务不会起作用,这就意味着从 JDBC 连接池接收连接时,会从该池将 AutoCommit 设置为 true。这就预示我们的JTA事务控制根本就不起作用。这也就解释了为什么用户删除事务操作失败的原因。
为了支持I2SS框架,目前的解决方式就是替换支持分布式事务的驱动,修改数据源配置将其替换成<xa-datasource>。配置文件如下:
<datasources>
<xa-datasource>
<jndi-name>DataSource</jndi-name>
<track-connection-by-tx>true</track-connection-by-tx>
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
<new-connection-sql>set autocommit=1</new-connection-sql>
<no-tx-separate-pools>true</no-tx-separate-pools>
<xa-datasource-property name="Url">jdbc:mysql://127.0.0.1:3306/sms1?autoReconnect=true</xa-datasource-property>
<xa-datasource-property name="User">smsdb</xa-datasource-property>
<xa-datasource-property name="Password">smsdb</xa-datasource-property>
<transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
<exception-sorter-class-name>
org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
</exception-sorter-class-name>
<type-mapping>mySQL</type-mapping>
<!--pooling parameters-->
<min-pool-size>5</min-pool-size>
<max-pool-size>100</max-pool-size>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>15</idle-timeout-minutes>
</xa-datasource>
</datasources>
在上述配置文件中用户色标记出的元素需要特别注意,而且这四个配置元素必须同时使用。下面将进行详细解释:
1、<no-tx-separate-pools>
这个元素的设定使得两个连接池能够分开:一个支持JTA事务的连接,另一个不支持JTA事务的连接。连接池在第一次使用时创建。
2、<track-connection-by-tx>
当这个值设置为true时,连接管理器会保持一个XID-to-connection影射表,当事务结束时而且所有的连接关闭或者未被引用时连接将放回连接池。这样做的副作用是不会挂起或者恢复连接的XAResource里的XID,这个行为和本地事务是一样的。
3、<xa-datasource-class>
指定javax.sql.XASDataSource实现类的全名
4、<new-connection-sql>
当新的连接创建时执行此语句。
在我们的配置文件中,我们进行了如下配置:
<new-connection-sql> set autocommit=1</new-connection-sql>
这样做的原因是因为我们通过MySQLXADataSource中获取的连接默认被设置成autocommit 为false,如果我们的连接并入到事务中时(即事务启动之后获取的连接),会统一由UserTransaction 进行提交。对于那些没有并入事务中的单连接,就会出现操作一直不能提交的错误,可能出现XAER_OUTSIDE 异常。通过上述的设置,我们可以将单连接操作放入一个隔离的连接池中,并将其设置为自动提交。
【避免此类问题的建议】
产生这个问题的原因可以归结为I2SS框架中事务处理不当,connection 封装不合理。为了适应框架改用分布式事务,理论上讲会造成非常大的性能开销。
注意需要在JBoss中引入支持XA的数据库驱动包
获取数据源和数据库连接的方式为:
//获取数据源:
InitialContext ic = new InitialContext();
ds = (DataSource) ic.lookup("java:mySQLDataSource");
//获取连接
Connection conn = ds.getConnection();
一个事务操作实例如下:
try {
ut.begin();
Admin a = new Admin();
a.setId(2);
adminDAO.delAdmin(a);
System.out.println("before add:" + cache.get("KEY-10"));
int index = serviceWithinTx(servletResponse, cache);
System.out.println("after add:" + cache.get("KEY-10"));
printLine(servletResponse, "Bye #" + (index + 1));
ut.commit();
} catch (Exception e) {
printLine(servletResponse, "Caught a " + e.getClass() + "! Rolling Tx back");
if (!printStackTrace) {
PrintWriter s = servletResponse.getWriter();
e.printStackTrace(s);
s.flush();
}
rollbackTransaction(ut);
}
OK!在JBoss容器环境中,利用JTA分布式事务特性将应用中事务(如:Ehcache中支持获取JBoss JTA事务管理器)与数据库事务(即JDBC事务,包括多JBDC事务)组合起来的实现成功了,但是在非JBoss容器环境下该如何处理呢?下一篇再解决。