How to perform rollback in Spring Data JPA for the following scenario?
Transactional
#Override
public Employee saveEmployee(EmployeeDto dto) {
// check if EmployeeId and Department Id is present
Employee employee = this.getByEmployeeId(dto);
Department department = this.getByDepartmentId(dto);
Employee employee = convertToEntity(dto, employee, department);
employee.setEmployees(Arrays.asList(employee));
department.setEmployees(Arrays.asList(employee));
try {
employee = employeeRepository.save(employee); //line-11
} catch (DataIntegrityViolationException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "ConstraintViolationException", e.getCause());
} catch (Exception ex) {
throw new InternalServerException(HttpStatus.INTERNAL_SERVER_ERROR, env.getProperty(IConst.ERROR_DB_EXCEPTION), ex);
}
EmployeeEmployeeDepartment r = new EmployeeEmployeeDepartment();
r.setId(new EmployeeDepartmentPK());
r.setEmployee(employee);
r.setDepartment(department);
r.setEmployee(employee);
try {
compositeRepository.save(r); //line-22
}catch (DataIntegrityViolationException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "ConstraintViolationException", e.getCause());
}
catch (Exception ex) {
throw new InternalServerException(HttpStatus.INTERNAL_SERVER_ERROR, env.getProperty(IConst.ERROR_DB_EXCEPTION), ex);
}
return employee;
}
How to roll back line-11 if line-22 fails?
1) If ResponseStatusException and InternalServerException are both RuntimeExceptions then you do not need to do anything as Spring by default rolls back the entire transaction on any RTE.
2) Just keep in mind that invoking save() and eventually persist() on entityManager does not cause any physical update on the DB until the transaction commits. These methods simply register an entity in the Persistence Context.
Use "rollbackFor"
#Transactional(rollbackFor = DataIntegrityViolationException.class)
Multiple exception:
#Transactional(rollbackFor = { ResponseStatusException.class, InternalServerException.class })
Related
I'm using a Data Access Object (DAO) pattern in Java and I have the same piece of code repeated all over my files. The thing is somethimng like this:
public User getById(int id) throws BDException {
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.getTransaction();
try {
tx.begin();
Query query = session.createQuery("SELECT u FROM User u WHERE u.id=:id");
query.setString("id", id);
User user = (User) query.uniqueResult();
tx.commit();
return user;
}
catch(javax.validation.ConstraintViolationException | org.hibernate.exception.ConstraintViolationException cve) {
try {
if(tx.getStatus() == TransactionStatus.ACTIVE) {
tx.rollback();
}
}
catch(Exception exc) {
LOGGER.error("Error rollback in method='" + getMethodName() + "'");
}
throw new BDException(cve);
}
catch(RuntimeException ex) {
try {
if(tx.getStatus() == TransactionStatus.ACTIVE) {
tx.rollback();
}
}
catch(Exception exc) {
LOGGER.error("Error rollback in method='" + getMethodName() + "'");
}
throw ex;
}
catch(Exception ex) {
try {
if(tx.getStatus() == TransactionStatus.ACTIVE) {
tx.rollback();
}
}
catch(Exception exc) {
LOGGER.error("Error rollback in method='" + getMethodName() + "'");
}
throw new RuntimeException(ex);
}
}
Well, I want you to look at the catch's part. I have it repeated in every method I have. If it was simple code, I could create a method, put all that code inside and call the method instead of repeat the code. The problem is that it is not normal code, they are exceptions.
So, is there any solution to reuse code and not to repeat (copy-pasting) the code in every method?
Thanks!
is there any solution to reuse code and not to repeat (copy-pasting) the code in every method?
There is.
The "meat" of your function is here
Query query = session.createQuery("SELECT u FROM User u WHERE u.id=:id");
query.setString("id", id);
User user = (User) query.uniqueResult();
If you squint very carefully, you may see that this is a "function" that accepts a Session as an argument, and returns a User. What you can then do is make this function an argument to the thing that does all of the exception handling.
In Java, that usually means expressing the function as an "object"
User MyCrazyFunctionThing::uniqueResult(Session session) {
Query query = session.createQuery(this.sql);
query.setString("id", this.id);
return query.uniqueResult();
}
User DatabaseGateway::execute(MyCrazyFunctionThing q) {
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.getTransaction();
try {
tx.begin();
User user = q.uniqueResult(session)
tx.commit();
return user;
} catch (...) {
// ...
}
}
Right away, you can turn that into logic that can be run any time you try to fetch a unique user from a session.
You can make that even more general with generics
interface MyCrazyGenericThing<T> {
T uniqueResult(Session session);
}
class MyCrazyFunctionThing implements MyCrazyGenericThing<User> {
User uniqueResult(Session session) {
Query query = session.createQuery(this.sql);
query.setString("id", this.id);
return query.uniqueResult();
}
}
<T> T DatabaseGateway::execute(MyCrazyGenericThing<T> q) {
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.getTransaction();
try {
tx.begin();
T result = q.uniqueResult(session)
tx.commit();
return result;
} catch (...) {
// ...
}
}
What you are seeing here is the Strategy Pattern being used to specify what code should run inside the transaction logic.
Looks like a job for the Execute Around idiom.
Place the specialised code in a lambda expression. Pass the specialised code to a method with the general code that executes the object holding the lambda expression at the appropriate point.
For your code, depending on exactly what you want to factor out, usage may look something like:
public User getById(int id) throws BDException {
return query(
"SELECT u FROM User u WHERE u.id=:id",
query -> {
query.setString("id", id);
return (User) query.uniqueResult();
}
);
}
I work on a Spring webStart application...
I have 2 (and potentially more) methods that process multi-tier Exception clause, as in:
...
try {
employeeService.updateEmployeePartner(employeeId, partner);
LOG.info("partner details updated for partner id {}", employeeId);
result = new ResponseEntity<>(partner.getId(), HttpStatus.OK);
} catch (EmployeePartnerNotFoundException ex) {
LOG.error(ex.getMessage() + " employee id: ", employeeId);
errorResponse = new ErrorResponse("500", ex.getMessage());
} catch (ReadOperationDeniedException ex) {
LOG.error("User doesn't have permissions to update employee's {} details: {}", employeeId, ex.getMessage());
errorResponse = new ErrorResponse("403", "User doesn't have permissions to update employee's details");
} catch (Exception ex) {
LOG.error("something went wrong while updating employee's {} partner details: {}", employeeId, ex.getMessage());
errorResponse = new ErrorResponse("500", "unspecified server error");
} finally {
result = (result != null) ? result : new ResponseEntity<>(errorResponse, HttpStatus.I_AM_A_TEAPOT); // should be INTERNAL_SERVER_ERROR
}
...
Another method is almost identical, apart for this change:
employeeService.updateEmployeePartner(employeeId, partner); =>
employeeService.createEmployeePartner(employeeId, partner);
and catching EmployeePartnerAlreadyExistsException in that block.
Now, to reduce code duplication, I want to group all Error handling code in one place (method), so I replaced the above code with the following
...
try {
employeeService.updateEmployeePartner(employeeId, partner);
LOG.info("partner details updated for partner id {}", employeeId);
result = new ResponseEntity<>(partner.getId(), HttpStatus.OK);
} catch (Exception ex) {
errorResponse = processException(ex, employeeId, "update");
} finally {
result = (result != null) ? result : new ResponseEntity<>(errorResponse, HttpStatus.I_AM_A_TEAPOT); // should be INTERNAL_SERVER_ERROR
}
...
private ErrorResponse processException(Exception ex, Long employeeId, String operation) {
ErrorResponse errorResponse;
if (ex.getClass().equals(EmployeePartnerNotFoundException.class) ||
ex.getClass().equals(EmployeePartnerExistsException.class)) {
LOG.error(ex.getMessage() + " employee id: ", employeeId);
errorResponse = new ErrorResponse("500", ex.getMessage());
} else if (ex.getClass().isInstance(ReadOperationDeniedException.class)) {
LOG.error("User doesn't have permissions to " + operation + " employee's {} details: {}", employeeId, ex.getMessage());
errorResponse = new ErrorResponse("403", "User doesn't have permissions to " + operation + " employee's details");
} else { // Exception
LOG.error("something went wrong while trying to " + operation + " employee's {} partner details: {}", employeeId, ex.getMessage());
errorResponse = new ErrorResponse("500", "unspecified server error");
}
return errorResponse;
}
Is that a good enough approach or are there any patterns to handle exceptions in the above scenario by outsourcing the handling to a separate method/class?
Since it's a spring application, I also consider using Spring exception handlers, as in:
#ExceptionHandler(Exception.class)
, but that will only cover part of my requirements.
Use #ControllerAdvice with your custom ErrorResponse and each Handler for seprate exceptions. Refer Custom error response Spring
Sample code:
#ControllerAdvice
public class GlobalExceptionHandlers {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandlers.class);
/***************** User Defined Exceptions *************************/
#ExceptionHandler({ EmployeePartnerNotFoundException.class })
public ResponseEntity<Object> handleEmployeePartnerNotFoundException(EmployeePartnerNotFoundException ex) {
logger.error("EmployeePartnerNotFoundException : ", ex);
ErrorResponse errorResponse = new ErrorResponse("500", ex.getMessage());
return new ResponseEntity<Object>(errorResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST);
}
// other exception handlers
}
That's what I ended up doing in the end, along with the reply by Sangam:
Individual Exception handlers worked quite well; Do note there is no need to put these in separate class.
But I still wonder if there is a similar pattern where the application is not Spring MVC?
public ResponseEntity<?> updatePartnerDetails(#PathVariable("employeeId") Long employeeId,
#RequestBody PersonDetails partnerDto) {
LOG.info("Updating partner details for employee {}, partner details {}", employeeId, partnerDto);
validateRequestValues(partnerDto);
// Try-catches were around this call
Person partner = PersonMapper.fromPersonDetails(partnerDto);
employeeService.updateEmployeePartner(employeeId, partner);
LOG.info("partner details updated for partner id {}", employeeId);
return new ResponseEntity<>(partner.getId(), HttpStatus.OK);
}
#ResponseStatus(HttpStatus.I_AM_A_TEAPOT) // TODO: BAD_REQUEST
#ExceptionHandler({EmployeePartnerExistsException.class, EmployeePartnerNotFoundException.class})
public ResponseEntity<?> employeePartnerError(Exception ex) {
LOG.error(ex.getMessage());
return new ResponseEntity<Object>(new ErrorResponse(400, ex.getMessage()), HttpStatus.OK);
}
#ResponseStatus(HttpStatus.I_AM_A_TEAPOT) // TODO: BAD_REQUEST
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<?> validationError(Exception ex) {
LOG.error(ex.getMessage());
return new ResponseEntity<Object>(new ErrorResponse(400, ex.getMessage()), HttpStatus.OK);
}
#ResponseStatus(HttpStatus.I_AM_A_TEAPOT) // TODO: FORBIDDEN
#ExceptionHandler(ReadOperationDeniedException.class)
public ResponseEntity<?> forbidden(Exception ex) {
LOG.error("User doesn't have permissions to amend employee's details");
return new ResponseEntity<Object>(new ErrorResponse(403, "User doesn't have permissions to amend employee's details"), HttpStatus.OK);
}
#ResponseStatus(HttpStatus.I_AM_A_TEAPOT) // TODO: INTERNAL_SERVER_ERROR
#ExceptionHandler(Exception.class)
public ResponseEntity<?> unspecifiedError(Exception ex) {
LOG.error("User doesn't have permissions to amend employee's details");
return new ResponseEntity<Object>(new ErrorResponse(500, "Something went wrong while editing employee's details"), HttpStatus.OK);
}
With this session handler:
public class SessionHandler {
private static SessionFactory DBContext;
static {
try {
DBContext = HibnerateConfiguration.config().buildSessionFactory();
}
catch(Throwable t) {
throw new ExceptionInInitializerError(t);
}
}
/*
* Returns a session anyway. If currently no session exist, open a new one;
* If there is a current session, use the existing one.
*/
#Override
public Session getSession() {
try {
return DBContext.getCurrentSession();
}
catch (HibernateException he) {
logger.error("session already exist.");
return DBContext.getCurrentSession();
}
}
public void close() {
DBContext.close();
}
}
and the following create and get methods:
public Serializable create(T type_entity) {
Session session = getSessionHandler().getSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Serializable result = session.save(type_entity);
tx.commit();
return result;
} catch (HibernateException ex) {
ex.printStackTrace();
if (tx!=null) tx.rollback();
throw ex;
} finally {
getSessionHandler().close();
}
}
#SuppressWarnings("unchecked")
public T get(Serializable id) throws InvalidRequestException {
Session session = getSessionHandler().getSession();
Transaction tx = session.beginTransaction();
tx.commit();
try {
Object obj = session.get(_classtype, id);
if (obj == null) {
throw new InvalidRequestException(String.format("requested object with id %s does not exist.", id));
} else {
return (T)obj;
}
} catch(HibernateException ex) {
ex.printStackTrace();
if (tx!=null) tx.rollback();
throw ex;
} finally {
getSessionHandler().close();
}
}
When I create an object that returns me id = 4, and if immediately I make a request on browser that eventually ask for the new object of id 4, I have to wait for a few seconds (last time I tried is > 3 seconds).
When the id is returned from the create, the data should already exist. However the get returns null. I highly suspect the get is using the old cache which then is updated every a few seconds, but I have no idea how to fix it.
Let me know if any info is required and I am happy to provide them.
In my project, I just generate the pojo and dao classes with the hibernate. But the dao classes generated by Hibernate are all in this style:
package com.ligadesportiva.data;
// Generated 14/03/2014 22:39:34 by Hibernate Tools 3.4.0.CR1
import java.util.List;
import javax.naming.InitialContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Example;
import org.ligadesportiva.core.Jogador;
/**
* Home object for domain model class Jogador.
* #see com.ligadesportiva.data.Jogador
* #author Hibernate Tools
*/
public class JogadorHome {
private static final Log log = LogFactory.getLog(JogadorHome.class);
private final SessionFactory sessionFactory = getSessionFactory();
protected SessionFactory getSessionFactory() {
try {
return (SessionFactory) new InitialContext()
.lookup("SessionFactory");
} catch (Exception e) {
log.error("Could not locate SessionFactory in JNDI", e);
throw new IllegalStateException(
"Could not locate SessionFactory in JNDI");
}
}
public void persist(Jogador transientInstance) {
log.debug("persisting Jogador instance");
try {
sessionFactory.getCurrentSession().persist(transientInstance);
log.debug("persist successful");
} catch (RuntimeException re) {
log.error("persist failed", re);
throw re;
}
}
public void attachDirty(Jogador instance) {
log.debug("attaching dirty Jogador instance");
try {
sessionFactory.getCurrentSession().saveOrUpdate(instance);
log.debug("attach successful");
} catch (RuntimeException re) {
log.error("attach failed", re);
throw re;
}
}
public void attachClean(Jogador instance) {
log.debug("attaching clean Jogador instance");
try {
sessionFactory.getCurrentSession().lock(instance, LockMode.NONE);
log.debug("attach successful");
} catch (RuntimeException re) {
log.error("attach failed", re);
throw re;
}
}
public void delete(Jogador persistentInstance) {
log.debug("deleting Jogador instance");
try {
sessionFactory.getCurrentSession().delete(persistentInstance);
log.debug("delete successful");
} catch (RuntimeException re) {
log.error("delete failed", re);
throw re;
}
}
public Jogador merge(Jogador detachedInstance) {
log.debug("merging Jogador instance");
try {
Jogador result = (Jogador) sessionFactory.getCurrentSession()
.merge(detachedInstance);
log.debug("merge successful");
return result;
} catch (RuntimeException re) {
log.error("merge failed", re);
throw re;
}
}
public Jogador findById(int id) {
log.debug("getting Jogador instance with id: " + id);
try {
Jogador instance = (Jogador) sessionFactory.getCurrentSession()
.get("com.ligadesportiva.data.Jogador", id);
if (instance == null) {
log.debug("get successful, no instance found");
} else {
log.debug("get successful, instance found");
}
return instance;
} catch (RuntimeException re) {
log.error("get failed", re);
throw re;
}
}
public List findByExample(Jogador instance) {
log.debug("finding Jogador instance by example");
try {
List results = sessionFactory.getCurrentSession()
.createCriteria("com.ligadesportiva.data.Jogador")
.add(Example.create(instance)).list();
log.debug("find by example successful, result size: "
+ results.size());
return results;
} catch (RuntimeException re) {
log.error("find by example failed", re);
throw re;
}
}
}
But I want data be saved/read from a postgresql database. Is there any code I should add to this project for make this dao classes interact with my DB?
Just for the record, I am put here some considerations about this subject I learn there few days:
First all, besides the two sets of classes generated by Hibernate (DAO and POJO), I create one anotherm based in this example:
http://www.baeldung.com/hibernate-4-spring
where I configure some options and methods of the Hibernate and point to the file where I place the options for conect to the database (database.properties).
After, I made some changes in the generated classes:
1) In the POJO class, I added the annotation #Entity and #Table for the class (and the second annotation with de parameter value="?", where ? is the name of the table associated to this class). For the attributes, I add the annotation #Column for all of them and the annotation #Id for the attribute related to the primary key.
2) In the DAO classes, I added the annotation #Repository for the class, and the annotation #Transactional for all the methods. Because of this annotation, I may insert in my *-servlet.xml file the follow line:
<tx:annotation-driven transaction-manager="transactionManager"/>
and in the header, this options:
xmlns:tx="http://www.springframework.org/schema/tx"
and inside xsi:schemaLocation:
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd.
Also, the atribute sessionFactory was annotated with #Autowired, the association was removed, and this methos was added:
protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
For use of this class to handle the queries to database, I add them as atributes of the controller, always informing the annotation #Autowired.
Inside each method of the controller, I use the classes as this examples:
read data
Usuario novo = usuario.findById(this.getId_usuario());
save data
Sessao nova = new Sessao(novo, 0);
sessao.persist(nova);
I have an application in android which uses sqlite to persist the data.
is it correct if you invoke a method that starts a transaction from other method that starts another transaction like the example? what happend with the two transactions?
#Override
public void saveSurvApplied(MSurvey surv, MSApplied sa) {
try {
db.beginTransaction();
SurveyManager surveyManager = ManagerFactory.getSurveyManager(ctx);
surveyManager.saveSurveyState(surv); // it begins a transaction
saDao.save(sa);
db.setTransactionSuccessful();
} catch (SQLException ex) {
Log.e(tag, "Error insertando las encuestas aplicadas", ex);
} finally {
db.endTransaction();
}
}
...
SurveyManagerImpl.java
#Override
public void saveSurveyState(MSurvey survey) {
try {
db.beginTransaction();
for (MNode node : survey.getNodes()) {
nodeDao.update(node);
MItem[] items = node.getItems();
if (items != null) {
for (MItem item : items) {
itemDao.update(item);
}
}
}
surveyDao.update(survey);
db.setTransactionSuccessful();
} catch (SQLException ex) {
Log.e(tag, "Error actualizando la encuesta", ex);
} finally {
db.endTransaction();
}
}
could I provide some reference to learn what happens when you invoke nested transactions within sqlite?
Thank you.
The documentation says:
Transactions can be nested. When the outer transaction is ended all of the work done in that transaction and all of the nested transactions will be committed or rolled back.