Quantcast
Channel: Java Creed » Spring
Viewing all articles
Browse latest Browse all 8

Handle InterruptedExceptions with JdbcTemplate

$
0
0

Spring JdbcTemplate (Java Doc) provides a centralised way to handle errors while accessing the underlying database. Say that a duplicate key error occurs while adding a new record into the database, JdbcTemplate provides a simple means to create custom exceptions that work well with your architecture.

The default configuration works well for most cases and the developer need to do nothing. Any exceptions thrown during the process are analysed and translated to a more (Spring) specific exception. Any unrecognised exceptions are simply wrapped into an instance of UncategorizedSQLException (Java Doc). This works well for most of the cases but fails miserably when it comes to InterruptedException (Java Doc).

All code listed below is available at: https://java-creed-examples.googlecode.com/svn/spring/InterruptedExceptions JdbcTemplate. Most of the examples will not contain the whole code and may omit fragments which are not relevant to the example being discussed. The readers can download or view all code from the above link.

Handling Interruptions

If the current thread is interrupted while the JdbcTemplate is executing the query, an UncategorizedSQLException can be thrown. The cause of this exception will be: InterruptedException. Unfortunately the thread interrupted status is swallowed by the UncategorizedSQLException and the caller has little means to known whether the current thread was interrupted or not.

Before we discuss a possible solution, we will first replicate the problem. Consider the following test class (SVN).

package com.javacreed.examples.spring.hiwj;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/spring/test-context.xml" })
public class ThreadInterruptedTest {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  @Before
  public void dropAndCreateTable() {
    dropTable();
    jdbcTemplate.execute("CREATE TABLE `test_table` (`name` VARCHAR(128) NOT NULL)");
  }

  @After
  public void dropTable() {
    jdbcTemplate.execute("DROP TABLE IF EXISTS `test_table`");
  }

  @Test
  public void test() {
    Thread.currentThread().interrupt();
    try {
      jdbcTemplate.update("INSERT INTO `test_table` (`name`) VALUES (?)", "Hello World");
      Assert.fail("The operation should have failed as the current thread was interrupted");
    } catch (final Exception e) {
      Assert.assertTrue(Thread.currentThread().isInterrupted());
    }
  }
}

Observation

It is imperative to first replicate the problem and then try to come up with a solution. This is also known as test driven development (Book), where the tests are created before the code is fixed. This ensures that such problem is fixed and that it remains fixed. Before the code is release all test are executed and should any change breaks this fix will be caught during the test.

The test class shown above is very straight forward. We setup the database and then interrupt the thread before executing our query. Furthermore, here we are using an H2 in memory database (Homepage) which requires neither installation nor configuration as Maven (Homepage) is handling everything in the background. The test is verifying that the trader interrupted status remains unchanged after the query is executed and an exception is thrown.

As expected this test fails as an UncategorizedSQLException is thrown during the process and the thread interrupted status is cleared as shown in the following image.

Interrupted Operation Test Failed

Interrupted Operation Test Failed

Following is the full stacktrace.

java.lang.AssertionError
	at org.junit.Assert.fail(Assert.java:86)
	at org.junit.Assert.assertTrue(Assert.java:41)
	at org.junit.Assert.assertTrue(Assert.java:52)
	at com.javacreed.examples.spring.hiwj.ThreadInterruptedTest.test(ThreadInterruptedTest.java:38)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:601)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Before we start pointing fingers and blaming Spring for all this, one needs to understand that here we are dealing with several layers of code written by different entities as shown below.

API Layers Involded

API Layers Involded

The InterruptedException is a checked exception (Tutorial) and the Java JDBC API (Tutorial) only deals with SQLException (Java Doc). Therefore, if a thread is interrupted during the process, there is little way to communicate this to the caller via exceptions. With that said, the thread interruption status should have never been cleared, by the underlying code/API.

Observation

If the JDBC API only deals with SQLException, how come we have an InterruptedException?
The issue comes with statement caching. The data source is configured to cache the SQL statements as shown in the following Spring XML configuration fragment.
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close" scope="singleton" primary="true">
  <property name="driverClass" value="org.h2.Driver" />
  <property name="jdbcUrl" value="jdbc:h2:mem:db" />
  <property name="user" value="sa" />
  <property name="password" value="" />
  <property name="maxStatements" value="1" />
</bean>

The thread interruptions will case the statement caching to throw an InterruptedException which is caught and rethrown as UncategorizedSQLException. If, the statement caching is disabled, then this problem will not occur and the statement is executed with no problems. The thread interruption state only affects the statement caching provided by the ComboPooledDataSource (Java Doc).

Here one needs to appreciate that all parts may work well individually, but when used together in a specific configuration, some errors may surface as we saw here. This subtle error may cause a chain of other problems as the calling code may not realise that the current thread was interrupted.

In the following section we will see how we can use Spring SQLErrorCodeSQLExceptionTranslator (Java Doc) to address this problem.

Using the SQLErrorCodeSQLExceptionTranslator to Handle Interruptions

Spring JdbcTemplate provides a centralised way to handle errors while accessing the underlying database through SQLErrorCodeSQLExceptionTranslator. All we need to do is create a custom instance of SQLErrorCodeSQLExceptionTranslator and set this as the JdbcTemplate‘s exceptionTranslator as we will see next (SVN).

package com.javacreed.examples.spring.hiwj;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.jdbc.support.SQLErrorCodes;

public class CustomSqlExceptionTranslator extends SQLErrorCodeSQLExceptionTranslator {

  public CustomSqlExceptionTranslator() {
    super();
  }

  public CustomSqlExceptionTranslator(final DataSource dataSource) {
    super(dataSource);
  }

  public CustomSqlExceptionTranslator(final SQLErrorCodes errorCodes) {
    super(errorCodes);
  }

  public CustomSqlExceptionTranslator(final String dbName) {
    super(dbName);
  }

  @Override
  protected DataAccessException doTranslate(final String task, final String sql, final SQLException e) {
    for (Throwable cause = e.getCause(); cause != null; cause = cause.getCause()) {
      if (cause instanceof InterruptedException) {
        Thread.currentThread().interrupt();
        return new InterruptedOperationException((InterruptedException) cause);
      }
    }

    return super.doTranslate(task, sql, e);
  }
}

The above class is quite straight forward. It extends SQLErrorCodeSQLExceptionTranslator, and verifies that the exception’s cause before delegating the call to its superclass. In the event of an InterruptedException, a custom exception InterruptedOperationException is thrown.

The JdbcTemplate needs to be configured to use this class. This can be done through the XML configuration as shown next or programmatically, through the use of the setter method JdbcTemplate.setExceptionTranslator(SQLExceptionTranslator) (Java Doc).

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" scope="singleton">
  <constructor-arg ref="dataSource" />
  <property name="exceptionTranslator">
    <bean class="com.javacreed.examples.spring.hiwj.CustomSqlExceptionTranslator" />
  </property>
</bean>

Once the fix is ready, all we need is to run the test again to make sure that the problem is truly solved. If we run the test we will see that this time, using the new custom exception translated, the test passes and the thread interruption status is unchanged.

Note that we have two configuration files in the test resources. One makes use of the custom error handling and is called: test-ceh-context.xml, and the other which does not make use of the custom error handling and is called: test-context.xml. The test will fail when executed with the latter configuration, but succeeds when executed with the first configuration. The configuration can be modified by changing the location attribute of the annotation @ContextConfiguration (Java Doc).

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/spring/test-ceh-context.xml" })
public class ThreadInterruptedTest {

Conclusion

JDBC technology is one of the most popular technologies in most programming languages as most of the programs do interact with a database. One can easily say that the existing API is faulty and does not address problems like the one described here. Unfortunately whoever thinks this is definitely mistaking. The JDBC APIs (including all third parties such as the connection pool) are used and tried by many and it does not mean that if there is a small problem somewhere this API is broken. In fact in this article we saw how easy is to configure Spring to overcome such problem.

A small note to the developers: Using the “try/catch all” approach is never a good solution especially when the cause can be an interruption. The thread interruption state has a large scope and can be used by many others in the same call stack (and in other threads’ call stack) and suppressing it (by catching the InterruptedException) is never a good idea. As we saw in the InterruptedOperationException, in the event the InterruptedException needs to be caught and rethrown as another exception, then make sure you interrupt the current thread again as shown below.

Thread.currentThread().interrupt();
return new InterruptedOperationException((InterruptedException) cause);

Viewing all articles
Browse latest Browse all 8

Latest Images

Trending Articles





Latest Images