Hibernate应用程序部署在WebSphere AS 上出现记录死锁的分析与解决

有一个项目,是用Hibernate开发的部署在WebSphere Application Server 5.1下,总是会出现数据库记录被死锁的情况,即使是一个简单的更新动作,都有可能造成。


这个问题,困扰我许久,检查过所有代码,事物一定要提交或者回滚,Session及时释放,怀疑过是否是因为是部署在集群环境,但好像那些措施总于事无补,百思不得其解。

就差没有使用容器提供的事物了。

突然间想到去各自的官网,查查有没有人碰到过类似的信息,于是在 http://www.hibernate.org 网站找到了Hibernate in WAS 5.x - fixes possible deadlock problem,其中提到要定制一个 ConnectionProvider,名之为 WSDataSourceConnectionProvider,代码如下:
  1package com.unmi.db;
  2
  3import java.sql.Connection;
  4import java.sql.SQLException;
  5import java.util.Properties;
  6
  7import org.apache.log4j.Logger;
  8import org.hibernate.HibernateException;
  9import org.hibernate.cfg.Environment;
 10import org.hibernate.connection.ConnectionProvider;
 11import org.hibernate.util.NamingHelper;
 12
 13import com.ibm.websphere.rsadapter.JDBCConnectionSpec;
 14import com.ibm.websphere.rsadapter.WSCallHelper;
 15import com.ibm.websphere.rsadapter.WSDataSource;
 16import com.ibm.websphere.rsadapter.WSRRAFactory;
 17import com.ibm.ws.rsadapter.jdbc.WSJdbcConnection;
 18import com.ibm.ws.rsadapter.jdbc.WSJdbcUtil;
 19
 20/**
 21 * ConnectionProvider implementation that implements WSDataSource specific
 22 * Transaction Isolation level settings for the connection. Modified from the
 23 * original DatasourceConnectionProvider class by Lari Hotari
 24 *
 25 */
 26public class WSDataSourceConnectionProvider implements ConnectionProvider {
 27    public boolean supportsAggressiveRelease() {
 28        return false;
 29    }
 30
 31    private WSDataSource ds;
 32    private String user;
 33    private String pass;
 34    private Integer isolation;
 35
 36    private static final Logger log = Logger
 37            .getLogger(WSDataSourceConnectionProvider.class);
 38
 39    public void configure(Properties props) throws HibernateException {
 40
 41        String jndiName = props.getProperty(Environment.DATASOURCE);
 42        if (jndiName == null) {
 43            String msg = "datasource JNDI name was not specified by property "
 44                    + Environment.DATASOURCE;
 45            log.fatal(msg);
 46            throw new HibernateException(msg);
 47        }
 48
 49        user = props.getProperty(Environment.USER);
 50        pass = props.getProperty(Environment.PASS);
 51
 52        String i = props.getProperty(Environment.ISOLATION);
 53        if (i == null) {
 54            isolation = null;
 55        } else {
 56            isolation = new Integer(i);
 57            log.info("JDBC isolation level: "
 58                    + Environment.isolationLevelToString(isolation.intValue()));
 59        }
 60
 61        try {
 62            ds = (WSDataSource) NamingHelper.getInitialContext(props).lookup(
 63                    jndiName);
 64        } catch (Exception e) {
 65            log.fatal("Could not find datasource: " + jndiName, e);
 66            throw new HibernateException("Could not find datasource", e);
 67        }
 68        if (ds == null)
 69            throw new HibernateException("Could not find datasource: "
 70                    + jndiName);
 71        log.info("Using datasource: " + jndiName);
 72    }
 73
 74    public Connection getConnection() throws SQLException {
 75        if (log.isDebugEnabled()) {
 76            log.debug("getConnection()");
 77        }
 78        Connection connection = ds.getConnection(createJDBCConnectionSpec());
 79
 80        if (log.isDebugEnabled()) {
 81            if (connection != null) {
 82                log.debug("Returning connection, System.identityHashCode="
 83                        + System.identityHashCode(connection));
 84                log.debug("Connection=" + connection);
 85                log.debug("Shareable=" + WSCallHelper.isShareable(connection));
 86                log.debug("Class=" + connection.getClass());
 87                Connection nativeConnection = (Connection) WSJdbcUtil
 88                        .getNativeConnection((WSJdbcConnection) connection);
 89                log.debug("Native connection=" + nativeConnection);
 90                log.debug("Native connection, System.identityHashCode="
 91                        + System.identityHashCode(nativeConnection)
 92                        + ", class=" + nativeConnection.getClass());
 93            } else {
 94                log.debug("Returning null");
 95            }
 96        }
 97        return connection;
 98    }
 99
100    public void closeConnection(Connection conn) throws SQLException {
101        conn.close();
102    }
103
104    public void close() {
105    }
106
107    protected JDBCConnectionSpec createJDBCConnectionSpec() {
108        JDBCConnectionSpec connSpec = WSRRAFactory.createJDBCConnectionSpec();
109
110        // 设置事称隔离级别
111        if (isolation != null) {
112            connSpec.setTransactionIsolation(isolation);
113        }
114
115        if (user != null || pass != null) {
116            connSpec.setUserName(user);
117            connSpec.setPassword(pass);
118        }
119        return connSpec;
120    }
121}

以上代码中类 WSRRAFactory、JDBCConnectionSpec 用到了 $WAS_HOME/lib/rsaexternal.jar;类WSJdbcConnection、WSJdbcUtil 用到了 $WAS_HOME/lib/rsadapterspi.jar,所以需要在编译时 $WAS_HOME/lib 中两那两个包引用进来。

同时,在 hibernate.cfg.xml 中要有如下配置,记得在 WAS 还需配置相应的连接池 jdbc/unmiDs
1<property name="connection.datasource">jdbc/unmiDs</property>
2<property name="dialect">org.hibernate.dialect.OracleDialect</property><br/><br/>
3<property name="hibernate.connection.provider_class">
4    com.unmi.db.WSDataSourceConnectionProvider
5</property><br/><br/>
6<property name="connection.username">user</property>
7<property name="connection.password">password</property>

文中又有链接提到 Sharing connections in WebSphere Application Server V5

大致说到的是,在WAS环境中Hibernate应用程序运行时可能一个事物使用的并非是同一个链接,无形中搞出个分布式事物来吧,具体情况和解决办法还有待细究,希望这个就是我想要的结果。

OK,明天再来分析上面那两篇文章吧,期望找到一个治本的解决方案。

好像与现今使用的C3P0连接池也有关系,有建议说换成最近新版的

文中 c3p0 problem 也有提到把 maxStatements 和 maxStatementsPerConnection 设置为零
c3p0.maxStatements=0
c3p0.maxStatementsPerConnection=0 永久链接 https://yanbin.blog/hibernate-was-dead-lock/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。