1

I have an Ingestion class that exposes a single method ingest. This method processes each section of a passed in form (section 1, section 2, etc, etc).

I have private methods for each section, saving the entity as it processes through. I'm aware that @Transactional has no effect on private methods, however I do not want to expose these methods but would like to use the functionality that @Transactional provides.

I'm looking to make sure each section completes in its own Transaction; I could do this through 'AspectJ' (as other SO answers have suggested) instead of Spring's out the box implementation, but I am trying to avoid due to the system wide changes it would cause.

Any thoughts on another approach?

The pseudo code provided below gives a general idea on the structure of the class:

public Class Ingestion {
   // Autowired Repo's
   ...
   ...
   @Transactional
   public void ingest(Form form){
       this.processSection1(form);
       this.processSection2(form);
       this.processSection3(form);
   }

   @Transactional
   private void processSection1(Form form){
     // do specific section 1 logic
     section1Repo.save(form);
   }

   @Transactional
   private void processSection2(Form form){
     // do specific section2 logic
     section2Repo.save(form);
   }

   @Transactional
   private void processSection3(Form form){
     // do specific section3 logic
     section3Repo.save(form);
   }
}

========================================================================= This is not a duplicate question as marked in the comments. I know @Transactional doesnt work on private methods. My question is more along the lines of 'how do we get around this Spring AOP issue without having to use AspectJ'

Dan
  • 979
  • 1
  • 8
  • 29
  • 1
    What added benefit do the desired `@Transactional` annotations on the private methods provide that you're not already getting from the annotation on the public method? – Jordan Mar 01 '19 at 15:51
  • Are you really calling the same `save` method three times on the same entity as shown? – The Head Rush Mar 01 '19 at 15:55
  • I want to make sure each processed section gets commited. So if section1 fails, I still want section2 to continue processing. Problem is if a single section fails the entire `Transaction` gets marked for rollback causing the other sections to not get persisted. – Dan Mar 01 '19 at 15:58
  • @TheHeadRush - The save method is each section is different. Each section has it's own `Repository`. I've updated my example code above. – Dan Mar 01 '19 at 15:58
  • @Dan, just so I'm clear, are you saying that you want the equivalent of having the annotations on the private methods, but you *don't* want it on the public method? – Jordan Mar 01 '19 at 16:11
  • @Jordan - Yes. Due to encapsulation rules (only 1 public method `ingest`) I'd like to be able to utilize the `@Transactional` functionality on private methods, so the entire `Transaction` doesn't get rolled back for a single exception that might be thrown – Dan Mar 01 '19 at 16:13
  • Possible duplicate of [Does Spring @Transactional attribute work on a private method?](https://stackoverflow.com/questions/4396284/does-spring-transactional-attribute-work-on-a-private-method) – Dherik Mar 01 '19 at 16:45
  • @Dherik - It's not a duplicate b/c I know it doesn't work, Im looking for other way of applying the `Transactional` functionality without making them public. – Dan Mar 01 '19 at 17:14
  • @Dan the only way is using the `AspectJ`. If another answer without `AspectJ` could exists, your question will still be a possible duplicate, because the best place for this answer seems to be in the question that I mentioned. – Dherik Mar 01 '19 at 17:20
  • @Dherik - The answers below give some ideas on how proceed other than "just using AspectJ" as the answer to your linked question provides. I think others might get some ideas from riguron's answer. – Dan Mar 01 '19 at 20:06
  • the easiest thing you can do - is to inject this service into itself using setter injection. and call 'save' on injected service. but this is not the most elegant solution, isn't it? )) – Yurii Bondarenko Mar 01 '19 at 22:22
  • https://stackoverflow.com/a/54956130/7569908 This answer explains how to get around using Spring AOP – Arjun Patil Mar 02 '19 at 19:34

2 Answers2

0

The reason this doesn't work is that annotations like @Transactional add additional functionality that is intercepted by Spring's proxy object that wraps the actual object. But when you call a private method on an object with the this keyword, you're going straight to the real object and bypassing the proxy.

One way to solve this is to @Autowire the object into itself, and make the transactional calls via that autowired variable. You can still access private methods that way, and the call will be to a Spring-managed proxy instead of the bare object.

Jordan
  • 2,273
  • 9
  • 16
0

You may extract these three processing methods in another class, make them public, but set the class constructor access level to package-local (but not private, since Spring can't proxy classes with private constructors), so no classes from other packages could access these methods just because they are not able to instantiate their class. It doesn't hide these methods completely, but may fit your needs. This trick can be done with an inner class as well (note that it must be declared with package-local access).

To completely hide these methods, you may make use of declarative transaction management by injecting TransactionTemplate bean and using its execute method in private methods. This feature comes out-of-the-box. See more here.

Also, take note that for creating new transaction on executing method B from method A, method B must be declared @Transactional with propagation type REQUIRES_NEW. Otherwise, any nested methods will be invoked in the same transaction started by initial calling method.

jolice
  • 98
  • 1
  • 6