users@glassfish.java.net

XADataSource (standalone derby), no JTA transaction started, data still commited

From: Wujek Srujek <wujek.srujek_at_gmail.com>
Date: Wed, 20 Feb 2013 09:56:57 +0100

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;

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

            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 in a plain Java application. 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?

wujek