【问题标题】:Managing JDBC transaction inside EJB container transaction在 EJB 容器事务中管理 JDBC 事务
【发布时间】:2012-08-02 14:28:31
【问题描述】:

我有一个 EJB,它有 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)。在 EJB 中,我有一个记录器,它已被配置为使用 JNDI appender 和 JDBC 登录数据库:

public class JNDIAppender extends AppenderSkeleton {

    private Connection connection;
    private Statement statement;
    private String sql;
    private String dataSourceLookupAddress;

    /**
     * Constructor.
     */
    public JNDIAppender() {
    }

    /**
     * @return the sql
     */
    public final String getSql() {
        return sql;
    }

    /**
     * @param sql the sql to set
     */
    public final void setSql(final String sql) {
        this.sql = sql;
    }

    /**
     * @return the dataSourceLookupAddress
     */
    public final String getDataSourceLookupAddress() {
        return dataSourceLookupAddress;
    }

    /**
     * @param dataSourceLookupAddress the dataSourceLookupAddress to set
     */
    public final void setDataSourceLookupAddress(final String dataSourceLookupAddress) {
        this.dataSourceLookupAddress = dataSourceLookupAddress;
    }

    private synchronized Connection getConnection() {
        if (connection == null) {
            try {
                final Context ctx = new InitialContext();
                final DataSource ds = (DataSource) ctx.lookup(getDataSourceLookupAddress());
                connection = ds.getConnection();
                connection.setAutoCommit(false);
            } catch (final NamingException e) {
                errorHandler.error("Datasource JNDI lookup failed: " + dataSourceLookupAddress + "!");
                errorHandler.error(e.toString());
            } catch (final SQLException e) {
                errorHandler.error("Sql connection failed to " + dataSourceLookupAddress + "!");
                errorHandler.error(e.toString());
            }
        }
        return connection;
    }

    private synchronized Statement getStatement() {
        if (statement == null) {
            try {
                statement = getConnection().createStatement();
            } catch (final SQLException e) {
                errorHandler.error(e.toString());
            }
        }
        return statement;
    }

    @Override
    public void activateOptions() {
        if (getSql() == null) {
            errorHandler.error("param 'sql' is null!");
        }
        if (getDataSourceLookupAddress() == null) {
            errorHandler.error("param 'DataSourceLookupAddress' is null!");
        }
    }

    /*
     * (non-Javadoc)
     * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent)
     */
    @Override
    protected synchronized void append(final LoggingEvent event) {
        try {
            ((PatternLayout) getLayout()).setConversionPattern(getSql());
            final String sqlClause = getLayout().format(event);
            getStatement().executeUpdate(sqlClause);
            getConnection().commit();
        } catch (final SQLException e) {
            errorHandler.error(e.toString());
        } finally {
            close();
        }
    }

    /*
     * (non-Javadoc)
     * @see org.apache.log4j.AppenderSkeleton#close()
     */
    public void close() {
        try {
            if (statement != null) {
                statement.close();
                statement = null;
            }
        } catch (final SQLException e) {
            errorHandler.error(e.toString());
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                    connection = null;
                } catch (final SQLException e) {
                    errorHandler.error(e.toString());
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see org.apache.log4j.AppenderSkeleton#requiresLayout()
     */
    public boolean requiresLayout() {
        return true;
    }

    /*
     * (non-Javadoc)
     * @see org.apache.log4j.AppenderSkeleton#finalize()
     */
    @Override
    public void finalize() {
        close();
        super.finalize();
    }

} 

现在,当 EJB 方法调用期间发生异常时,不会将任何内容记录到数据库中,因为事务已回滚(但是,我已将 autoCommit 设置为 false 并在 JNDIAppender 中手动提交事务)。

我的问题是:有没有办法在单独的事务中登录数据库? (我尝试将 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 添加到 JNDIAppender,但这没有帮助)。或者是否有任何其他解决方案能够登录数据库,即使已引发异常?我可能会使用单独的数据源来登录数据库,但这似乎有点过头了。

UPD: 好吧,实际上 JNDIAppener 确实提交了事务(并因此登录到 DB),我只是错过了一些行,当我测试它时:) 但问题是它也提交了一切,这是在异常之前在 EJB 中完成的(实际上不能提交)。

我必须说,我们的持久层也是 JDBC,所以基本上 EJB 与使用 JDBC 的 DB 一起工作。因此,就我在创建连接时在 JNDIAppender 中看到的而言,它仍然使用与 EJB 中相同的事务。在已经打开的事务存在的情况下,我可以使用 JDBC 创建一个单独的事务并对其进行管理吗?

【问题讨论】:

  • 您是否在您的 bean 类或其超类上放置了 @TransactionAttribute 注释?过去我在意外事务行为方面遇到了非常相似的问题,问题在于注释被放置在超类上而不是不同的类上。
  • 是的,它在我的 bean 上。但是,我认为这里的 EJB 有点误导,它与 JDBC 更相关,因为我们使用 JDBC 作为持久层(请参阅我的更新)。

标签: java logging jdbc transactions ejb


【解决方案1】:

该问题的解决方案是使用单独的线程(启动新事务)进行日志记录。与此类似,但不是这样(我手头没有确切的代码):

Thread t = new Thread() {
            @Override
            public void run() {
                try {
                    ((PatternLayout) getLayout()).setConversionPattern(getSql());
                    final String sqlClause = getLayout().format(event);
                    getStatement().executeUpdate(sqlClause);
                    getConnection().commit();
                } catch (final SQLException e) {
                    errorHandler.error(e.toString());
                } finally {
                    close();
                }
            }
        };
        t.run();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-25
    • 2015-11-26
    • 2017-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多