Exception Handling with Spring's #Transactional - java

I'm developing a web app with Spring MVC and hibernate for persistence.
Given my DAO where GenericDao has a SessionFactory member attribute:
#Repository
public class Dao extends GenericDao {
public void save(Object o) {
getCurrentSession().save(o);
}
}
And a Service class
#Service
public class MyService {
#Autowired
Dao dao;
#Transactional
public void save(Object o) {
dao.save(o);
}
}
I want to inform my user if a persistence exception occurs (constraint, duplicate, etc). As far as I know, the #Transactional annotation only works if the exception bubbles up and the transaction manager rolls back so I shouldn't be handling the exception in that method. Where and how should I catch an exception that would've happened in the DAO so that I can present it to my user, either directly or wrapped in my own exception?
I want to use spring's transaction support.

Spring provides Exception Handlers.
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-exceptionhandlers
So you could have something like this in your controller to handle a ConstraintViolationException
#ExceptionHandler(ConstraintViolationException.class)
public ModelAndView handleConstraintViolationException(IOException ex, Command command, HttpServletRequest request)
{
return new ModelAndView("ConstraintViolationExceptionView");
}

After chasing around the issue for a while, I solved this by using an exception handler (as described in another answer) and the rollbackFor property of the #Transactional annotation:
#Transactional(rollbackFor = Exception.class)
My exception handler is still called and writes the response accordingly, but the transaction is rolled back.

Related

Roll back A if B goes wrong. spring boot, jdbctemplate

I have a method, 'databaseChanges', which call 2 operations: A, B in iterative way. 'A' first, 'B' last.
'A' & 'B' can be Create, Update Delete functionalities in my persistent storage, Oracle Database 11g.
Let's say,
'A' update a record in table Users, attribute zip, where id = 1.
'B' insert a record in table hobbies.
Scenario: databaseChanges method is been called, 'A' operates and update the record. 'B' operates and try to insert a record, something happen, an exception is been thrown, the exception is bubbling to the databaseChanges method.
Expected: 'A' and 'B' didn't change nothing. the update which 'A' did, will be rollback. 'B' didn't changed nothing, well... there was an exception.
Actual: 'A' update seems to not been rolled back. 'B' didn't changed nothing, well... there was an exception.
Some Code
If i had the connection, i would do something like:
private void databaseChanges(Connection conn) {
try {
conn.setAutoCommit(false);
A(); //update.
B(); //insert
conn.commit();
} catch (Exception e) {
try {
conn.rollback();
} catch (Exception ei) {
//logs...
}
} finally {
conn.setAutoCommit(true);
}
}
The problem: I don't have the connection (see the Tags that post with the question)
I tried to:
#Service
public class SomeService implements ISomeService {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
private NamedParameterJdbcTemplate npjt;
#Transactional
private void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
}
My AppConfig class:
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
#Configuration
public class AppConfig {
#Autowired
private DataSource dataSource;
#Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(dataSource);
}
}
'A' makes the update. from 'B' an exception is been thrown. The update which been made by 'A' is not been rolled back.
From what i read, i understand that i'm not using the #Transactional correctly.
I read and tried several blogs posts and stackverflow Q & A without succeess to solve my problem.
Any suggestions?
EDIT
There is a method that call databaseChanges() method
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
Which method should be annotated with #Transactional,
changes()? databaseChanges()?
#Transactional annotation in spring works by wrapping your object in a proxy which in turn wraps methods annotated with #Transactional in a transaction. Because of that annotation will not work on private methods (as in your example) because private methods can't be inherited => they can't be wrapped (this is not true if you use declarative transactions with aspectj, then proxy-related caveats below don't apply).
Here is basic explanation of how #Transactional spring magic works.
You wrote:
class A {
#Transactional
public void method() {
}
}
But this is what you actually get when you inject a bean:
class ProxiedA extends A {
private final A a;
public ProxiedA(A a) {
this.a = a;
}
#Override
public void method() {
try {
// open transaction ...
a.method();
// commit transaction
} catch (RuntimeException e) {
// rollback transaction
} catch (Exception e) {
// commit transaction
}
}
}
This has limitations. They don't work with #PostConstruct methods because they are called before object is proxied. And even if you configured all correctly, transactions are only rolled back on unchecked exceptions by default. Use #Transactional(rollbackFor={CustomCheckedException.class}) if you need rollback on some checked exception.
Another frequently encountered caveat I know:
#Transactional method will only work if you call it "from outside", in following example b() will not be wrapped in transaction:
class X {
public void a() {
b();
}
#Transactional
public void b() {
}
}
It is also because #Transactional works by proxying your object. In example above a() will call X.b() not a enhanced "spring proxy" method b() so there will be no transaction. As a workaround you have to call b() from another bean.
When you encountered any of these caveats and can't use a suggested workaround (make method non-private or call b() from another bean) you can use TransactionTemplate instead of declarative transactions:
public class A {
#Autowired
TransactionTemplate transactionTemplate;
public void method() {
transactionTemplate.execute(status -> {
A();
B();
return null;
});
}
...
}
Update
Answering to OP updated question using info above.
Which method should be annotated with #Transactional:
changes()? databaseChanges()?
#Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
Make sure changes() is called "from outside" of a bean, not from class itself and after context was instantiated (e.g. this is not afterPropertiesSet() or #PostConstruct annotated method). Understand that spring rollbacks transaction only for unchecked exceptions by default (try to be more specific in rollbackFor checked exceptions list).
Any RuntimeException triggers rollback, and any checked Exception does not.
This is common behavior across all Spring transaction APIs. By default, if a RuntimeException is thrown from within the transactional code, the transaction will be rolled back. If a checked exception (i.e. not a RuntimeException) is thrown, then the transaction will not be rolled back.
It depends on which exception you are getting inside databaseChanges function.
So in order to catch all exceptions all you need to do is to add rollbackFor = Exception.class
The change supposed to be on the service class, the code will be like that:
#Service
public class SomeService implements ISomeService {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
private NamedParameterJdbcTemplate npjt;
#Transactional(rollbackFor = Exception.class)
private void databaseChanges() throws Exception {
A(); //update
B(); //insert
}
}
In addition you can do something nice with it so not all the time you will have to write rollbackFor = Exception.class. You can achieve that by writing your own custom annotation:
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Transactional(rollbackFor = Exception.class)
#Documented
public #interface CustomTransactional {
}
The final code will be like that:
#Service
public class SomeService implements ISomeService {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
private NamedParameterJdbcTemplate npjt;
#CustomTransactional
private void databaseChanges() throws Exception {
A(); //update
B(); //insert
}
}
The first code you present is for UserTransactions, i.e. the application has to do the transaction management. Usually you want the container to take care of that and use the #Transactional annotation. I think the problem in you case might be, that you have the annotation on a private method. I'd move the annotation to the class level
#Transactional
public class MyFacade {
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
Then it should rollback properly. You can find more details here
Does Spring #Transactional attribute work on a private method?
Try this:
#TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {
#TransactionAttribute(TransactionAttribute.REQUIRES_NEW)
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
What you seem to be missing is a TransactionManager. The purpose of the TransactionManager is to be able to manage database transactions. There are 2 types of transactions, programmatic and declarative. What you are describing is a need for a declarative transaction via annotations.
So what you need to be in place for your project is the following:
Spring Transactions Dependency (Using Gradle as example)
compile("org.springframework:spring-tx")
Define a Transaction Manager in Spring Boot Configuration
Something like this
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
return new DataSourceTransactionManager(dataSource);
}
You would also need to add the #EnableTransactionManagement annotation (not sure if this is for free in newer versions of spring boot.
#EnableTransactionManagement
public class AppConfig {
...
}
Add #Transactional
Here you would add the #Transactional annotation for the method that you want to participate in the transaction
#Transactional
public void book(String... persons) {
for (String person : persons) {
log.info("Booking " + person + " in a seat...");
jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
}
};
Note that this method should be public and not private. You may want to consider putting #Transactional on the public method calling databaseChanges().
There are also advanced topics about where #Transactional should go and how it behaves, so better to get something working first and then explore this area a bit later:)
After all these are in place (dependency + transactionManager configuration + annotation), then transactions should work accordingly.
References
Spring Reference Documentation on Transactions
Spring Guide for Transactions using Spring Boot - This has sample code that you can play with

Spring #Transactional not rollbacking a bean attribute

Modification of bean attribute not rollbacked with spring #Transactional
TestController.java
#RestController
public class TestController {
#Autowired
private TestService testService;
#Autowired
private TestBean testBean;
#RequestMapping(value = "/test")
public String testTransaction() {
try{
testService.testTransaction();
}catch(Exception e){
System.out.println("After exception: " + testBean.getAttribute());
}
return "test"
}
}
TestService.java
#Service
public class TestService {
#Autowired
private TestBean testBean;
#Transactional(rollbackFor = Exception.class)
public void testTransaction() {
testBean.increment();
System.out.println("Before exception: " + testBean.getAttribute());
throw new UnexpectedRollbackException("unexpected exception");
}
}
TestBean.java
#Component
public class TestBean {
private int attribute = 0;
public TestBean() {
}
public void increment () {
attribute++;
}
public int getAttribute() {
return attribute;
}
}
Console Log
Before exception: 1
After exception: 1
I m wondering why the attribute value is not rollbacked to 0 (its initial value).
You cannot test the #Transactional annotation with this way, you need to have database connection. The rollback process is on the database, not on the objects or beans(if there is, i don't know). Firstly get the database connection, after that, type the some code that is deal with your database in your Service like obj.save() or obj.update(other_obj), also throw the Exception in this method. In Controller, invoke your that service method and control your own database whether the data is saved or not.
Spring use the rollback mechanism for RunTimeException or its sub-classes.
you can find some my spring training here.
#Transactional manages database transactions. Typically you have a data source bean. This bean manages connections to your database. On top of this bean you have a transaction manager. It is another bean which together with #Transactional annotation checks your methods which are supposed to run within transactions. If your method ends with an exception the transaction manager will do rollback of underlying database transaction.
You can find more details in the documentation here

Runtime exception is #ApplicationException(rollback=false) but transaction is finally rolled back

My runtime exception
#ApplicationException(rollback=false)
public class UncheckedException extends RuntimeException {}
My EJB code
#Stateless
#Transactional(TxType.REQUIRES_NEW)
public class ContainerManagedTransactionBean {
#PersistenceContext EntityManager em;
public void insertAndThrowUnchecked() throws UncheckedException {
em.persist(new Entry());
throw new UncheckedException();
}
}
My another EJB is client
#Singleton
#Startup
#Transactional(TxType.NOT_SUPPORTED)
public class Start {
#EJB
ContainerManagedTransactionBean bean;
#PostConstruct
public void start() {
//...
try {
bean.insertAndThrowUnchecked();
} catch (Exception e) {
System.out.println("Start unchecked exception catched");
}
}
Could someone explain me why insertAndThrowUnchecked is rolled back?
In similar case, when exception is checked,
#ApplicationException(rollback=false)
public class CheckedException extends Exception {}
transaction is committed.
Working example is at this GitHub link.
I will appreciate clear explanation and link to proper part of EJB specification
Per section 7.1 of the EJB 3.2 specification:
It is illegal to associate JTA transactional interceptors (see [8])
with Enterprise JavaBeans. The EJB Container should fail deployment of
such applications.[39]
[39] This restriction may be removed in a future release of this specification.
Since the #Transactional annotation is incorrectly being used to specify a JTA transaction interceptor on an EJB , the #ApplicationException annotation has no effect. Try using the #TransactionAttribute annotation instead.

Spring boot - taking control of 404 Not Found

I'm trying to figure out the simplest way to take control over the 404 Not Found handler of a basic Spring Boot RESTful service such as the example provided by Spring:
https://spring.io/guides/gs/rest-service/
Rather than have it return the default Json output:
{
"timestamp":1432047177086,
"status":404,
"error":"Not Found",
"exception":"org.springframework.web.servlet.NoHandlerFoundException",
"message":"No handler found for GET /aaa, ..."
}
I'd like to provide my own Json output.
By taking control of the DispatcherServlet and using DispatcherServlet#setThrowExceptionIfNoHandlerFound(true), I was able to make it throw an exception in case of a 404 but I can't handle that exception through a #ExceptionHandler, like I would for a MissingServletRequestParameterException. Any idea why?
Or is there a better approach than having a NoHandlerFoundException thrown and handled?
It works perfectly Fine.
When you are using SpringBoot, it does not handle (404 Not Found) explicitly; it uses WebMvc error response. If your Spring Boot should handle that exception, then you should do some hack around Spring Boot. For 404, the exception class is NoHandlerFoundException; if you want to handle that exception in your #RestControllerAdvice class, you must add #EnableWebMvc annotation in your Application class and set setThrowExceptionIfNoHandlerFound(true); in DispatcherServlet. Please refer to the following code:
#SpringBootApplication
#EnableWebMvc
public class Application {
#Autowired
private DispatcherServlet servlet;
public static void main(String[] args) throws FileNotFoundException, IOException {
SpringApplication.run(Application.class, args);
}
#Bean
public CommandLineRunner getCommandLineRunner(ApplicationContext context) {
servlet.setThrowExceptionIfNoHandlerFound(true);
return args -> {};
}
}
After this you can handle NoHandlerException in your #RestControllerAdvice class
#RestControllerAdvice
public class AppException {
#ExceptionHandler(value={NoHandlerFoundException.class})
#ResponseStatus(code=HttpStatus.BAD_REQUEST)
public ApiError badRequest(Exception e, HttpServletRequest request, HttpServletResponse response) {
e.printStackTrace();
return new ApiError(400, HttpStatus.BAD_REQUEST.getReasonPhrase());
}
}
I have created ApiError class to return customized error response
public class ApiError {
private int code;
private String message;
public ApiError(int code, String message) {
this.code = code;
this.message = message;
}
public ApiError() {
}
//getter & setter methods...
}
According to the Spring documentation appendix A. there is a boolean property called spring.mvc.throw-exception-if-no-handler-found which can be used to enable throwing NoHandlerFoundException. Then you can create exception handler like any other.
#RestControllerAdvice
class MyExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(MyExceptionHandler.class);
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(NoHandlerFoundException.class)
public String handleNoHandlerFoundException(NoHandlerFoundException ex) {
log.error("404 situation detected.",ex);
return "Specified path not found on this server";
}
}
#ExceptionHandler itself without #ControllerAdvice (or #RestControllerAdvice) can't be used, because it's bound to its controller only.
In short, the NoHandlerFoundException is thrown from the Container, not from your application within your container. Therefore your Container has no way of knowing about your #ExceptionHandler as that is a Spring feature, not anything from the container.
What you want is a HandlerExceptionResolver. I had the very same issue as you, have a look at my solution over there: How to intercept "global" 404s on embedded Tomcats in spring-boot
The #EnableWebMvc based solution can work, but it might break Spring boot auto configurations.
The solution I am using is to implement ErrorController:
#RestController
#RequiredArgsConstructor
public class MyErrorController implements ErrorController {
private static final String ERROR_PATH = "/error";
#NonNull
private final ErrorAttributes errorAttributes;
#RequestMapping(value = ERROR_PATH)
Map<String, Object> handleError(WebRequest request) {
return errorAttributes.getErrorAttributes(request, false);
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
}
The solution for this problem is:
Configure DispatcherServlet to throw and exception if it doesn't find any handlers.
Provide your implementation for the exception that will be thrown from DispatcherServlet, for this case is the NoHandlerFoundException.
Thus, in order to configure DispatcherServlet you may use properties file or Java code.
Example for properties.yaml,
spring:
mvc:
throw-exception-if-no-handler-found: true
Example for properties.properties,
spring.mvn.throw-exception-if-no-handler-found=true
Example for Java code, we just want to run the command servlet.setThrowExceptionIfNoHandlerFound(true); on startup, I use the InitializingBean interface, you may use another way. I found a very well written guide to run logic on startup in spring from baeldung.
#Component
public class WebConfig implements InitializingBean {
#Autowired
private DispatcherServlet servlet;
#Override
public void afterPropertiesSet() throws Exception {
servlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Be careful! Adding #EnableWebMvc disables autoconfiguration in Spring Boot 2, meaning that if you use the annotation #EnableWebMvc then you should use the Java code example, because the spring.mvc.* properties will not have any effect.
After configuring the DispatcherServlet, you should override the ResponseEntityExceptionHandler which is called when an Exception is thrown. We want to override the action when the NoHandlerFoundException is thrown, like the following example.
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"errormessage\":\"WHATEVER YOU LIKE\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_FOUND, request);
}
}
Finally, adding a break point to method handleException of ResponseEntityExceptionHandler might be helpful for debugging.

Transaction Rollback in Controller if exception occurred in any service

I am newbie to spring and I face problem in Transaction.
I have created two Model as below:
UserDto - Stored user information
RoleDto - Stored user's role information
Service respected to both models are as below (both are annotated with #Transactional):
UserService - void saveUser(UserDto userDto) throws Exception;
RoleService - void saveRole(RoleDto roleDto) throws Exception;
now when user create account in the application at that time I called "add" method of the controller, which has the code snippet as below:
userService.saveUser(userDto);
roleService.saveRole(roleDto);
now in this code if exception occurred in Roleservice than it still insert user data into database table. but I want to rollback that too if roleService throws any exception. I tried to find the solution but could not get any good tutorial. any help will be appreciated.
The way you're calling the method makes each of it has its own transaction. Your code can be understood like this:
Transaction t1 = Spring.createTransaction();
t1.begin();
try {
//your first service method marked as #Transactional
userService.saveUser(userDto);
t1.commit();
} catch (Throwable t) {
t1.rollback();
} finally {
t1.close();
}
Transaction t2 = Spring.createTransaction();
t2.begin();
try {
//your second service method marked as #Transactional
roleService.saveRole(roleDto);
t2.commit();
} catch (Throwable t) {
t2.rollback();
} finally {
t2.close();
}
An option to solve this would be to create another service class where its implementation has RoleService and UserService injected, is marked as #Transactional and calls these two methods. In this way, both methods will share the same transaction used in this class:
public interface UserRoleService {
void saveUser(UserDto userDto, RoleDto roleDto);
}
#Service
#Transactional
public class UserRoleServiceImpl implements UserRoleService {
#Autowired
UserService userService;
#Autowired
RoleService roleService;
#Override
public void saveUser(UserDto userDto, RoleDto roleDto) {
userService.saveUser(userDto);
roleService.saveRole(roleDto);
}
}
A better design would be to make RoleDto a field of USerDto and that implementation of USerService has a RoleService field injected and perform the necessary calls to save each. Note that service classes must provide methods that contains business logic, this also means business logic rules. Service classes are not just wrappers for Dao classes.
This could be an implementation of the above explanation:
#Service
public class UserServiceImpl implements UserService {
#Autowired
RoleService roleService;
public void saveUSer(UserDto userDto) {
//code to save your userDto...
roleService.saveRole(userDto.getRoleDto());
}
}

Categories

Resources