我最近在我的Web应用程序中编写了一个类,用于解析一个巨大的XML文件并为其提供内容。我的应用程序在Wildfly9上运行,并使用JPA和Hibernate提供程序来处理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
看起来我没有关闭连接,但事实并非如此! 有什么建议吗?
答案 0 :(得分:1)
有一些可能出错的可能性。首先,您在initParser()
中打开连接,但在finalizeParser()
中关闭它,而不使用finally。如果抛出异常,则不会关闭连接。使用try-with-resources仍然会更好。
另一个潜在的问题是该类不是线程安全的。例如,如果在没有同步的情况下使用实例,如果在XMLFeedParser.initParser()
之前调用finalizeParser()
两次,则可能会丢失对connection
的引用,然后该引用永远不会关闭(您的EJB如何注入XMLFeedParser
看起来像?)
编辑:使用try-with-resources:
这取决于您需要Connection
的位置。您可以在parseAndWriteToDatabase()
中打开连接并将其传递给您需要的方法。因此,您无需明确调用close()
。您的PreparedStatements和ResultSet也可以包含在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
和其他变量不是该类的成员时,您不必担心其他线程访问它们。