最近,我在Web应用程序中编写了一个类,用于解析巨大的XML文件并将其内容提供给db表。我的应用程序在Wildfly9上运行,并与Hibernate提供程序一起使用JPA来处理MySQL数据库。
AS配置非常标准,我刚刚添加了数据源conf:
<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&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查询将我的数据插入db中。这是代码:
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()方法。有用!
痛苦从这里开始。自解析结束以来,我的应用程序开始随机地在其他方面给出错误。 stacktrace看起来像这样:
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()
中将其关闭,而无需最终使用。如果引发异常,则不会关闭连接。使用try-with-resources还是更好。
另一个潜在的问题是该类不是线程安全的。例如,如果使用一个没有同步的实例,如果您在XMLFeedParser.initParser()
之前两次调用finalizeParser()
,则可能会丢失对connection
的引用,而该引用却永远不会关闭(注入XMLFeedParser
的EJB外观如何)喜欢?)
编辑:使用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
和其他变量不是该类的成员时,您不必担心其他线程会访问它们。