I noticed that the following is not working in a class marked as a #Controller:
#Autowired
SessionFactory sessionFactory;
#ResponseBody
#Transactional
#RequestMapping(method = RequestMethod.GET , value = "/map")
public ArrayList<PhotoDTO> getPhotos(...someParams) {
Entity result sessionFactory.getCurrentSession()... //do some manipulation
return result;
}
when I call the URL, I get an error saying that the method is not transactional (although, as you can see, it is marked as one)
If I copy this method to a another class called MyService and call it from the controller instead, it works perfectly
Is this some sort of a Spring advice (a conspiracy to make me use more classes more or less)?
Don't do transactions in your controller. Put them in your service layer classes.
Separate your code into model-view-controller.
Yes it is a conspiracy. It enables to you to share code between controllers/views without repeating code. And also stops rollbacks of transactions unnecessarily (for exceptions unrelated to the actual transaction).
It might seem like more code to begin with, but in the long run it is much more manageable and simpler to develop.
Probably you have two application contexts here: main Spring context loaded by ContextLoaderListener and a child context loaded by DispatcherServlet. You need to put <tx:annotation-driven /> in the configuration loaded by the child context too. If you show us your web.xml file maybe I can help you more.
Anyway, as #NimChimpsky says, is usually not a good practice to manage transactions in your controller layer.
Related
I have an old controller within my app that is defined as a spring bean in xml and makes use of Spring's SimpleFormController. I've tried to make the processes within the onSubmit method of the controller transactional by adding the #Transactional annotation but it doesn't work. According to this guide the invocation of the annotation must happen "outside of the bean", does this mean that the annotation cannot be used in old Spring controllers like mine? Are there any alternatives or workarounds?
The reason I know it's not working is because 1) changes to the db are not rolled back on error (this is despite the fact that I have defined rollbackFor = Exception.class, and even in some instances used TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();, in this instances where it tries to use the latter it throws an error stating there is no transaction present. 2) I've added breakpoints to where #Transactional is instantiated within Spring and none of them get hit.
EDIT: So people are asking for reproducible examples of code. The problem doesn't lie within the business logic code, I'm looking for clarity on the usage of the annotation within a Spring 2 controller. So what I have for example is this:
public class ImportController extends SimpleFormController {
#Override
#Transactional(rollbackFor = Exception.class)
public ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception {
...
}
}
You are right. #Transactional will not work here because onSubmit is invoked by the same bean.
And in this case the call is done directly and the default spring transaction handling does not work.
See answers in this question for a detailed explanation of the options you have
I'm using spring boot. I was new to spring and started a spring project. So I didn't know about pre defined repositories (JPA, CRUD) which can be easily implemented. In case, I wanted to save a bulk data, so I use for loop and save one by one, Its taking more time. So I tried to use #Async. But it doesn't also work, is my concept wrong?
#Async has two limitation
it must be applied to public methods only
self-invocation – calling the async method from within the same class won’t work
1) Controller
for(i=0;i < array.length();i++){
// Other codes
gaugeCategoryService.saveOrUpdate(getEditCategory);
}
2) Dao implementation
#Repository
public class GaugeCategoryDaoImpl implements GaugeCategoryDao {
// Other codings
#Async
#Override
public void saveOrUpdate(GaugeCategory GaugeCategory) {
sessionFactory.getCurrentSession().saveOrUpdate(GaugeCategory);
}
}
After removing #Async , it working normally. But with that annotation it doesn't work. Is there any alternative method for time consuming? Thanks in advance.
the #Async annotation creates a thread for every time you call that method. but you need to enable it in your class using this annotation #EnableAsync
You also need to configure the asyncExecutor Bean.
You can find more details here : https://spring.io/guides/gs/async-method/
In my opinion, there are several issues with your code:
You overwrite the saveOrUpdate() method without any need to do so. A simple call to "super()" should have been enough to make #Async work.
I guess that you somewhere (within your controller class?) declare a transactional context. That one usually applies to the current thread. By using #Async, you might leave this transaction context as (because of the async DAO execution), the main thread may already be finished when saveOrUpdate() is called. And even though I currently don't know it exactly, there is a good change that the declared transaction is only valid for the current thread.
One possble fix: create an additional component like AsyncGaugeCategoryService or so like this:
#Component
public class AsyncGaugeCategoryService {
private final GaugeCategoryDao gaugeCategoryDao;
#Autowired
public AsyncGaugeCategoryService(GaugeCategoryDao gaugeCategoryDao) {
this.gaugeCategoryDao = gaugeCategoryDao;
}
#Async
#Transactional
public void saveOrUpdate(GaugeCategory gaugeCategory) {
gaugeCategoryDao.saveOrUpdate(gaugeCategory);
}
}
Then inject the service instead of the DAO into your controller class. This way, you don't need to overwrite any methods, and you should have a valid transactional context within your async thread.
But be warned that your execution flow won't give you any hint if something goes wrong while storing into the database. You'll have to check the log files to detect any problems.
Why am I posting this?
After several hours of search / analysis / getting paranoid I would like to share my fault with you - I nailed down my cause of the problem.
When did it happen?
within the application server (in my case embedded tomcat) - tests had no problem
on two specific #Services on which the methods got properly annotated with #Transactional
What was not the problem?
Missing #EnableTransactionManagement in the #Configuration
Some weired AOP issue, eg having no default constructor in a Service not implementing an interface (AFAIK this problem does not exist anymore since Spring 4.0)
What was the problem?
I am using a CustomPermisionEvaluator for Security eg for securing methods like this:
#RequestMapping(value = "/{name}", method = RequestMethod.GET)
#PreAuthorize("hasPermission(null, 'SERVICE.ASSOCIATION.FIND_BY_NAME')")
public ResponseEntity<AssociationResult> findByName(#PathVariable String name) {
//do stuff
}
For doing this I have to extend the GlobalMethodSecurityConfiguration to get my CustomPermissionEvaluator into business. In there I used #Autowiring to get access to the beans needed for the permission evaluation. And guess what - this were those 2 #Services which the TransactionInterceptor ignored.
tl;dr
Do not autowire beans into GlobalMethodSecurityConfiguration => they will not get intercepted properly afterwards.
I have the following problem:
I'm using Spring MVC 4.0.5 with Hibernate 4.3.5 and I'm trying to create a Restfull Web application. The problem is that I want to exclude some fields from getting serialized as JSON, depending on the method called in the controller using aspects.
My problem now is that Hiberate does not commit the transaction immideatly after it returns from a method but just before serializing.
Controller.java
public class LoginController {
/*
* Autowire all the Services and stuff..
*/
#RemoveAttribues({"fieldA","fieldB"})
#RequestMapping{....}
public ResponseEntity login(#RequestBody user) {
User updatedUser = userService.loginTheUser(user);
return new ResponseEntity<>(updatedUser,HttpStatus.OK);
}
}
Service.java
public class UserService {
#Transactional
public User loginUser(User user) {
user.setLoginToken("some generated token");
return userDao.update(user); //userDao just calls entityManager.merge(..)
}
}
The advice of the aspect does the following:
for every String find the corresponding setter and set the field to null
This is done, like I said, to avoid serialization of data (for which Jackson 2 is used)
The problem now is that only after the advice has finished the transaction is commited. Is there anything I can do to tell hibernate to commit immediatly or do I have to dig deeper and start handling the transactions myself (which I would like to avoid)?
EDIT:
I also have autocommit turned on
<prop key="hibernate.connection.autocommit">true</prop>
I think the problem lies in the fact that I use lazy loading (because each user may have a huge laod of other enities attached to him), so the transaction is not commited until I try to serialze the object.
Don't set auto-commit to true. It's a terrible mistake.
I think you need a UserService interface and a UserServiceImpl for the interface implementation. Whatever you now have in the UserService class must be migrated to UserServiceImpl instead.
This can ensure that the #Transactions are applied even for JDK dynamic proxies and not just for CGLIB runtime proxies.
If you are using Open-Session-in-View anti-patterns, you need to let it go and use session-per-request instead. It's much more scalable and it forces you to handle optimum queries sin your data layer.
Using JDBC Transaction Management and the default session-close-on-request pattern you should be fine with this issue.
I removed static from DAO methods and sessionFactory. Now IDE makes me switch back to use static DAO methods because it says Non-static method updatePrice(long) cannot be referenced form a static context. Neither of classes includes static keyword. What's wrong? How to fix it?
ServiceActionDAO
#Transactional
public class ServiceActionDAO{
#Autowired
SessionFactory sessionFactory;
public void insert(ServiceActionEntity paramServiceAction){
Transaction localTransaction = null;
try{
Session localSession = sessionFactory.getCurrentSession();
localSession.save(paramServiceAction);
localSession.getTransaction().commit();
ServiceOrderDAO.updatePrice(paramServiceAction.getServiceOrderFk().longValue());// error
}
catch (Exception localException){
if (localTransaction != null) {
localTransaction.rollback();
}
}
}
UPDATE
I find a quick way to solve this problem by replacing error line with:
new ServiceOrderDAO().updatePrice(paramServiceAction.getServiceOrderFk().longValue());
Now it's not a static call.
UPDATE 2
I have a lot of DAO classes and a number of controllers. I have to find quick fix with minimum code changes taking into account Spring architecture. I have one DAO calls one or more DAOs to perform some complex queries.
As was denoted before: creating new instance of DAO would lead to unpredictable Spring session behavior.
It appears that my controllers also have calls for DAO classes.
What is the easiest way (with minimum code changes) to fix this problem?
UPDATE 3
Ended up injecting DAO into DAOs and Controllers. It seems like quick fix but from the conceptual point of view I doubt that this is the best solution...
You can either
a) inject a reference to your ServiceOrderDAO into the ServiceActionDao, and call the method on the injected DAO instance, or
b) you can introduce a service layer that calls both DAOs in the same transaction, where each DAO is injected into the service.
Either way you have to make both of the DAOs spring-managed beans.
If you have a situation where you're needing to call one DAO from another DAO it seems like introducing a service would be an appropriate solution.
Also the commit and rollback are unnecessary and even counterproductive here. When using Spring you should be able to remove this code without a problem.
Making a new instance of the DAO is not a great solution because it's not a Spring-managed bean. If it has autowired properties then those won't get set. If it's using its own SessionFactory, different from the autowired one, then you will get strange behavior as it will be using a different session than Spring-managed DAOs.