I have a Java, Spring Data application with a PostgreSQL DB and I have a code similar to this in my service layer:
public void method1() {
PersonDto dto = method2();
method3(dto);
}
@Transactional
public PersonDto method2() {
Person p1 = personRepository.saveAndFlush(new Person());
return createPersonDto(p1);
}
@Transactional
public void method3(PersonDto p1) {
Person p2 = personRepository.findById(p1.getId());
if (p2 == null) {
System.out.println("Not in the DB");
} else {
System.out.println("In the DB");
}
}
Sometimes this code prints "Not in the DB", when I was expecting that it would always print "In the DB". My questions are:
- This saveAndFlush is even necessary? Since I'm not reusing that Entity in the same transaction, I guess it would have the same effect as a save, right?
- How to guarantee that the changes in method2 will be committed before method 3 calls it? So it will always print "In the DB"?
- The fact that I'm converting my Entity to a DTO is what is messing this up? Maybe if I was passing the Entity itself it would work properly?
I'm thinking about @Transactional only in method1, or replacing @Transactional in method3 with @Transactional(isolation = Isolation.READ_UNCOMMITTED). Any advice?
CodePudding user response:
I can see a couple of reason why you're getting the results you describe.
One possibility is that @Transactional
doesn't necessarily mean "execute this method in a totally separate transaction." The default propagation is REQUIRED
, which will use an existing transaction if one is already active. So if method2()
is called in a stack where a transaction was opened earlier, it won't create a new one (and thus the new entity won't be committed) until that original transaction is committed. If this is what's happening, one solution would be to change the propagation to REQUIRES_NEW
:
@Transactional(propagation = REQUIRES_NEW)
Another possibility, which seems more likely, is that these methods are all in the same class. To understand why that fails, you have to understand how Spring implements handling of @Transactional
(and most other behavioral annotations): using proxies. That means that only method calls that come through the proxy get the transactional behavior. That's the typical scenario when one object calls a reference it has to another; the reference is actually to the proxy, which intercepts the call and wraps the behavior (in this case, transactionality) around it. However, when calling methods within the same instance, there is no proxy in play (Spring doesn't/can't replace this
with a reference to the proxy), and thus no transactional behavior.
There are a few ways to work around that, but first I would consider how to restructure my classes and methods to avoid it. If that's not reasonable, things like AspectJ, TransactionTemplate
, and maybe some other ideas are discussed in this SO Q&A.
Addendum: This question and its answers also talks about options to work around the proxying problem.