@Resource 数据源断开数据库连接

@Resource datasource breaking DB connection

我最近在我的 Web 应用程序中编写了一个 class,用于解析一个巨大的 XML 文件并将其内容提供给数据库 table。我的应用程序在 Wildfly9 上 运行,并使用 JPA 和 Hibernate 提供程序来处理 MySQL 数据库。

AS配置很标准,我刚刚添加了我的数据源配置:

<datasource jta="false" jndi-name="java:jboss/datasources/spazio_visione" pool-name="spazio_visione" enabled="true" use-ccm="false">
                    <connection-url>jdbc:mysql://127.0.0.1:3306/spazio_visione?zeroDateTimeBehavior=convertToNull&amp;rewriteBatchedStatements=true</connection-url>
                    <driver-class>com.mysql.jdbc.Driver</driver-class>
                    <driver>mysql</driver>
                    <security>
                        <user-name>myuser</user-name>
                        <password>mypasswd</password>
                    </security>
                    <validation>
                        <validate-on-match>false</validate-on-match>
                        <background-validation>false</background-validation>
                    </validation>
                    <statement>
                        <share-prepared-statements>false</share-prepared-statements>
                    </statement>
                </datasource>

这是我的 persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

    <persistence-unit name="backoffice" transaction-type="JTA">
        <jta-data-source>java:jboss/datasources/spazio_visione</jta-data-source>        
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
        <properties>            
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
            <property name="hibernate.cache.use_query_cache" value="true" />
            <property name="hibernate.cache.use_second_level_cache" value="true" />
            <property name="hibernate.jdbc.batch_size" value="100" />
            <property name="hibernate.order_inserts" value="true" />
            <property name="hibernate.order_updates" value="true" />            
            <!-- <property name="hibernate.show_sql" value="true"/> -->       
            <!-- <property name="hibernate.hbm2ddl.auto" value="validate"/> -->
        </properties>
    </persistence-unit>

</persistence>

一切正常,使用 JPA 实体管理我的领域模型。

回到我的解析器...实际上,出于多种原因,它需要使用本机 JDBC 查询将我的数据插入数据库。这是代码:

public class XMLFeedParser extends DefaultHandler {

    @Inject Logger logger;
    @Resource(lookup="java:jboss/datasources/spazio_visione") DataSource datasource;

    private static final int STATEMENT_BATCH_THRESHOLD = 1000;      
    private MyXMLFeedItem item; 

    private Connection connection;
    private PreparedStatement ps;

    public XMLFeedParser() {

    }

    protected void initParser() throws SQLException {

        connection = datasource.getConnection();    

        Statement deleteStatement = connection.createStatement();
        deleteStatement.executeUpdate("DELETE FROM mytable WHERE id_feed = "+feed.getId());
        deleteStatement.close();

        ps = connection.prepareStatement(
                "INSERT INTO mytable "
                + "( first, second, ...) "
                + "values ( ?, ?, ... )"
                );
    }

    protected void finalizeParser() throws SQLException {
        if (ps!=null) {
            ps.executeBatch();
            ps.close();
        }
        if (connection!=null) {
            connection.close();
        }       
    }

    public void parseAndWriteToDatabase(String filePath) throws ParserConfigurationException, SAXException, IOException, SQLException {

        File file = Paths.get(filePath).toFile();       

        SAXParserFactory factory = SAXParserFactory.newInstance();          
        SAXParser saxParser = factory.newSAXParser();

        initParser();
        saxParser.parse(file, this);
        finalizeParser();               
    }

    private void writeToDb(MyXMLFeedItem item) {

        try {

            ps.setString(1, "first");
            ps.setString(2, "second");
            // ...
            ps.addBatch();

            if ( counter % STATEMENT_BATCH_THRESHOLD == 0 ) {
                ps.executeBatch();
            }

        } catch (SQLException e) {              
            logger.severe(e.getMessage());
        }

    }

    @Override
    public void startElement(String namespaceURI, String localName, String qualifiedName, Attributes attrs) throws SAXException {       
        // ...parsing logic
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        // ...parsing logic
    }

    @Override
    public void endElement(String namespaceURI, String localName, String qualifiedName) throws SAXException {
        // calls writeToDb() for each record found
    }

}

我的 XMLFeedParser 被注入(使用@Inject)到我的一个 EJB 中,它将调用 parseAndWriteToDatabase() 方法。有用!

痛苦从这里开始。自解析结束以来,我的应用程序开始在其他地方随机出现错误。堆栈跟踪看起来像这样:

Caused by: javax.resource.ResourceException: IJ000453: Unable to get managed connection for java:jboss/datasources/spazio_visione
    at org.jboss.jca.core.connectionmanager.AbstractConnectionManager.getManagedConnection(AbstractConnectionManager.java:646)
    at org.jboss.jca.core.connectionmanager.AbstractConnectionManager.getManagedConnection(AbstractConnectionManager.java:552)
    at org.jboss.jca.core.connectionmanager.AbstractConnectionManager.allocateConnection(AbstractConnectionManager.java:737)
    at org.jboss.jca.adapters.jdbc.WrapperDataSource.getConnection(WrapperDataSource.java:138)
    ... 165 more
Caused by: javax.resource.ResourceException: IJ000655: No managed connections available within configured blocking timeout (30000 [ms])
    at org.jboss.jca.core.connectionmanager.pool.mcp.SemaphoreArrayListManagedConnectionPool.getConnection(SemaphoreArrayListManagedConnectionPool.java:553)
    at org.jboss.jca.core.connectionmanager.pool.AbstractPool.getSimpleConnection(AbstractPool.java:622)
    at org.jboss.jca.core.connectionmanager.pool.AbstractPool.getConnection(AbstractPool.java:594)
    at org.jboss.jca.core.connectionmanager.AbstractConnectionManager.getManagedConnection(AbstractConnectionManager.java:579)
    ... 168 more

看来我没有关闭连接,但事实并非如此! 有什么建议吗?

有几种可能出错的可能性。首先,您在 initParser() 中打开连接,但在 finalizeParser() 中关闭它,而不使用 finally。如果抛出异常,连接不会关闭。还是用try-with-resources比较好

另一个潜在的问题是 class 不是线程安全的。例如,如果在没有同步的情况下使用实例,如果您在 finalizeParser() 之前调用 XMLFeedParser.initParser() 两次,您可能会丢失对 connection 的引用,该引用将永远不会关闭(您的 EJB 如何注入 XMLFeedParser 的样子?)

编辑:使用 try-with-resources: 这取决于您在哪里需要 Connection。您可以在 parseAndWriteToDatabase() 中打开连接并将其传递给您需要它的方法。因此您不必显式调用 close()。您的 PreparedStatements 和 ResultSets 也可以包装在 try-with-resources 中。

例如:

 public void parseAndWriteToDatabase(String filePath) throws ParserConfigurationException, SAXException, IOException, SQLException {

    // ...
    try (Connection connection = getDataSource().getConnection();)
    {
    initParser(connection);
    saxParser.parse(file, this);
    finalizeParser(connection);               
    }
}

因此当您的 Connection 和其他变量不是 class 的成员时,您不必担心其他线程访问它们。