Skip to main content

XADataSource, no JTA transaction started, but data gets committed - is this expected?

No replies
wujek.srujek
Offline
Joined: 2013-02-17
Points: 0

Hi. I configured a (standalone) Derby XADataSource for use in my application. The application consists of the following servlet code:

@WebServlet(name = "JdbcServlet", value = "/jdbc")
public class JdbcServlet extends HttpServlet {

@Resource(name = "jdbc/DerbyTestXAPool")
private DataSource dataSource;

@Resource
private UserTransaction utx;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
Writer out = response.getWriter();
Connection conn = null;
try {
// utx.begin();
out.write(utx.getStatus() + "\n");
conn = dataSource.getConnection();
out.write(conn.getAutoCommit() + "\n");
conn.setAutoCommit(false);
out.write(conn.getAutoCommit() + "\n");
ResultSet rs = conn.createStatement().executeQuery("select max(id) from person");
rs.next();
long nextid = rs.getLong(1) + 1;

out.write(conn.getAutoCommit() + "\n");
PreparedStatement pst = conn.prepareStatement("insert into person(id, first_name, last_name) values(?, ?, ?)");
out.write(conn.getAutoCommit() + "\n");
pst.setLong(1, nextid);
pst.setString(2, "fn" + nextid);
pst.setString(3, "ln" + nextid);
pst.execute();
out.write(conn.getAutoCommit() + "\n");

rs = conn.createStatement().executeQuery("select id, first_name, last_name from person");
while (rs.next()) {
long id = rs.getLong("id");
String firstName = rs.getString("first_name");
String lastName = rs.getString("last_name");
out.write(String.format("[%d] %s %s\n", id, firstName, lastName));
}
// utx.commit();
out.write(utx.getStatus() + "\n");
} catch (Exception e) {
// try {
// utx.rollback();
// } catch (SystemException e2) {
// throw new ServletException(e2);
// }
throw new ServletException(e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new ServletException(e);
}
}
}
}
}

This code is buggy. The UserTransaction is never actually used, so Derby uses a local transaction (as allowed by the JTA specification), and should fail during close() because there is an active transaction running - that's how it behaves when used standalone. Here, the close() method belongs to com.sun.gjc.spi.jdbc40.ConnectionWrapper40, and during releasing the pooled connection, deep in the code, it does this (in com.sun.gjc.spi.ManagedConnection.java):

private void resetAutoCommit() throws ResourceException {
if (defaultAutoCommitValue != getLastAutoCommitValue() && !(isTransactionInProgress())) {
try {
actualConnection.setAutoCommit(defaultAutoCommitValue);
} catch (SQLException sqle) {
String i18nMsg = localStrings.getString(
"jdbc.error_during_setAutoCommit");
throw new ResourceException(i18nMsg + sqle.getMessage(), sqle);
}
setLastAutoCommitValue(defaultAutoCommitValue);
}
}

For some reason, isTransactionInProgress() returns false (I guess this relates to the global transaction status) and the autoCommit status is set, which causes the transaction to commit implicitly, as per the jdbc specification.
I think this is weird and confusing - I spend quite some time debugging what is going wrong here, why are things committed when I expect things to fail. Is this the expected behavior?
I would expect an exception of some kind. Could anybody of the GlassFish team maybe shed some light? Maybe this is correct the way it is?