java - Spring transactions in unit tests and annotation based configuration -


i have trouble getting transactions working in unit test. transactiontest class contains necessary spring configuration. starts up, initializes database , executes 2 runnables in parallel (inserter , selector). logged output visible test executes, records inserted , selected database in correct order, there no transaction isolation.

what expect see in log like:

2016-01-16 00:29:32,447 [main] debug  transactiontest - starting test 2016-01-16 00:29:32,619 [pool-2-thread-2] debug  selector - select 1 returned: 0 2016-01-16 00:29:33,121 [pool-2-thread-1] debug  inserter - inserting record: 1 2016-01-16 00:29:33,621 [pool-2-thread-2] debug  selector - select 2 returned: 0 2016-01-16 00:29:34,151 [pool-2-thread-1] debug  inserter - inserting record: 2 2016-01-16 00:29:34,624 [pool-2-thread-2] debug  selector - select 3 returned: 2 2016-01-16 00:29:34,624 [main] debug  transactiontest - terminated 

however, see is:

2016-01-16 00:29:32,447 [main] debug  transactiontest - starting test 2016-01-16 00:29:32,619 [pool-2-thread-2] debug  selector - select 1 returned: 0 2016-01-16 00:29:33,121 [pool-2-thread-1] debug  inserter - inserting record: 1 2016-01-16 00:29:33,621 [pool-2-thread-2] debug  selector - select 2 returned: 1 2016-01-16 00:29:34,151 [pool-2-thread-1] debug  inserter - inserting record: 2 2016-01-16 00:29:34,624 [pool-2-thread-2] debug  selector - select 3 returned: 2 2016-01-16 00:29:34,624 [main] debug  transactiontest - terminated 

please consider test code below. in transactiontest.java there few annotations commented out ahead of class body itself. when include these annotations, can see log spring executes entire test in separate transaction. aim execute method inserter.insertseveralrecords() in separate transaction. there sadly no indications in log spring sees @transactional annotation there.

i have tried add @enabletransactionmanagement annotation transactiontest class itself, instead of configuration section, makes no difference.

transactiontest.java

package program.test.db.transaction;  import java.util.concurrent.executors; import java.util.concurrent.threadpoolexecutor; import java.util.concurrent.timeunit;  import org.apache.commons.dbcp2.basicdatasource; import org.apache.logging.log4j.logmanager; import org.apache.logging.log4j.logger; import org.flywaydb.core.flyway; import org.flywaydb.test.annotation.flywaytest; import org.flywaydb.test.junit.flywaytestexecutionlistener; import org.jooq.dslcontext; import org.jooq.sqldialect; import org.jooq.impl.datasourceconnectionprovider; import org.jooq.impl.defaultconfiguration; import org.jooq.impl.defaultdslcontext; import org.jooq.impl.defaultexecutelistenerprovider; import org.junit.test; import org.junit.runner.runwith; import org.springframework.beans.factory.annotation.autowired; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.context.annotation.propertysource; import org.springframework.core.env.environment; import org.springframework.jdbc.datasource.datasourcetransactionmanager; import org.springframework.jdbc.datasource.transactionawaredatasourceproxy; import org.springframework.test.context.contextconfiguration; import org.springframework.test.context.testexecutionlisteners; import org.springframework.test.context.junit4.springjunit4classrunner; import org.springframework.test.context.support.annotationconfigcontextloader; import org.springframework.test.context.support.dependencyinjectiontestexecutionlistener; import org.springframework.test.context.transaction.transactionconfiguration; import org.springframework.test.context.transaction.transactionaltestexecutionlistener; import org.springframework.transaction.annotation.enabletransactionmanagement; import org.springframework.transaction.annotation.transactional;  import program.db.jooqexceptiontranslator; import static org.junit.assert.asserttrue; import static program.db.tables.system_log;  @runwith(springjunit4classrunner.class) @contextconfiguration(loader=annotationconfigcontextloader.class) @testexecutionlisteners({dependencyinjectiontestexecutionlistener.class, flywaytestexecutionlistener.class})//, transactionaltestexecutionlistener.class}) //@transactional //@transactionconfiguration(transactionmanager="transactionmanager", defaultrollback=false) public class transactiontest {      private static logger log = logmanager.getlogger(transactiontest.class);      @configuration     @propertysource("classpath:program.properties")     @enabletransactionmanagement     static class contextconfiguration {         @autowired         private environment env;         @bean         public flyway flyway(){             flyway flyway = new flyway();             flyway.setdatasource(datasource());             flyway.setschemas("program_x");             flyway.setlocations("db/migration");             return flyway;         }         @bean         public basicdatasource datasource() {             basicdatasource result = new basicdatasource();             result.setdriverclassname(env.getrequiredproperty("program.database.driver"));             result.seturl(env.getrequiredproperty("program.database.url"));             result.setusername(env.getrequiredproperty("program.database.username"));             result.setpassword(env.getrequiredproperty("program.database.password"));             return result;         }         @bean         public datasourcetransactionmanager transactionmanager() {             return new datasourcetransactionmanager(datasource());         }         @bean         public transactionawaredatasourceproxy transactionawaredatasource(){             return new transactionawaredatasourceproxy(datasource());         }         @bean         public datasourceconnectionprovider connectionprovider(){             return new datasourceconnectionprovider(transactionawaredatasource());         }         @bean         public jooqexceptiontranslator jooqexceptiontranslator(){             return new jooqexceptiontranslator();         }         @bean         public defaultconfiguration config(){             defaultconfiguration result = new defaultconfiguration();             result.set(connectionprovider());             result.set(new defaultexecutelistenerprovider(jooqexceptiontranslator()));             result.set(sqldialect.postgres);             return result;         }         @bean         public defaultdslcontext db(){             return new defaultdslcontext(config());         }         @bean         public inserter inserter(){             return new inserter();         }         @bean         public selector selector(){             return new selector();         }     }      @autowired     private dslcontext db;     @autowired     private selector selector;     @autowired     private inserter inserter;      private final threadpoolexecutor thread_pool = (threadpoolexecutor) executors.newcachedthreadpool();      @test     @flywaytest     public void runtest() throws interruptedexception {         log.debug("starting test");         int count0 = db.selectcount().from(system_log).fetchone(0, int.class);         asserttrue(count0 == 0);          thread_pool.execute(inserter);         thread_pool.execute(selector);          thread_pool.shutdown();         thread_pool.awaittermination(5, timeunit.seconds);         log.debug("terminated");     }  } 

selector.java

package program.test.db.transaction;  import org.apache.logging.log4j.logmanager; import org.apache.logging.log4j.logger; import org.jooq.dslcontext; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.component;  import static program.db.tables.system_log;  @component public class selector implements runnable {      private static logger log = logmanager.getlogger(selector.class);      @autowired     private dslcontext db;      @override     public void run() {         try {             int count1 = db.selectcount().from(system_log).fetchone(0, int.class);             log.debug("select 1 returned: " + count1);             thread.sleep(1000);             int count2 = db.selectcount().from(system_log).fetchone(0, int.class);             log.debug("select 2 returned: " + count2);             thread.sleep(1000);             int count3 = db.selectcount().from(system_log).fetchone(0, int.class);             log.debug("select 3 returned: " + count3);         } catch (interruptedexception e) {             log.error("selects interrupted", e);         }     }  } 

inserter.java

package program.test.db.transaction;  import org.apache.logging.log4j.logmanager; import org.apache.logging.log4j.logger; import org.joda.time.datetime; import org.jooq.dslcontext; import org.jooq.insertquery; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.component; import org.springframework.transaction.annotation.transactional;  import program.db.tables.records.systemlogrecord; import static org.junit.assert.asserttrue; import static program.db.tables.system_log;  @component public class inserter implements runnable {      private static logger log = logmanager.getlogger(inserter.class);      @autowired     private dslcontext db;      @override     public void run() {         insertseveralrecords();     }      @transactional     private void insertseveralrecords(){         try {             thread.sleep(500);             insertrecord(1);             thread.sleep(1000);             insertrecord(2);         } catch (interruptedexception e) {             log.error("inserts interrupted", e);         }     }      private void insertrecord(int i){         log.debug("inserting record: " + i);         insertquery<systemlogrecord> insertquery = db.insertquery(system_log);         insertquery.addvalue(system_log.service, "service " + i);         insertquery.addvalue(system_log.message, "message " + i);         insertquery.addvalue(system_log.sys_insert_time, datetime.now());         int result = insertquery.execute();         asserttrue(result == 1);     } } 

i missing rather basic here - doing wrong?

the problem in question caused by:

  1. the method inserter.insertseveralrecords() annotated @transactional private method.

    • only public methods should annotated @transactional
  2. making method inserter.insertseveralrecords() public still did not start transaction. because method called internally method inserter.run() (as opposed externally other class).

    • when adding support @transactional, spring uses proxies add code before , after invocation of annotated method. in case of classes implement interface, these dynamic proxies. means external method calls come in through proxy intercepted
    • the class inserter implements runnable interface - @transactional picked if annotated method called directly outside
  3. moving @transactional annotation method inserter.run() fixes class still not enough run test. when starting up, throw error:

    "could not autowire field: transactiontest.inserter; nosuchbeandefinitionexception: no qualifying bean of type [program.test.db.transaction.inserter] found dependency"

    this caused fact transactiontest.inserter field of type inserter , not runnable, whereas @transactional annotation added method of runnable interface. not find reference why works this, changing @autowired field type inserter runnable allows spring start correctly , use transactions when executer calls inserter.run(). (probably because dynamic proxy created on interface also?)


here relevant code sections 3 changes described above:

inserter.java

@override @transactional public void run() {     insertseveralrecords(); }  private void insertseveralrecords(){     try {         thread.sleep(500);         insertrecord(1);         thread.sleep(1000);         insertrecord(2);     } catch (interruptedexception e) {         log.error("inserts interrupted", e);     } } 

transactiontest.java

@autowired private dslcontext db; @autowired private runnable selector; @autowired private runnable inserter;  private final threadpoolexecutor thread_pool = (threadpoolexecutor) executors.newcachedthreadpool();  @test @flywaytest public void runtest() throws interruptedexception {     log.debug("starting test");     int count0 = db.selectcount().from(system_log).fetchone(0, int.class);     asserttrue(count0 == 0);      thread_pool.execute(inserter);     thread_pool.execute(selector);      thread_pool.shutdown();     thread_pool.awaittermination(5, timeunit.seconds);     log.debug("terminated"); } 

the test executes correctly transactional isolation, producing log output desired in original question.

used resources:

  1. http://www.baeldung.com/2011/12/26/transaction-configuration-with-jpa-and-spring-3-1/
  2. http://www.nurkiewicz.com/2011/10/spring-pitfalls-proxying.html
  3. http://www.ibm.com/developerworks/java/library/j-jtp08305/index.html

Comments

Popular posts from this blog

get url and add instance to a model with prefilled foreign key :django admin -

css - Make div keyboard-scrollable in jQuery Mobile? -

ruby on rails - Seeing duplicate requests handled with Unicorn -