Disclaimer: Yes, I know that @Transactional
should only be put on Service methods, ideally.
I am supporting an old and poorly-designed application where all the business logic is cluttered in Controller methods, and those methods aren't transactional, so errors don't trigger rollback and the data is corrupted.
Our team needs a quick fix to enable error rollback for this legacy application, so it was decided to put @Transactional
on Controller methods (rather than refactor the application which would be a major effort).
However, when I tried the following on a Controller method, it didn't roll back my exception:
@Controller
public class ReviewDetailsController extends BaseController{
@RequestMapping(value={"/testException"}, method = RequestMethod.POST)
@Transactional(readOnly = false, rollbackFor = Exception.class)
public void testException(@RequestParam(required = true) String planId) throws Exception {
// Simulate some simple DB change: we'll change a Date to a dummy value, 03:33:33
Plans plan = planService.findById(new Integer(planId));
PlanWorkflow pw = processService.getLatestWorkflow(plan);
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
Date d = sdf.parse("12/06/2019 03:33:33");
pw.setCreatedDate(d);
// Save it
processService.savePlanWorkflow(pw);
// Exception test
throw new Exception("EXCEPTION TEST");
}
On the other hand, when put the exact same logic inside a sample Service class method, it did roll back successfully.
Controller now calls Service method, the Service method has @Transactional
@RequestMapping(value={"/testException"}, method = RequestMethod.POST)
@Transactional(readOnly = false, rollbackFor = Exception.class)
public void testException(@RequestParam(required = true) String planId) throws Exception {
// Call a Service method with the exact same logic from the Controller
processService.testException(planId);
}
Service
@Component
public class ProcessServiceImpl implements ProcessService {
@Override
@Transactional(readOnly = false, rollbackFor = Exception.class)
public void testException(@RequestParam(required = true) String planId) throws Exception {
// Simulate some simple DB change: we'll change a Date to a dummy value, 03:33:33
Plans plan = planService.findById(new Integer(planId));
PlanWorkflow pw = processService.getLatestWorkflow(plan);
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
Date d = sdf.parse("12/06/2019 03:33:33");
pw.setCreatedDate(d);
// Save it
processService.savePlanWorkflow(pw);
// Exception test
throw new Exception("EXCEPTION TEST");
}
In theory, everyone says I should be able to attach @Transactional
to Controller methods (even though it's not recommended -- but I still should be able to do it). So why don't the Controller methods pick it up or honor it?
applicationContext:
<context:component-scan base-package="mypackage.myapp">
</context:component-scan>
<jpa:repositories base-package="mypackage.myapp.dao"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="/META-INF/persistence.xml"/>
</bean>
<cache:annotation-driven/>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cache-manager-ref="ehcache"/>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:config-location="/WEB-INF/ehcache.xml"/>
Things I've tried:
- Make the Controller implement an Interface, to enable "JDK Proxy". Now the Controller
implements
ControllerInterface
where the method signatures have been extracted (Source -> Refactor -> Extract Interface in Eclipse). - Add
<aop:aspectj-autoproxy proxy-target-class="true" />
to enable "CGLIB Proxy".
Related threads where I got these ideas from: 1, 2.
But none of this helped. Data still isn't getting rolled back from the Controller.
Similar thread: 1. No solution offered.