I need to add additional business-logic for POST method. For now, I'm going to reuse the logic from RepositoryEntityController for getting and save the needed object.
#RepositoryRestController
#RequestMapping("/customPost")
public class UserController implements ApplicationEventPublisherAware {
private final UserRepository userRepository;
private final RepositoryRestConfiguration config;
private final HttpHeadersPreparer headersPreparer;
private ApplicationEventPublisher publisher;
#Autowired
public UserController(UserRepository userRepository, RepositoryRestConfiguration config, HttpHeadersPreparer headersPreparer) {
this.userRepository = userRepository;
this.config = config;
this.headersPreparer = headersPreparer;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
#ResponseBody
#RequestMapping(
value = {"/{repository}"},
method = {RequestMethod.POST}
)
public ResponseEntity<ResourceSupport> postCollectionResource(PersistentEntityResource payload, PersistentEntityResourceAssembler assembler, #RequestHeader(value = "Accept", required = false) String acceptHeader) throws HttpRequestMethodNotSupportedException {
return this.createAndReturn(payload.getContent(), assembler, this.config.returnBodyOnCreate(acceptHeader));
}
private ResponseEntity<ResourceSupport> createAndReturn(Object domainObject, PersistentEntityResourceAssembler assembler, boolean returnBody) {
publisher.publishEvent(new BeforeCreateEvent(domainObject));
Object savedObject = userRepository.save((User) domainObject);
publisher.publishEvent(new AfterCreateEvent(savedObject));
PersistentEntityResource resource = returnBody ? assembler.toFullResource(savedObject) : null;
HttpHeaders headers = headersPreparer.prepareHeaders(resource);
addLocationHeader(headers, assembler, savedObject);
return ControllerUtils.toResponseEntity(HttpStatus.CREATED, headers, resource);
}
private void addLocationHeader(HttpHeaders headers, PersistentEntityResourceAssembler assembler, Object source) {
String selfLink = assembler.getSelfLinkFor(source).getHref();
headers.setLocation((new UriTemplate(selfLink)).expand(new Object[0]));
}
}
The code which I sent - is working ok. But the issue is that I need to add some request mapping #RequestMapping("/customPost") to controller.
Without this mapping - method will not work. I tried to have the same controller but without "/customPost" mapping. I got this exception at start of applicaton:
caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'repositoryEntityController' method
public org.springframework.http.ResponseEntity<org.springframework.hateoas.ResourceSupport> org.springframework.data.rest.webmvc.RepositoryEntityController.postCollectionResource(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework.data.rest.webmvc.PersistentEntityResource,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler,java.lang.String) throws org.springframework.web.HttpRequestMethodNotSupportedException
to {[/{repository}],methods=[POST],produces=[application/hal+json || application/json]}: There is already 'userController' bean method
public org.springframework.http.ResponseEntity<org.springframework.hateoas.ResourceSupport> com.project.controller.UserController.postCollectionResource(org.springframework.data.rest.webmvc.PersistentEntityResource,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler,java.lang.String) throws org.springframework.web.HttpRequestMethodNotSupportedException mapped.
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.assertUniqueMethodMapping(AbstractHandlerMethodMapping.java:576) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.register(AbstractHandlerMethodMapping.java:540) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.registerHandlerMethod(AbstractHandlerMethodMapping.java:264) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
I also tried to remove "/customPost" mapping from controller and change mapping in metod to "/users". But for this case, I have this exception:
java.lang.NullPointerException: null
at org.springframework.data.rest.webmvc.config.RootResourceInformationHandlerMethodArgumentResolver.resolveArgument(RootResourceInformationHandlerMethodArgumentResolver.java:86) ~[spring-data-rest-webmvc-2.6.10.RELEASE.jar:na]
at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.resolveArgument(PersistentEntityResourceHandlerMethodArgumentResolver.java:113) ~[spring-data-rest-webmvc-2.6.10.RELEASE.jar:na]
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
The question is: how can I have needed logic without adding "/customPost" mapping?
Spring Data REST emits its own events when working with entities:
BeforeCreateEvent
AfterCreateEvent
BeforeSaveEvent
AfterSaveEvent
BeforeLinkSaveEvent
AfterLinkSaveEvent
BeforeDeleteEvent
AfterDeleteEvent
You can listen to these events to add your additional business logic. For example, to perform something after the SDR creates a new User (with POST method) or saves (updates) the existent User (with PUT/PATCH methods), you can use such a handler:
#Component
#RepositoryEventHandler
public class UserEventHandler {
private final UserServie userServie;
public UserEventHandler(UserServie userServie) {
this.userServie = userServie;
}
#HandleAfterCreate
public void handleAfterCreateUser(User user) {
userService.afterCreate(user)
}
#HandleAfterSave
public void handleAfterSaveUser(User user) {
userService.afterSave(user)
}
}
P.S. If I'm not mistaken, SDR emits these events outside the current transactions, only before it is started or after it is committed. You need to take this into account when implementing your business logic...
Related
I am trying to fetch an article from the db using jquery code
this is the jquery function
function searchArticle(codeArticle){
if(codeArticle){
//alert(codeArticle);
var detailHtml ="";
$.getJSON("detailArticle",
{
codeArticle: codeArticle,
ajax:true
},
function(data){
if(data){
detailHtml+= "<tr>"+
"<td>"+data[0].article.codeArticle+"</td>"+
"<td>1</td>"+
"<td>"+data[0].prixUnitaireTTC+"</td>"+
"<td>0</td>"+
"</tr>";
$("#detailNouvelleCommande").append(detailHtml);
}else{
alert("article not found");
}
});
}
}
and this is my controller method
#RequestMapping(value = "/detailArticle")
#ResponseBody
public Article getArticleByCode(String codeArticle){
if(codeArticle == null){
return null;
}
Article article = articleService.findOne("codeArticle", codeArticle);
if(article == null){
return null;
}
return article;
}
the method selects the article from database correctly however while returning it I receive the following eror:
java.lang.IllegalArgumentException: No converter found for return value of type: class com.stock.mvc.entity.Article
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:187)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:174)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
I have some REST Web Services in Spring Boot (version 1.5.13)
I would like to use #ControllerAdvice to handle the exceptions thrown by the controllers.
For the API testA below, the #ControllerAdvice class is able to capture and handle the exception if id=123, however, if id is not equals to 123, my program is not able to convert the ResponseEntity to JSON and "org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation (406)" is thrown.
If I uncomment the #EnableWebMvc below, the ResponseEntity is able to return the JSON result correctly, however, the #ControllerAdvice will not be able to capture the exception.
#RestController("SpocController")
//#EnableWebMvc
#RequestMapping(value = { "/testing" })
public class TestController
{
#GetMapping(value = "/testA", produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ResponseEntity<Object> testA(#RequestParam String id) throws IOException
{
if(id.equals("123"))
{
throw new IOException("customized exception");
}
Map<String,Object> body = new HashMap<String, Object>();
body.put("hello", "hihi");
return new ResponseEntity<Object>(body, HttpStatus.OK);
}
#GetMapping(value = "/testB", produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String testB(#RequestParam String id) throws IOException
{
if(id.equals("123"))
{
throw new IOException("customized exception");
}
return "{ \"ok\":\"test ok\" }";
}
}
For API testB, as the return type is String, it was able to return the string in JSON, and #ControllerAdvice is also able to capture and handle the exception.
This is my ControllerAdvice:
#ControllerAdvice
public class TestExceptionHandler
{
#ResponseBody
#ExceptionHandler(IOException.class)
protected ResponseEntity<Object> handleIOException(IOException ex)
{
Map<String, Object> map = new HashMap<String, Object>();
map.put("message", ex.getMessage());
return new ResponseEntity(map, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is the HttpMediaTypeNotAcceptableException (406) I mentioned above.
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:259)
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:208)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:113)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
1) No matter where I put the #EnableWebMvc (in #RestController, #ControllerAdvice or other classes), my #ExceptionHandler will not be able to capture any exception. Any idea why it happens?
2) I would like to get my API testA working as I really want to return a ResponseEntity type and the exception thrown by it can be captured by the #ControllerAdvice. Any advice is welcomed.
Thanks in advance.
With #RestController,
No need to use #ResponseBody, you can view the document for more detail.
-Make sure your request mapping work and log something.
-Try using #RestControllerAdvice for #RestController
Found the culprit, after commenting the following section of code, everything works like a charm. Anyway, thank you everyone!
public HandlerExceptionResolver handlerExceptionResolver()
{
return new HandlerExceptionResolver()
{
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
{
return null;
}
};
}
#RestController
#RequestMapping(value = { "/testing" })
public class ExceptionController {
#GetMapping(value = "/testA", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> testA(#RequestParam String id) throws IOException
{
if(id.equals("123"))
{
throw new IOException("customized exception");
}
Map<String,Object> body = new HashMap<String, Object>();
body.put("hello", "hihi");
return new ResponseEntity<Object>(body, HttpStatus.OK);
}
#GetMapping(value = "/testB", produces = MediaType.APPLICATION_JSON_VALUE)
public String testB(#RequestParam String id) throws IOException
{
if(id.equals("123"))
{
throw new IOException("customized exception");
}
return "{ \"ok\":\"test ok\" }";
}
}
2.change your advice controller-:
package com.ad.blog.controller;
import java.io.IOException;
import java.util.Optional;
import org.springframework.hateoas.VndErrors;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
#org.springframework.web.bind.annotation.ControllerAdvice
#RequestMapping(produces = "application/vnd.error+json")
public class ControllerAdvice {
#ExceptionHandler(IOException.class) public ResponseEntity < VndErrors > notFoundException(final IOException e) {
return error(e, HttpStatus.NOT_FOUND, e.getMessage());
}
private ResponseEntity < VndErrors > error(final Exception exception, final HttpStatus httpStatus, final String logRef) {
final String message = Optional.of(exception.getMessage()).orElse(exception.getClass().getSimpleName());
return new ResponseEntity < > (new VndErrors(logRef, message), httpStatus);
}
}
you need to add hateos dependency in your pom.xml file.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
#EnableWebMVc annotation is used to enable MVC Java config that overrides the DispatcherServlet defaults configuration.if you want to override default configuration then create a class-:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
}
TL;DR:
Service method annotated with #Transactional(propagation = Propagation.NOT_SUPPORTED)
Hibernate 5.0.4.Final: everything works as expected (method is executed without transaction)
Hibernate 5.2.5.Final: javax.persistence.TransactionRequiredException: no transaction is in progress is thrown
as a testcase for this issue I created a simple maven web app and the only change made in code (copy-paste from old working project) was Hibernate version bump in pom.xml
Question:
What is the proper way to execute service methods without transaction nowadays?
Code snippets (Spring used as a main framework):
DAO:
#Repository
public class UrlDaoImpl implements UrlDao {
#Autowired
private SessionFactory sessionFactory;
#Override
public List<Url> getAllUrls() {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("from Url");
return query.list();
}
}
Service:
#Service
public class UrlServiceImpl implements UrlService {
#Autowired
private UrlDao urlDao;
#Override
#Transactional // THIS WORKS IN NEW HIBERNATE
public List<Url> getAllUrls() {
return urlDao.getAllUrls();
}
#Override
#Transactional(propagation = Propagation.NOT_SUPPORTED) // THIS USED TO WORK BUT NOW THROWS EXCEPTION
public List<Url> getAllUrlsNoTxn() {
return urlDao.getAllUrls();
}
}
Controller:
#Controller
public class HomeController {
#Autowired
private UrlService urlService;
#RequestMapping(value = "/", method = RequestMethod.GET, produces = "text/plain")
public String entryPoint() {
urlService.getAllUrls();
System.out.println("--------------------- ok");
return "ok";
}
#RequestMapping(value = "/no-txn", method = RequestMethod.GET, produces = "text/plain")
public String entryPointNoTxn() {
// EXCEPTION WILL BE THROWN BELOW
urlService.getAllUrlsNoTxn();
System.out.println("--------------------- ok no txn");
return "ok no txn";
}
}
Stacktrace for the exception in new Hibernate:
exception
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
root cause
javax.persistence.TransactionRequiredException: no transaction is in progress
org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3439)
org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1410)
org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1406)
org.springframework.orm.hibernate5.SessionFactoryUtils.flush(SessionFactoryUtils.java:144)
org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:95)
org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:95)
org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:932)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:744)
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
com.sun.proxy.$Proxy32.getAllUrlsNT(Unknown Source)
com.example.web.controller.HomeController.entryPointNoTxn(HomeController.java:31)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:220)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
Ok, after few hours of trying different configurations (before posting this question to SO), I finally found the solution.
For new Hibernate versions there is another required parameter that must be declared on #Transactional if you want to execute a method without a transaction: readOnly = true. So the working example of the Service part is:
#Service
public class UrlServiceImpl implements UrlService {
#Autowired
private UrlDao urlDao;
#Override
#Transactional
public List<Url> getAllUrls() {
return urlDao.getAllUrls();
}
#Override
#Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED) // ADDED READONLY TO WORK IN NEW HIBERNATE VERSIONS
public List<Url> getAllUrlsNoTxn() {
return urlDao.getAllUrls();
}
}
I also confirmed that this works on debug by calling ((org.hibernate.engine.transaction.internal.TransactionImpl) session.getTransaction()).isActive(); which returns true for the first Service method (with transaction) and false for the second Service method (with Propagation.NOT_SUPPORTED).
I have hibernate filter like this:
#Entity
#FilterDef(name = "filter_name", parameters = { #ParamDef(name = "tenatId", type = "long") })
#Filter(name = "filter_name", condition = "tenant_id = :tenantId")
public class MyEntity {
private long tenantId;
}
And I want to enable this filter every time request come, so I create Request Interceptor like this:
#Configuration
public class RequestInterceptor implements HandlerInterceptorAdapter {
#PersistenceContext
private EntityManager entityManager;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
Filter filter = entityManager.unwrap(Session.class).enableFilter("filter_name");
filter.setParameter("tenantId", myService.getTentantId());
}
}
But I cannot the transaction manager, got error:
java.lang.IllegalStateException: No transactional EntityManager available
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:272)
at com.sun.proxy.$Proxy184.unwrap(Unknown Source)
at id.gdplabs.koi.api.config.security.RequestInterceptor.preHandle(RequestInterceptor.java:27)
at org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter.preHandle(WebRequestHandlerInterceptorAdapter.java:56)
at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:134)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:958)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
...
It works if I move the code to controller:
#RequestMapping(method = RequestMethod.GET, value = "/test")
public List<MyEntity> test() {
Filter filter = entityManager.unwrap(Session.class).enableFilter("filter_name");
filter.setParameter("tenantId", myService.getTentantId());
return myService.findAll();
}
Do you have idea why I cannot unwrap hibernate session in the Interceptor?
Any suggestion are welcome, Thanks!
I have an error while I am testing my rest controller on a specific method. I am using the #Query annotation to do my database query. And it's using the "principal.username" to do it. I don't have the all picture on how principal.username is fetched and used in my application. I am currently looking at the spring-security documentation about it. But my problem is in the test part, when I execute the test below, I have an error "Faillure" because of the #Query.
The repository:
public interface MeetingRepository extends JpaRepository<Meeting,Long> {
#Query("select m from Meeting m where m.visibility = 'PUBLIC' OR m.user.login = ?#{principal.username}")
List<Meeting> findOpenAndUserMeetings();
}
A Rest Controller Method:
#RequestMapping(value = "/api/meetings", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Meeting>> getAll()
{
List<Meeting> meetings = MeetingRepository.findOpenAndUserMeetings();
return new ResponseEntity<List<Meeting>>(meetings, HttpStatus.OK);
}
A test:
#Test
#Transactional
public void getAllMeetings() throws Exception {
// Initialize the database
MeetingRepository.saveAndFlush(Meeting);
// Get all the Meetinges
restMeetingMockMvc.perform(get("/api/meetings"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
And this error:
getAllMeetings(com.ent.web.rest.MeetingResourceTest) Time elapsed: 0.07 sec <<< ERROR!
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 10): Property or field 'username' cannot be found on object of type 'java.lang.String' - maybe not public?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:226)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:93)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.access$000(PropertyOrFieldReference.java:46)
at org.springframework.expression.spel.ast.PropertyOrFieldReference$AccessorLValue.getValue(PropertyOrFieldReference.java:372)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:88)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:131)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:299)
at org.springframework.data.jpa.repository.query.SpelExpressionStringQueryParameterBinder.evaluateExpression(SpelExpressionStringQueryParameterBinder.java:131)
at org.springframework.data.jpa.repository.query.SpelExpressionStringQueryParameterBinder.potentiallyBindExpressionParameters(SpelExpressionStringQueryParameterBinder.java:89)
at org.springframework.data.jpa.repository.query.SpelExpressionStringQueryParameterBinder.bind(SpelExpressionStringQueryParameterBinder.java:69)
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.doCreateCountQuery(AbstractStringBasedJpaQuery.java:109)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.createCountQuery(AbstractJpaQuery.java:190)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.doExecute(JpaQueryExecution.java:173)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:74)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:97)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:88)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:395)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:373)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:122)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy148.findOpenAndUserMeetings(Unknown Source)
at com.ent.web.rest.MeetingResource.getAll(MeetingResource.java:77)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:144)
at com.ent.web.rest.MeetingResourceTest.getAllMeetings(MeetingResourceTest.java:151)
Edit
How to be able to use this "?#{principal.username}" in the test? After investigating, I found that here: Spring Security 4.0: WebSocket, Spring Data and Test Support
Spring Data Integration
It is now possible to access the current user within Spring Data queries using SpEL. To enable this feature with Java Configuration, you can define a #Bean.
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension(){
return new SecurityEvaluationContextExtension();
}
Then you can refer to Spring Security's principal in your queries. For example:
public interface BlogRepository extends JpaRepository<Blog,Long> {
#Query("select blog from Blog blog where blog.user.login = ?#{principal.username}")
List<Blog> findAllForCurrentUser();
}
Is it a context problem?
In my project, I have a repository that uses #{principal.username} in one of its queries. Here's what it looks like:
public interface BlogRepository extends JpaRepository<Blog, Long> {
#Query("select blog from Blog blog where blog.user.login = ?#{principal.username}")
List<Blog> findAllForCurrentUser();
}
My BlogResource controller calls this as follows:
#Timed
public List<Blog> getAll() {
log.debug("REST request to get all Blogs for current user");
return blogRepository.findAllForCurrentUser();
}
To test this, I upgraded to Spring Security 4.0.1 and added a dependency on spring-security-test:
<spring-security.version>4.0.1.RELEASE</spring-security.version>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>${spring-security.version}</version>
<scope>test</scope>
</dependency>
In BlogResourceTest, I added dependencies on WebApplicationContext and UserRepository:
#Autowired
WebApplicationContext context;
#Inject
UserRepository userRepository;
Then I modified the getAllBlogs() test to use Spring Security's "with(user(username))" functionality.
#Test
#Transactional
public void getAllBlogs() throws Exception {
restBlogMockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
// Initialize the database
blog.setUser(userRepository.findOneByLogin("user").get());
blogRepository.saveAndFlush(blog);
// Get all the blogs
restBlogMockMvc.perform(get("/api/blogs").with(user("user")))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[*].id").value(hasItem(blog.getId().intValue())))
.andExpect(jsonPath("$.[*].name").value(hasItem(DEFAULT_NAME.toString())))
.andExpect(jsonPath("$.[*].handle").value(hasItem(DEFAULT_HANDLE.toString())));
}
I don't know why Spring Security's test annotations (#WithMockUser and #WithUserDetails) don't work. I asked a question about this a week ago:
Spring MVC Test with RequestPostProcessor vs. Annotations