Strange spring transaction support alert for JPA + Hibernate + @ Transactional annotation

I found really strange behavior in a relatively simple use case, maybe I can’t figure it out due to not deep knowledge of spring @Transactional nature, but it’s quite interesting.

I have a simple custom dao that extends the spring class of JpaDaoSupport and contains a standard save method:

@Transactional public User save(User user) { getJpaTemplate().persist(user); return user; } 

If it worked fine until I add a new method to the same class: User getSuperUser (), this method should return the user with isAdmin == true, and if there is no superuser in db, the method should create it. Here's how it looked:

  public User createSuperUser() { User admin = null; try { admin = (User) getJpaTemplate().execute(new JpaCallback() { public Object doInJpa(EntityManager em) throws PersistenceException { return em.createQuery("select u from UserImpl u where u.admin = true").getSingleResult(); } }); } catch (EmptyResultDataAccessException ex) { User admin = new User('login', 'password'); admin.setAdmin(true); save(admin); // THIS IS THE POINT WHERE STRANGE THING COMING OUT } return admin; } 

As you can see, the code is strange, and I was very confused when I found out that no transaction was created and committed when the save (admin) method was called, and no new user was created, despite the @Transactional annotation.

As a result, a situation arises: when the save () method is called from outside the UserDAO class - the @Transactional annotation counts and the user is successfully created, but if save () calls from the dao class from another method - the @Transactional annotation is ignored.

This is how I modified the save () method to make it always create a transaction.

 public User save(User user) { getJpaTemplate().execute(new JpaCallback() { public Object doInJpa(EntityManager em) throws PersistenceException { em.getTransaction().begin(); em.persist(user); em.getTransaction().commit(); return null; } }); return user; } 

As you can see, I manually call begin and commit. Any ideas?

+7
java spring aop hibernate jpa
source share
3 answers

I think that declarative transactions with annotation are implemented based on the proxy.

If you access the DAO through a dynamic proxy, it checks to see if there is an annotation and wraps it with a transaction.

If you call your class from a class, there is no way to intercept this call.

To avoid this problem, you can also annotate the createSuperuser method.

+5
source share
  • @Transactional is only considered for calls from outside the object. For internal calls this is not the case. To do this, simply add @Transactional to your entry point.
  • Do not use @Transactional for your DAO - use it instead in service classes.
+7
source share

Your problem is related to Spring AOP limitations. Bozho's answer is good, and you should consider reorganizing your code to support its advice.

But if you want your transaction to work without changing the code, it is possible!

Spring AOP is the default choice in Spring aspect technology. But with some configuration and the addition of AspectJ weaving, it will work, since AspectJ is a much more powerful technology that allows pointcuts between two methods in the same class.

0
source share

All Articles