We are using Spring 4.x and Spring Data JPA with declarative transaction management, I have a Controller, Service and a Repository like below pseudo code.
#Service
#Transactional(readOnly=true)
public class SampleService {
#Autowired
private SampleRepository sampleRepository;
#Transactional
public MyEntity saveMyEntity(MyEntity entity) {
//do some business logic
return sampleRepository.save(entity);
}
}
public class SampleController {
#Autowired
private SampleService sampleService;
public String saveSample(#Valid MyEntity entity) {
//Validation
//If Valid
sampleService.saveMyEntity(entity);
//After saving do some view related rendering logic
//Assume here view related rendering logic throws Exception
return "view"
}
}
In the above code an error gets thrown after call to sampleService.saveMyEntity(entity); but the transaction doesn't mark for rollback, so end user will get an error page but behind the scene entity got persisted.
Is there any way i can rollback the transaction ?
You can do the following.
#Transactional(rollbackFor=Exception.class)
public String saveSample(#Valid MyEntity entity) {
//Validation
//If Valid
sampleService.saveMyEntity(entity);
//After saving do some view related rendering logic
//Assume here view related rendering logic throws Exception
return "view"
}
Since the default Transaction Propagation is Required not Required new. Transaction will actually begin at SampleController.saveSample() and the same one will be used SampleService.saveMyEntity(). When an exception thrown from saveSample() the entire transaction will be rolled back.
Related
Currently if exception occurs in method C, code is not rolling back database changes from method B. My expectation is method A should manage transaction in such a way that if exception occurs in method C, code should rollback changes done in method B.
I am using spring boot, maven project.
class SomeClassA{
#Autowired
SomeClassB someClassB;
#Autowired
SomeClassC someClassC;
#Transactional
public A(){
try{
//This method works fine with some database operations.
someClassB.B();
//In this method, exception occurrs.
someClassC.C();
}
catch(Exception e){
}
}
}
class SomeClassB{
#Transactional
public B(){
//some code with database operation
}
}
class SomeClassC{
#Transactional
public C(){
//some code with database operation
//some exception occurs here
}
}
Is it a Checked or Runtime exception ?
Because the default behaviour of #Transactional says:
Any RuntimeException triggers rollback, and any checked Exception does
not.
I have a very basic Spring Boot/JPA stack app, with a controller, service layer, and repository that does not persist updates as I understand it should.
A trivial Entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
protected Customer() {}
public Customer(String name) { this.name = name; }
// standard getters,setters //
}
A trivial Repository:
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {}
A simple Service layer:
// If the service is #Transactional and the controller is not, the update does NOT occur
#Transactional
#Service
public class CustomerService {
private static final Logger LOG = getLogger(CustomerService.class);
#Autowired
private CustomerRepository customerRepository;
boolean updateCustomerName(Long id, String name) {
Customer customer = customerRepository.findOne(id);
if (customer == null) { return false; }
// Modifies the entity
customer.setName(name);
// No explicit save()
return true;
}
}
And a REST controller that uses it all:
// If the controller is #Transactional and the service is not, the update occurs
#RestController
#RequestMapping("/mvc")
public class CustomerController {
#Autowired
private CustomerService customerService;
#RequestMapping(path = "{id}", method = RequestMethod.PUT)
public ResponseEntity updateCustomerName(#PathVariable Long id, #RequestParam("name") String name) {
customerService.updateCustomerName(id,name);
return ResponseEntity.noContent().build();
}
}
These are wired together with a simple one-liner SpringBootApplication
I have SQL debug logs enabled and see the selects, update, etc.
With the code above: When the service method is invoked by the controller, the modified entity is not persisted. SQL logs show the select of the entity but no update.
There is also no update if nothing is marked #Transactional
However, simply by moving the #Transactional annotation from the service class to the controller class, the SQL update does occur.
If I add an explicit customerRepository.save(customer) to the service method, the update also occurs. But my understanding is that the ORM should automatically save modified persistent entities.
I'm sure the issue has something to do with the EntityManager lifecycle in the web request, but I'm puzzled. Do I need to do additional configuration?
Complete example at https://github.com/monztech/SO-41515160
EDIT: This was solved, see below. Per the Spring spec #Transactional does not work in package-private methods and mistakenly did not make the update service method public.
The update will occur if the method is public and the service class has the #Transactional annotation.
I do have another question, however. Why is the #Transactional annotation necessary? (the update does not occur without it) Shouldn't the entity manager still persist the object because of the open session in view mechanism that Spring uses, independent of any transaction?
Make your updateCustomerName method public.
We have class like below
#Transactional(readOnly=true)
public class AService {
#PersistenceContext
private EntityManager em;
#Autowired
private ARepository arepo;
public void saveA(A a) {
arepo.save(a);
}
public void saveandFlushA(A a) {
arepo.save(a);
entityManager.unwrap(Session.class).flush();
}
}
We are using postgres + jpa-hibernate + spring transaction management, when we call the save(a) method its not causing a save commit, which is normal behavior since my transaction is set to readonly=true but when i call saveandFlushA(a) its causing insert statement fired and get committed but i'm expecting it to be not committed.
Spring Transaction doc says readOnly flag sets Read/write vs. read-only transaction
I did not understand why the data gets committed even its a readonly transaction ? any insight would be greatly welcomed.
I have rest style controller in Spring. In controller I have injected dao interfaces. From controller I persist data. In the other words, I have like REST web service. people sends me data, and I persits it.
/**
* Payment rest controller which receives
* JSON of data
*/
#Controller
#RequestMapping("/data")
public class PaymentTransaction {
#Autowired
private TestDao dao;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
}
At the moment I have #transaction annotation in Dao classes. For instance:
import org.springframework.transaction.annotation.Transactional;
#Component
#Transactional
public interface TestDao {
#Transactional(propagation = Propagation.REQUIRED)
public void first();
}
I have read that this is very bad style. Using this answer at stackoverflow , here is explain and examples why is this bad - we must not add this annotation in DAO and in controller too. We must add it in service layer.
But I don't understand what is the service layer? Or where is it? I do not have anything like this.
where should I write #Transactional annotation?
Best regards,
According to the cited post, you should design your classes somehow like this (rather pseudocode):
controller (responsible for handling clients' requests/responses)
#Controller
#RequestMapping("/data")
public class TestREST {
#Autowired
private TestService service;
public void storePayment(PaymentDTO dto) {
service.storePayment(dto); //request from a client
}
public PaymentDTO getPayment(int paymentId) {
return service.getPayment(paymentId); //response to a client
}
}
service layer (also called business layer, responsible for business logic - knows what to do with incoming messages, but does not know where they come from).
public class TestServiceImpl {
#Autowired
private TestDao dao;
#Transactional(propagation=Propagation.REQUIRED) //force transaction
public void storePayment(PaymentDTO paymentDto) {
// transform dto -> entity
dao.storePayment(paymentEntity); //read-write hence transaction is on
}
#Transactional(propagation=Propagation.NOT_SUPPORTED) //avoid transaction
public Payment getPayment(int paymentId) {
return dao.findPayment(paymentId); //read-only hence no transaction
}
}
data access layer (also called persistence layer, responsible for accessing database - knows how to use entity model / ORM, does not know anything about the upper service layer)
public class TestDAOImpl {
#PersistenceContext
private EntityManager em;
public void storePayment(PaymentEntity paymentEntity) {
em.persist(paymentEntity);
}
public PaymentEntity getPayment(int paymentId) {
return em.find(PaymentEntity.class, paymentId);
}
}
By this approach you get separation of concerns mentioned in the post. From the other hand such an approach (business layer vs data access layer) got a little dose of criticism from Adam Bien's on his blog ("JPA/EJB3 killed the DAO"). As you can see there is no a single solution for the problem, but I encourage to read some other opinions and apply the solution you find the most suitable for your needs.
When you call two Dao methods first & second from controller, 2 transactions will be done, one with starts before first method and ends after it's execution and the second one starts before second method starts and ends after it's execution. Whereas you create an additional class in between controller and dao (usually this is called service layer) and annotate it with #Transactional and call multiple Dao methods in it, a transaction is started at the start of service method and all the dao calls will be executed and transaction will be closed, which is what you require. And inject the Service into Controller.
Controller -> Service -> Dao
#Controller
#RequestMapping("/data")
public class PaymentTransaction {
#Autowired
private TestService service;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
}
}
#Service
#Transactional
public class TestService {
#Autowired
private TestDao dao;
#Transactional
public void serviceCall(){
dao.first();
dao.second();
}
}
I have:
1) Service:
#Service("scanner")
#Transactional
public class Scanner
{
#Inject
AnalyzerService analyzerService;
#Transactional
private void scan() {
analyzerService.analyze();
}
}
2) Service:
#Service
public class AnalyzerService
{
#Inject
AnalyzerDao analyzerDao;
#Transactional
public void analyze() {
List<AnalyzerResult> items;
// code filling items list removed;
save(items);
}
#Transactional
private void save(List<SomeType> items) {
analyzerDao.save(items); // <--- Why after call save items are not saved in DB?
}
}
3) Dao:
#Repository
public class AnalyzerDao extends GenericDaoImpl<AnalyzerResult>
{
//all needed methods for find, edit, delete and save which works fine in other cases.
}
Question:
Why after call analzyerDao.save(items) DB is still empty? Is it problem with transaction some how?
When I invoke flush() method and getSession().getTransaction().commit() just after line analyzerDao.save(items) then records appearing in DB but exception is thrown:
Caused by: org.springframework.transaction.TransactionSystemException: Could not commit Hibernate transaction; nested exception is org.hibernate.TransactionException: Transaction not successfully started
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:660)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy44.execute(Unknown Source)
at org.quartz.core.JobRunShell.run(JobRunShell.java:223)
... 1 more
Caused by: org.hibernate.TransactionException: Transaction not successfully started
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:127)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656)
... 9 more
How should implementation be done to work 'save' method fine?
What should I do to save items just in line analyzerDao.save(items) and not only after first transaction will be finished?
What you need is a new transaction just to save what you need to save. You can achieve this by configuring the propagation of #Transactional annotation to REQUIRES_NEW.
Unfortunately your case is a bit tricky, because you are invoking a method within this context when you do save(items);, this means the transaction interceptor will not intercept such invocation, therefore you have the possibility to inject the service to a field hold by itself and invoke it on the injected service instead of this forcing the invocation to that method be intercepted by the transaction interceptor, please try the following implementation:
#Service
public class DefaultAnalyzerService implements AnalyzerService {
#Inject
AnalyzerDao analyzerDao;
#Inject
AnalyzerService analyserService;
#Transactional
#Override
public void analyze() {
List<AnalyzerResult> items;
// code filling items list removed;
analyserService.save(items);
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Override
public void save(List<SomeType> items) {
analyzerDao.save(items); // <--- Why after call save items are not saved in DB?
}
}
Another thing that changed was the visibility of save(List<SomeType> items), that is public now on in order to be intercepted by transaction interceptor and an interface was extracted. This is needed due to limitations with spring, but you can use AspectJ to handle such interceptor, therefore please take a look here.
Data won't appear in the database until the transaction is committed. For #Transactional methods, the transaction is committed by Spring after returning from the method.
By the way, #Transactional on private methods has no effect, so Scanner.scan() is not transactional at all.