2

I'm trying to use SpringJunit4ClassRunner to test my DAO classes without leaving data behind when I've finished, through the use of the @Transactional annotation. My DAO class contains (stripped down):

@Repository
public class IdsFunctionJpaController {

  @PersistenceContext
  EntityManager em;

  public void save(IdsFunction function) {
    if (function.getId() == 0) {
      create(function);
    } else {
      update(function);
    }
  }

  @Transactional
  private void create(IdsFunction idsFunction) {
    try {
      em.persist(idsFunction);
    }
    catch (Exception e) {
      System.out.println(e);
    } finally {
      em.close();
    }
  }

  @Transactional
  private void update(IdsFunction function) {
    try {
      em.merge(function);
    } finally {
      em.close();
    }
  } 
}

and my starting JUnit test case is

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/applicationContext.xml"} )
public class IdsFunctionJpaControllerTest {

  @Autowired
  IdsFunctionJpaController dao;

  @Test
  @Transactional
  public void addFunction() {
    IdsFunction function = new IdsFunction();
    function.setDescription("Test Function Description");
    dao.save(function);
    assertTrue(function.getId() != 0);
  }
}

What I'm trying to do here is simply test that the entity has been created, but this test fails. If I remove the @Transactional annotation, then the test passes, but the test entity remains in the database. What am I doing wrong?

Regards

user497087
  • 1,561
  • 3
  • 24
  • 41

2 Answers2

6

Proxying mechanisms

You are banging your head against JDK proxies.

Your dao.save() method is non-transactional, and it tries to call the transactional methods create() and update(). But the transactional stuff is happening in a JDK proxy outside the class, while the save method is already in the class.

See this previous answer of mine for reference.

Solutions:

  • make your save() method transactional
  • (much better) don't make your DAOs transactional at all. Transactions belong in the service layer, not the DAO layer.

Reference:


Update: I was misguided by the presence of the confusing @Transactional annotations on the Dao methods. You should delete them, they do nothing and confuse people.

As you can read in the links I posted above, @Transactional annotations only have effect when they are present on public Methods that will be called from outside the Spring bean (so you can't have one method in a class that delegates to one or more proxied methods of the same class).


Transactional Tests

Spring provides special support classes for transactional testing, as outlined in 9.3.5.4 Transaction management . If you let your test class extend from AbstractTransactionalJUnit4SpringContextTests you get an automatic transaction rollback after every test. In most cases that is exactly what you need.

Community
  • 1
  • 1
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • -1 as it has nothing to do with proxies and transactions do not propagate like that. +1 for pointing out that the dao layer should not be transactional. – OrangeDog Feb 07 '11 at 10:02
  • @OrangeDog I have now seen that there is also a `@Transactional` attribute on the test method. I had only seen the `@Transactional` annotations on the DAO methods. These annotations are nonsense, and I have pointed out why. – Sean Patrick Floyd Feb 07 '11 at 10:08
5

You need to flush the session. Ordinarily this happens at the end of a transaction, which is why you usually only have to worry about it in tests.

Inject the EntityManager into your test class and call em.flush() after the save.

Also, your DAO layer shouldn't be transactional. Transactions typically only make sense in the service layer.

Edit:

In fact, your DAO is also completely wrong, which this test won't be able to show. Those transactional annotations will have no effect as they are internal method calls. You should also never close the EntityManager yourself - the container (Spring) will do this for you. Also, don't catch generic Exceptions and when you do don't just log and ignore them. Exceptions should be propagating to the service layer where they should be handled properly. Also, don't print to stdout, use a proper logging framework.

OrangeDog
  • 36,653
  • 12
  • 122
  • 207