Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@Transactional and JPA object parameters

When programming multi-tier application it seems to be best practice to pass only object ids to transactional service methods. But I would rather like to pass actual JPA objects. Unlike in the question Is domain model object passing between layers overhead? some colleagues fear that a) object could belong to another/no transaction if and thus cause problems when modified inside the service method and b) objects could cause problems when modified after invoking such a service method from the UI component because the transaction is already committed.

Expressed in code I would rather like to have

@Named public class MyServiceImpl
{
   ...

   @Transactional
   public BigDecimal calculate(ObjectOne objectOne, ObjectTwo objectTwo)
   {
      ...
   }
}

instead of

@Named public class MyServiceImpl
{
   ...

   @Transactional
   public BigDecimal calculate(long objectOneId, long objectTwoId)
   {
      ObjectOne objectOne = objectOneService.find(objectOneId);
      ObjectTwo objectTwo = objectTwoService.find(objectTwoId);
      ...
   }
}

So is there a technique where the transaction manager (spring) cares for the objects properly? Or do you recommend to use JPA merge or anything else explicitly to handle direct object references properly? Or do you discourage passing objects instead of IDs as well?

Especially explanations with links to official or well-known sources would be helpful, obviously.

like image 452
Christian Avatar asked Oct 20 '22 22:10

Christian


1 Answers

As the default behavior of @Transactional is PROPAGATION_REQUIRED it should be fine to pass business objects into transactional methods. If the calling operation already opened a transaction a new virtual transaction (for the same physical transaction) is opened and the object references remain intact. If no transaction is active no persistent state can be harmed.

To make sure object are sane you can do

@Transactional
public BigDecimal calculate(ObjectOne objectOne, ObjectTwo objectTwo)
{
   objectOne = objectOneService.find(objectOne.getId());
   objectTwo = objectTwoService.find(objectTwo.getId());
   ...
}

This is cheap, as the objects are fetched from the cache. But it is not required as the objects are within the same physical transaction.

If the objects really are from a different transaction you can work around it with the code above. But your calling method will not see any changes made to the object unless it fetches its objects from database again (and depending on isolation starts a new transaction).

like image 166
isi Avatar answered Oct 22 '22 10:10

isi