As shown below, I am accessing a Service layer method inside of another DAO.
(Every DAO in the system is implemented using HibernateDAOSupport class)
I wanted to rollback the transaction when #1 or #2 (commented in the following code) is failed.
But when #2 throws an exception, #1 does not get rolled back and I can see the entries in the database.
#Transactional(readOnly=false, rollbackFor={DuplicateEmailException.class,DuplicateLoginIdException.class,IdentityException.class},propagation=Propagation.REQUIRES_NEW)
public void createUserProfile(UserProfile profile)
throws DuplicateEmailException, DuplicateLoginIdException,
IdentityException {
// #1 create principal using Identity Service
identityService.createPrincipal(profile.getSecurityPrincipal());
try {
// #2 save user profile using Hibernate Template
getHibernateTemplate().save(profile);
} catch (RuntimeException e) {
throw new IdentityException("UseProfile create Error", e);
}
}
Here is the signature for createPrincipal() method of'IdentityService'.
public void createPrincipal(Principal principal) throws DuplicateEmailException,DuplicateLoginIdException,IdentityException ;
There's no Transaction management configured in 'IdentityService'
What I am doing wrong here ?
Try Propagation.REQUIRED, instead of Propagation.REQUIRES_NEW
During the calls identityService.createPrincipal(profile.getSecurityPrincipal()); aren't you flushing the session ? (executing a query for example, with FlushMode.AUTO)
Related
I want to call different methods that interact with my database in one method.
something like this :
#Autowired
EnteteService es; // service for jpaRepository
#Autowired
SystemOracleServiceJpa so; // service using jdbcTemplate
#Autowired
DetailService ds; // service for jpaRepository
#Transactional
public void configure(EnteteAR entete) throws ConfigurationException {
try{
this.es.save(entete); // first methode
List<DetailAR> details = this.so.getStructure(entete.getTableName());
if(details.size()>0){
this.ds.saveAllDetails(details); // second
this.so.CreateTable(details, entete.getTableName(), "DEM");//third
this.so.createPkIdxDem(entete.getTableName()); // fourth
this.so.CreateTable(details, entete.getTableName(), "BACK"); // fifth
}
else{
throw new ConfigurationException("Configuration error");
}
}catch(Exception e){
throw new ConfigurationException(e.getMessage());
}
}
I want to commit only if no errors appears in all this methods inside my main method "configure".
I was thinking that #transactionnal annotation work for this, but that commit after each method inside.
Exemple :
if this.es.save work and this.ds.saveAllDetails dont, I find data of es.save on database :(
Someone can help my please ?
thank with advance for your reading and your potential help.
#Transactional will automatically invoke a rollback if an unchecked exception is thrown from the executed method.
ConfigurationException in your case is a checked exception and hence it does not work.
You can make it work by modifying your annotation to
#Transactional(rollbackOn = ConfigurationException.class)
public void configure(EnteteAR entete) throws ConfigurationException {
try{ ....
I had created a service layer in which my method was having the transactional annotation over it in the following manner :
#Transactional
void a() {
User user = new User(1, "Abc", "Delhi");
userDao.save(user);
A a = null;
a.toString(); //null pointer exception being encountered here.
}
The transaction should have been rolled back and the user's details should not have been persisted to the db, but it is not happening.
Run time exceptions will roll back the transaction by default. I don't know exactly in hibernate, but in eclipse link implementation of JPA, we can specify the rollback = true/false for the application exceptions as shown below.
#ApplicationException(inherited = true, rollback = true)
try similar configuration change.
you can also rollback in the catch block something like below
catch(Exception e) {
entityManger.getTransaction().rollback();
}
I created a service method that creates user accounts. If creation fails because the given e-mail-address is already in our database, I want to send the user an e-mail saying they are already registered:
#Transactional(noRollbackFor=DuplicateEmailException.class)
void registerUser(User user) {
try {
userRepository.create(user);
catch(DuplicateEmailException e) {
User registeredUser = userRepository.findByEmail(user.getEmail());
mailService.sendAlreadyRegisteredEmail(registeredUser);
}
}
This does not work. Although I marked the DuplicateEmailExcepetion as "no rollback", the second SQL query (findByEmail) still fails because the transaction was aborted.
What am I doing wrong?
There is no #Transactional annotation on the repository.
That's not a problem with Spring / JDBC or your code, the problem is with the underlying database. For example, when you are using the Postgres if any statement fails in a transaction all the subsequent statements will fail with current transaction is aborted.
For example executing the following statements on your Postgres:
> start a transaction
> DROP SEQUENCE BLA_BLA_BLA;
> Error while executing the query; ERROR: sequence "BLA_BLA_BLA" does not exist"
> SELECT * FROM USERS;
> ERROR: current transaction is aborted, commands ignored until end of transaction block
Still the SELECT and subsequent statements are expected to succeed against MySQL, Oracle and SQL Server
Why don't you change the logic as following :
void registerUser(User user) {
User existingUser = userRepository.findByEmail(user.getEmail())
if(existingUser == null){
userRepository.create(user);
}else{
mailService.sendAlreadyRegisteredEmail(existingUser)
}
}
This would ensure that only non-existing users to be inserted into the database.
#Transactional annotation is placed at incorrectly. Spring creates a AOP advisor around the method where #Transactional annotation is defined. So, in this case, pointcut will be created around registedUser method. But, registerUser method doesn't throw DuplicateEmailException. Hence, no rollback rules are evaluated.
You need to define the #Transactional rule around UserRepository.createUser method. This will ensure that Transaction pointcut created by spring doesn't rollback because of DuplicateEmailException.
public class UserRepository {
#Transactional(noRollbackFor=DuplicateEmailException.class)
public User createUser(){
//if user exist, throw DuplicateEmailException
}
}
void registerUser(User user) {
try {
userRepository.create(user);
catch(DuplicateEmailException e) {
User registeredUser = userRepository.findByEmail(user.getEmail());
mailService.sendAlreadyRegisteredEmail(registeredUser);
}
}
You could wrap the call to the userRepository in a try catch block. Or you could look first if the user exists, and abourt the creation of a new one.
I try to execute this code
#Transactional
#Controller
public class MyController{
....
#RequestMapping(..)
public String MyMethod(...)
{
....
try {
ao_history_repository.save(new AoHistory(..));
}
catch (DataIntegrityViolationException e) {
System.out.println("history already exist");
}
....
model.addAttribute("...", my_respository.findAoToDetail(id) );
return "...";
}
But when i got duplicate entry Exception i catch it but after i got a other Exception
org.hibernate.AssertionFailure: null id in persistence.AoHistory entry
(don't flush the Session after an exception occurs)
I know that When a ConstraintViolationException is thrown it invalidates the current session but how can i reopen a new session and a new transaction ?
As you write, you need a new transaction. From your code snippet it looks like the simplest thing would be to move #Transactional from the controller to the repository classes. As an alternative, you could add a service layer and move #Transactional there.
A different approach would be to pre-check the entity object before trying to save it in the entity manager, so that exception is never thrown.
Below is what I did, I need to implement rollback, using #transactional annotation, but not working as expected, what else need to be done for proper rollback to happen ?? I want that when the code is executed result in db should be "testingOne" , currently it is set to "notRollBacked". Can you please point my mistake.
public Response deleteUser(Request argVO)throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setUserName("testingOne");
UsersLocalServiceUtil.updateUsers(users);
try
{
testRollbackFunction();
}
catch (Exception ex)
{
}
return new Response();
}
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
private void testRollbackFunction() throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setUserName("notRollbacked");
UsersLocalServiceUtil.updateUsers(users);
throw new PortalException();
}
****************Edit 1*************
I did what was mentioned in answers:
I did taken bean from context
and written a class/bean as
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
public class RollBack
{
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
public void thisWillRollBack() throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setBarringReason("notRollbacked");
UsersLocalServiceUtil.updateUsers(users);
throw new PortalException();
}
}
spring xml file bean refrence set as :
<bean id="rollBackBean" class="com.alepo.RollBack">
</bean>
public Response myMethod(Request argVO)throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setBarringReason("testingOne");
UsersLocalServiceUtil.updateUsers(users);
try
{
Test test = new Test();
Object obj = test.getBean();
RollBack rollBack = (RollBack)obj;
rollBack.thisWillRollBack();
}
catch (Exception ex)
{
ex.printStackTrace();
}
return new Response();
}
#################EDIT 4
now calling rollback function as :
RollBack rollBack = (RollBack)PortalBeanLocatorUtil.getBeanLocator().locate("rollBackBean");
rollBack.thisWillRollBack();
No Test class in picture now ...no new anywhere ...
still NOT WORKING .......
If you have a #Transactional annotation on method, Spring wraps the call to this method with aspect handling the transaction.
So:
1) Only public methodes can be wrapped in aspect
2) You call wrapped code only if you call the method on a bean taken from / injected by Spring container.
In your case:
1) The code isn't wrapped in transactional aspect because it is not public method
2) Event if it was, it is called directly from within the class, so you wouldn't call wrapped version anyway.
So the solution is to make separate bean with #Transactional method, inject it into and call it from Response class.
Of course you need <tx:annotation-driven/> in your spring-xml or instruct Spring otherwise to process #Transactional annotations (see the reference).
The issue is you are outside the application context. You are creating a new instance of a class, NEW is bad in Spring, very bad. Get an instance of Test from the application context, not by creating a new instance unless you start your application context in Test. Try to Autowire test in your class you mention above or inject it from Spring and then let me know, but the code you are showing above will never work with transaction management.