I've been googling a bit and I'm running out of time so I'm throwing this out here in the hopes that someone just knows the answer. I've got a system where I have spring transaction boundaries at the Service layer. Below that lies a dao layer. I've got bean validation on my model objects and I've wrapped the DAO's in a compile time aspectj Around aspect like this:
#Aspect
public class ValidationCollectorAspect {
#Around("daoMethods()")
public Object collectDaoMessages(ProceedingJoinPoint thisJoinPoint) throws Throwable {
try {
return thisJoinPoint.proceed();
} catch (ConstraintViolationException e) {
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
List<UserMessage> userMessages = ThreadContext.MESSAGES.get();
for (ConstraintViolation<?> constraintViolation : constraintViolations) {
userMessages.add(new UserMessage(constraintViolation.getMessage(), MessageType.VALIDATION));
}
throw new MyPersistenceException("Validation failures", e);
}
}
#Pointcut("call(public * *(..)) &&!call(* getEntityManager()) && within(com.myclient.dao.impl..*)")
public void daoMethods() {
}
}
The problem is the validation appears to happen when the transaction is committed, not prior to the save or update operation in the DAO. This means that the ConstraintViolationException from bean validation is not thrown until AFTER the service method returns and way after this join point. My evidence for this is that the stack trace does not contain any of the dao service methods. The first method for code I wrote is shown by
at com.myclient.servlet.rest.Rest.updateObjects(Rest.java:323)
But that's a method on a servlet name Rest, and the whole point is to NOT need to create join points for a whole bunch of specific methods on the various servlets in the system, and also to be able to process the Constraint violation before it's wrapped in arbitrary layers of spring exceptions.
I understand that sometimes it might be cool to validate the sum total of all the hibernate changes, just before commit, but that's not what I want. (although as second round of validation it would not be unwelcome) How can I tell hibernate validator to process the validation when I call the hibernate save or update methods in the dao, not when the transaction commits?
Here's the versions of stuff from my build:
compile 'org.hibernate:hibernate-entitymanager:4.2.2.Final'
compile 'org.hibernate:hibernate-validator:5.0.1.Final'
compile 'org.hibernate:hibernate-c3p0:4.2.2.Final'
compile 'org.springframework:spring-orm:3.2.3.RELEASE'
compile 'org.springframework:spring-context:3.2.3.RELEASE'
compile 'org.springframework:spring-web:3.2.3.RELEASE'
compile 'javax.inject:javax.inject:1'
compile 'org.aspectj:aspectjrt:1.7.3'
ajc "org.aspectj:aspectjtools:1.7.3"
Edit: One further note... I'm doing all this under JPA, so I'd prefer a non-hibernate specific solution if one exists.
How can I tell hibernate validator to process the validation when I call the hibernate save or update methods in the dao, not when the transaction commits?
Implement a hibernate entity listeners and register the event listener for every event type when you want to do the validation.
Take a look at this question on how to configure event listeners with hibernate 4.
(Stack Overflow says this is too long to post as a comment, so posting it as an answer instead.)
I thought a bit about what you're trying to do, and can't figure out what the motivation is. So now I'm curious.
If you're receiving the object from some other piece of code and want it to be #Valid, you should be validating it before doing anything with it; not when calling save() or update(). You can either directly pass it to a validator or use the new method constraint support in Hibernate Validator 5 which will give you method validation using proxies.
If you're constructing the object yourself and are trying to catch code errors when you persist, that's a code smell. Why don't you have constructors or builders that prevent the construction of an invalid entity in the first place? And if you do have them but just want the double check when you persist, what difference would it make if the failures happen at save() time or at transaction commit, since the whole thing should roll back anyway?
You should also keep in mind that Hibernate does transactional write-behind and saves are deferred in the hope that they can be merged together to reduce chatter with the database. So if you want validation as a side-effect of saving, you should also expect that validation to be deferred or you're messing with semantics.
Anyway, I'd try HV5 method constraints at the DAO level and skip the custom validator as a first approach.
I'm not 100% sure I like this, but here's how I solved it. Basically I stopped trying to find a hibernate solution and added a couple of extra methods...
In my service layer:
#Override
public void save(T r) {
// This allows us to post a join point just outside the transaction boundary
save0(r);
}
#Transactional
private void save0(T r) {
getDao().save(r);
}
#Override
public T update(T obj) throws IllegalArgumentException {
// This allows us to post a join point just outside the transaction boundary
return update0(obj);
}
#Transactional
private T update0(T obj) {
return getDao().update(obj);
}
In my aspectJ pointcut
#Around("daoMethods()")
public Object collectDaoMessages(ProceedingJoinPoint thisJoinPoint) throws Throwable {
try {
return thisJoinPoint.proceed();
} catch (Exception e) {
ConstraintViolationException cve;
Throwable cause = e;
while (cause != null && !(cause instanceof ConstraintViolationException)) {
cause = cause.getCause();
}
if (cause == null) {
throw e;
} else {
cve = (ConstraintViolationException) cause;
}
Set<ConstraintViolation<?>> constraintViolations = cve.getConstraintViolations();
List<UserMessage> userMessages = ThreadContext.MESSAGES.get();
for (ConstraintViolation<?> constraintViolation : constraintViolations) {
userMessages.add(new UserMessage(constraintViolation.getMessage(), MessageType.VALIDATION));
}
throw new MyPersistenceException("Validation failures", e);
}
}
#Pointcut(
"execution(public * *(..)) " +
"&& !execution(public * load*(..)) " +
"&& within(com.myclient.service.impl..*) "
)
public void daoMethods() {}
Related
Need some help here, I'm not able to understand why my transactions are not getting rolled back in an event of exception.
I will try to put my code as close to as It is on the project (cannot share on the internet)
This is my Service
#Service
#SL4j
#Transactional(propagation = propagation.SUPPORTS, readOnly=true)
public class PublicationServiceImpl implements PublicationService{
#Autowired
private PartnerRepo partnerRepo;
#Autowired
private FlowRepo flowRepo;
#Autowired
private PubRepo pubRepo;
#Override
#Transactional(propagation = propagation.REQUIRED, rollbackFor=Exception.class)
public int save(Request request) {
try{
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode= pubRepo.save(pk_id_partner, pk_id_flow, request);
}
catch(Exception e){
log.error("Exception in saving");
}
return 0;
}
}
This is my Repository (example of 1 , all 3 repos follow same coding standards)
#Repository
#Slf4j
public class PartnerRepo implemets PartnerRepo{
#Autowired
private NamedParamaterJDBCTemplate namedParamaterJDBCTemplate;
//String Declarations .....
private MapSqlParameterSource sqlParameterSource;
#Override
public int save(Request request){
sqlParamatersSource = new MapSqlParameterSource();
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
return executeQuery();
}
private int executeQuery(){
try{
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource , kekHolder, new String[]{"pk_id"})
return keyHolder.getKey().intValue();
}catch(Exception e){
log.error("Exception while saving");
return 0;
}
}
}
So the problem is , Consider there is an exception in the method generatePubCode(request); , ideally since I have used #Transactional at class level and method level , The previous 2 repo transactions () should be rolled back right? However it isn't happening, Even After the code is finished execution I can see the records in DB (Postgres DB v10).
Please help figure out this issue , Am I doing something fundamentally wrong ?
Please do let me know in case you need further information that might help here!
P.S: I have tried all permutations of #Transactional , nothing works : ONLY having this in the catch block works! TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
I wonder If its the right approach for a springBoot project
Thanks in advance for the help!
Edit: as per suggestion made the PublicationServiceSaverImpl.save() public
Best reagards,
Bhargav.
There are several things that break proper transactions in Spring
Your service method is private
You are catching and swallowing exceptions
private method
The fact that your PublicationServiceImpl save method is private basically makes the #Transactional on that method useless. As a private method cannot be proxied, no transactions will apply. Even if it would be public it wouldn't work as you are calling the method from within the same object, hence the transactionality of that method applies.
To fix, make your method public and call the save method from an other class (or make the actual method that is calling save have the proper #Transactional.
The fact that is doesn't work is due to the type op AOP being used, by default Spring will use proxies and this is a drawback of using proxy based AOP.
Another solution to make it work with private methods is to switch to full-blown AspectJ with either compile-time or load-time weaving of the classes. Both require additional setup and that can be tedious.
Catch and swallow exceptions
You have in both your repository as well as your service a try/catch block. Each of those catches and swallows the exceptions (they are logged but not re-thrown).
For transactions to work properly it needs to see the exceptions. The fact that you are catching and swallowing them, makes the transaction aspect not see them and instead of doing a rollback, do a commit. For the transaction aspect everything is ok because there was no exception.
To fix, remove either the try/catch or rethrow the exceptions.
Annotations in general never work on methods called from the same class because of how proxies are created in Spring.
It has nothing to do with #Transaction in particular but with the fact that your methods is private and called from the within same object.
Please make the method public and move the #Transactional method in a separate class annoted with #Service and called it from outside of the instance of the class
#Service
public class PublicationServiceSaverImpl {
#Transactional
**public** int save(Request request) {
...
}
}
You must call the save method from outside of the class PublicationServiceSaverImpl, maybe from PublicationServiceImpl.
The method PublicationServiceImpl.save must be public if you want to use #Transactional.
As per Spring Documentation:
When you use transactional proxies with Spring’s standard configuration, you should apply the #Transactional annotation only to methods with public visibility. If you do annotate protected, private, or package-visible methods with the #Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.
First of all: make your method public.
Second: you have to throw the exception. If you catch and not rethrow it, how do you expect the transactional processing to know that an error occured and then rollback?
You have two options: throw the Exception instead of catching it, or catch, do some further processing and then rethrow it.
So in your repository, just add a throws keyword and then rethrow the exception after the log statement:
public int executeQuery() throws Exception {
try {
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource, kekHolder, new String[] {
"pk_id"
})
return keyHolder.getKey().intValue();
} catch(Exception e) {
log.error("Exception while saving");
throw e;
}
}
Now, for your service:
Example 1 - use the throws keyword to propagate the checked exception:
#Override
#Transactional(propagation = propagation.REQUIRED, rollbackFor = Exception.class)
public int save(Request request) throws Exception {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
return 0;
}
Example 2 - catch and rethrow it as an RuntimeException, which is unchecked.
#Override
#Transactional(propagation = propagation.REQUIRED)
public int save(Request request) {
try {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
return 0;
}
Note that the second example doesn't need the rollbackFor argument to the #Transactional. By default, a transaction is rolled back if a unchecked exception occurs, so there's no need to explicitly use rollbackFor in cases of RuntimeExceptions.
Another verification that would have to be done if the solution does not work. It is to verify that the database tables allow the rollback. For this, the engine has to be in InnoDB and not in MyISAM and others.
In my case adding #EnableTransactionManagement annotation on Application class resolved the issue
In stead of #Transactional(propagation = propagation.REQUIRED provide #Transactional(propagation = propagation.REQUIRED_NEW
If you use the latter, it will use the parent transaction boundary, which is at class level.
And you don't need explicitly state rollbackFor=Exception.class. By default spring will roll back on exception
And do change private to public
Try this out
I have two methods in the JPA repository. Both the methods have propagation level as REQUIRED
The methods are used to persist entity objects using Hibernate to Postgresql
#Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
Employee employee = new Employee("Peter", "Washington DC");
entityManager.persist(employee);
try {
persistLineManager();
}
catch( Exception e ) {
// some task
}
}
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
public void persistLineManager() {
Employee lineManager = new Employee("John", "NYC");
entityManager.persist(lineManager);
if(lineManager != null) // intentionally! To trigger rollback
throw new RuntimeException("Rollback!");
}
As per Spring docs when propagation level is REQUIRED both methods will run in the same transaction. In my code, I am intentionally throwing the Exception to trigger the rollback but still, both the entities are getting persisted. But I believe both the operations should be rollbacked. Please correct if my understanding is incorrect and let me know the correct way to
rollback both the operations.
PROPAGATION_REQUIRES_NEW: [ from spring Docs ]
PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions are different and hence can commit or roll back independently, with an outer transaction not affected by an inner transaction’s rollback status.
PROXYFICATION
In your service, you created 2 methods, both #Transactional. When you create your bean, spring will create a proxy to add for you at runtime the behavior for a Transactional method. Let's dive deeper:
This proxyfication is illustrated by the image. Any caller form the outside world will not directly talk to you, but to your proxy. And then, the proxy will call you to execute the code of your service.
Now, this "Any caller form the outside world will not directly talk to you" is very important. If you make an inner call, like you do in persistEmployee which is calling persistLineManager, then you do not pass through the proxy. You call directly your method, NO PROXY. Therefor, the annotations at the top of your persistLineManager method are not read.
So, when persistLineManager is throwing a RuntimeException, the exception is catched directly by your caller persistEmployee, you go directly in your catch. As there is no proxy, there is no rollback because the transactional proxy did not catch the exception.
If you do only this, you will have a rollback occurring:
#Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
Employee employee = new Employee("Peter", "Washington DC");
entityManager.persist(employee);
persistLineManager();
// Don't catch and the exception will be catched by the transaction proxy, which will rollback
}
public void persistLineManager() {
Employee lineManager = new Employee("John", "NYC");
entityManager.persist(lineManager);
if(lineManager != null) // intentionally! To trigger rollback
throw new RuntimeException("Rollback!");
}
By default, #Transactional rollback for a RuntimeException
TRANSACTION TEMPLATE
Suppose you still want both method to be transactional independently, what you can do is using the TransactionTemplate. Here is an example:
class MyService {
// Have a field of type TransactionTemplate
private TransactionTemplate template;
// In the constructor, Spring will inject the correct bean
public MyService(PlatformTransactionManager transactionManager) {
template = new TransactionTemplate(transactionManager);
// Set this here if you always want this behaviour for your programmatic transaction
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
// Here you start your first transaction when arriving from the outside
#Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
Employee employee = new Employee("Peter", "Washington DC");
entityManager.persist(employee);
// Inner call
try {
persistLineManager();
} catch (RuntimeException e) {
// Do what you want
}
}
public void persistLineManager() {
// Here, ask to your transactionTemplate to execute your code.
template.execute(status -> {
Employee lineManager = new Employee("John", "NYC");
entityManager.persist(lineManager);
if(lineManager != null) // intentionally! To trigger rollback
throw new RuntimeException("Rollback!");
return null;
});
}
}
I haven't tested everything, you might face some errors, but I hope you get the idea.
PROPAGATION
Let me add a last part about the difference between PROPAGATION_REQUIRED and PROPAGATION_REQUIRES_NEW:
PROPAGATION_REQUIRED:
Either I have no transaction, then I create one
Or There is a running transaction, and I join it.
PROPAGATION_REQUIRES:
In any situations, whether a transaction is running or not, I create a new one.
Example:
A client is entering in my transactional method, with PROPAGATION_REQUIRED. It creates a transaction name "TA".
This transactional method calls a method, which is also transactional, but PROPAGATION_REQUIRES_NEW. It creates a second transaction named "TB"
With have now: "client" -> "TA" -> "TB"
But the second method triggers a rollback. In that case, only "TB" will be rolledback, as "TA" and "TB" are 2 differents transactions.
So, in DB, I will persist every operation that has been made in "TA", but not in "TB".
Hope it helps
I have 2 services: RecentRecordService and BookService.
#Service
public class RecentRecordService {
#Transactional
public List<Book> getRecentReadBooks() {
List<Long> recentReadBookIds = getRecentReadBookIds();
List<Book> recentReadBooks = new ArrayList<>();
for (Long bookId : recentReadBookIds) {
try {
Book book = bookService.getBook(bookId);
recentReadBooks.add(book);
} catch (AccessDeniedException e) {
// skip
}
}
return recentReadBooks;
}
}
#Service
public class BookService {
#Transactional
public Book getBook(Long bookId) {
Book book = bookDao.get(bookId);
if (!hasReadPermission(book)) {
throw new AccessDeniedException(); // spring-security exception
}
return book;
}
}
Assume that getRecentReadBookIds() returns [1, 2, 3].
When the session user has permission for all the book IDs that returned from getRecentReadBookIds(), everything works fine. getRecentReadBooks() will return a list of 3 Books.
Suddenly, the owner of book #2 changed the permission setting of it from "Public" to "Private". Therefore, the session user can no longer read the book #2.
I was expecting that getRecentReadBooks() will return a list of 2 Books, which contains the info of book #1 and book #3. However, the method failed with the following exception:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
After some research, I found that it has something to do with the transaction propagation. The transation will be marked as "rollback-only" even if I use try-catch to handle the AccessDeniedException in getRecentReadBooks().
This problem seems to be solved by changing the #Transactional annotation for getBook() into:
#Transactional(propagation = Propagation.NESTED)
public Book getBook(Long bookId) {
// ...
}
or
#Transactional(noRollbackFor = AccessDeniedException.class)
public Book getBook(Long bookId) {
// ...
}
However, I was wondering if I can solve the problem by only modifying RecentRecordService. After all, RecentRecordService is the one who wants to handle AccessDeniedException on its own.
Any suggestion will help. Thanks.
I don't think it is possible to solve it by only modifying the RecentRecordService because the transaction is already marked as rollback in the BookService due to a RuntimeException happens in it. And once the TransactionStatus is marked as rollback , there is no way to revert it to not rollback currently.
So , you have to make BookService#getBook() does not mark the transaction as rollback by :
#Transactional(noRollbackFor = Throwable.class)
public Book getBook(Long bookId) {
}
which means the transaction will not marked as rollback no matter what exception happen. It makes sense to do it as this method is supposed to be read-only , so it is no point to rollback when a method is not supposed to modify anythings.
Quote from the documentation
By default, a transaction will be rolling back on RuntimeException and
Error but not on checked exceptions (business exceptions)
AccessDeniedException is a subclass of RuntimeException
If in your case the AccessDeniedException used is the spring frameworks , and as per the requirement if the exception on access denied can be made a custom business exception (not a subclass of Runtime Exception ) , it should work without annotating getBook(Long bookId) method.
Update
OP decided to fix the root cause . This is to provide the rationale behind my answer for any future references
AccessDeniedException : Thrown if an Authentication object does not
hold a required authority.
As per my understanding , here the access to a book is based on business logic and not user role specific. A custom business exception would be apt in such a scenario.
I have a system based on hibernate 4. I have an unique constraint in a table and need to handle it the following way:
try{
getMyService().create(myobj);
}catch(PersistenceException p){
//constraint fails
myobj.setConstraintColumn("new non unique value");
getMyService().create(myobj);//should save it
}
unfortunately I can't change the design of the system so I need just to figure it out this way.
EDIT
I get the following exception:
org.hibernate.AssertionFailure: null id in entry (don't flush the Session after an exception occurs)
The code of create method:
public E create(E entity) {
entityManager.persist(entity);
entityManager.flush();
entityManager.refresh(entity);
return entity;
}
It is not clear where your transaction boundaries are.
When the exception is thrown, you will need to:
1) Ensure that the first transaction is closed (it should be, but not sure - see if you get a nested transaction trying #2 alone)
2) begin a new transaction before you are able to persist/flush again (and subsequently commit that).
I finally figured out the issue. So, let me explain one by one.
First of all, take a look at
Propagation.REQUIRES_NEW does not create a new transaction in Spring with JPA
Then, for example we have a class and method like:
public class MyClass{
#Transactional
public void myMethod(){
....
}
}
So, first of all lets consider that myMethod is in it's own transaction completely, because transactions are AOP based, and it will be committed when appropriate aspect will fire, but only after method completes, throws exception, etc. So we can't partially commit, partially rollback, rollback incompletely,etc. So, we need to do the following:
Start big outer transaction
Start a new nested transaction, try to insert a record.
If nested transaction will fail, it will be rolled back, but the outer one will still be running.
If the first nested transaction failed, then start a new nested transaction and insert a record with new data, which will prevent ConstaintViolationException from being thrown.
So, in this case, we create a class:
public class ServiceHelper{
#Transational(proparation = **Propagation.REQUIRED_NEW**)
public void tryConstraint throws MyConstraintException{
try{
//insert
}catch(ConstraintViolationException e){
throw new MyConstraintException(e);
}catch(Exception ex){
throw new Exception(ex);
}
}
#Transational(proparation = **Propagation.REQUIRED_NEW**)
public void insertWithNoConflict throws Exception {
//Set new data
//insert, if still CVE or anything other, just throw it , or leave it for unchecked exceptions then
}
}
And our service:
public class MyService{
#Autowired
private ServiceHelper serviceHelper;
#Transactional(propagation = **Propagation.REGUIRED_NEW**)
public void createWithCheck(){
try{
serviceHelper.tryConstraint();
}catch(MyConstraintException e){
serviceHelper.insertWithNoConflict();
}
}
}
But there is still a weird situation because I need to use MyService methods for records creation in ServiceHelper, but I can't obtain them there because it will cause circular injections, so I have to get them via services factory like:
MyService service = (MyService)ServicesFactory.getInstance().getBean(MyService.BEAN_ID)
And I don't like it. But this approach works, I checked it today.
We should know 2 things: first of all we can't do anything with a transaction inside a method, we can't start there any new transaction, etc. A transaction context is relevant to method completely, it will still be there before a method ends. And the second thing is required_new doesn't work when we launch the method from the method of the same proxy class.
Good day. The following code:
class A{
private B b;
#Transactional
public SomeResult doSomething(){
SomeResult res = null;
try {
// do something
} catch (Exception e) {
res = b.saveResult();
}
return res ;
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
class B{
public SomeResult saveResult(){
// save in db
}
}
As I understand, if there is an exception in the method doSomething the transaction isn't rolled back. And how to make that it rolled? and returned SomeResult
You shouldn't call Rollback programmatically. The best way, as recommended by the docs, is to use declarative approach. To do so, you need to annotate which exceptions will trigger a Rollback.
In your case, something like this
#Transactional(rollbackFor={MyException.class, AnotherException.class})
public SomeResult doSomething(){
...
}
Take a look at the #Transaction API and the docs about rolling back a transaction.
If, despite the docs recommendation, you want to make a programmatic rollback, then you need to call it from TransactionAspectSupport as already suggested. This is from the docs:
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
There may be a architecture mistake though. If your method fails and you need to throw an exception, you shouldn't expect it to return anything. Maybe you're giving too much responsibilities to this method and should create a separated one that only model data, and throws an exception if something goes wrong, rolling back the transaction. Anyway, read the docs.
get TransactionStatus using TransactionAspectSupport.currentTransactionStatus()
ect transaction manager to your bean try to invoke Rollback(DefaultTransactionStatus status) in transaction manager.
refer to spring documentation
You are strongly encouraged to use the declarative approach to
rollback if at all possible. Programmatic rollback is available should
you absolutely need it, but its usage flies in the face of achieving a
clean POJO-based architecture.