Spring #Transactional TransactionRequiredException or RollbackException - java

I have read a lot of #Transactional annotation, I saw stackoverflow answers but it does not help me. So I am creating my question.
My case is to save user with unique email. In DB I have user with email xxx#xxx.com, and I am saving user with the same email address. For saving I have to use entityManager.merge() because of this post thymeleaf binding collections it is not important.
First example:
#Controller
public class EmployeeController extends AbstractCrudController {
// rest of code (...)
#RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
public String processNewEmployee(Model model, #ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
prepareUserForm(model);
if (!result.hasErrors()) {
try {
saveEmployee(employee);
model.addAttribute("success", true);
} catch (Exception e) {
model.addAttribute("error", true);
}
}
return "crud/employee/create";
}
#Transactional
public void saveEmployee(User employee) {
entityManager.merge(employee);
}
private void prepareUserForm(Model model) {
HashSet<Position> positions = new HashSet<Position>(positionRepository.findByEnabledTrueOrderByNameAsc());
HashSet<Role> roles = new HashSet<Role>(roleRepository.findAll());
User employee = new User();
model.addAttribute("employee", employee);
model.addAttribute("allPositions", positions);
model.addAttribute("allRoles", roles);
}
}
This code is throwing TransactionRequiredException, I do not know why? It looks like #Transactional annotation did not work, so I moved annotation to processNewEmployee()
Second example:
#Controller
public class EmployeeController extends AbstractCrudController {
// rest of code (...)
#Transactional
#RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
public String processNewEmployee(Model model, #ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
prepareUserForm(model);
if (!result.hasErrors()) {
try {
entityManager.merge(employee);
model.addAttribute("success", true);
} catch (Exception e) {
model.addAttribute("error", true);
}
}
return "crud/employee/create";
}
private void prepareUserForm(Model model) { /*(.....)*/ }
}
And this code is throwing PersistenceException (because of ConstraintViolationException) and of course I got "Transaction marked as rollbackOnly" exeption.
When I try to save email which not exists this code works fine, so I thing that #Transactional annotation is configured well.
If this is important I am putting my TransationManagersConfig:
#Configuration
#EnableTransactionManagement
public class TransactionManagersConfig implements TransactionManagementConfigurer {
#Autowired
private EntityManagerFactory emf;
#Autowired
private DataSource dataSource;
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm =
new JpaTransactionManager();
tm.setEntityManagerFactory(emf);
tm.setDataSource(dataSource);
return tm;
}
public PlatformTransactionManager annotationDrivenTransactionManager() {
return transactionManager();
}
}
Could you explain my what I am doing wrong and suggest possible solution of this problem?
Solution:
Thanks to R4J I have created UserService and in my EmployeeController I am using it instead of entityManager.merge() now it works fine
#Service
public class UserService {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void merge(User user) {
entityManager.merge(user);
}
}
And EmployeeController:
#Controller
public class EmployeeController extends AbstractCrudController {
#Autowired
private UserService userService;
#RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
public String processNewEmployee(Model model, #ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
// (.....)
userService.merge(employee);
// (.....)
}
}

Your transactions don't work because you call directly 'this.saveEmployee(...)' from your 'public String processNewEmployee' method.
How come?
When you add #Transactional, Spring creates a Proxy for your Component and proxies all public methods. So when Spring itself calls your method as a HTTP Rest Request it is considered an external call that goes properly through a Proxy and new Transaction is started as required and code works.
But when you have a Proxied Component and you call 'this.saveEmployee' (that has #Transactional annotation) inside your class code you are actually bypassing the Proxy Spring has created and new Transaction is not started.
Solution:
Extract entire database logic to some sort of a Service or DAO and just Autowire it to your Rest Controller. Then everything should work like a charm.
You should avoid direct database access from Controllers anyway as it is not a very good practice. Controller should be as thin as possible and contain no business logic because it is just a 'way to access' your system. If your entire logic is in the 'domain' then you can add other ways to run business functionalities (like new user creation) in a matter of just few lines of code.

Related

Create a convenience decorator to generate a Controller from a function using BeanPostProcessor?

I'm looking to write a decorator that takes a very static function and wraps it inside a controller.
Think of it as a global scope utility callable/runnable, so pathvariable/requestbody has to be injected into the parameters. And then it has to automatically be wrapped inside a bean controller with the appropriate getmapping/postmapping to expose it an endpoint
#AutoGetMapping("/users/{id}")
public ResponseEntity<User> getById(#PathVariable long id) {
Optional<User> user = userService.getById(id);
if (user.isPresent()) {
return new ResponseEntity<>(user.get(), HttpStatus.OK);
} else {
throw new RecordNotFoundException();
}
}
gets transformed to
#RestController
public class UserController {
#Autowired
UserService userService;
#GetMapping("users")
public ResponseEntity<List<User>> getAll() {
return new ResponseEntity<>(userService.getAll(), HttpStatus.OK);
}
#GetMapping("users/{id}")
public ResponseEntity<User> getById(#PathVariable long id) {
Optional<User> user = userService.getById(id);
if (user.isPresent()) {
return new ResponseEntity<>(user.get(), HttpStatus.OK);
} else {
throw new RecordNotFoundException();
}
}
}
(maybe even the service layers).
I'm just looking for a place to start. I think im making a mistake in trying to use BeanPostProcessor and BeanDefinitionRegistryPostProcessor to do this. Can someone point me in the right direction on how to start doing this ?
One way to do it could be using the approach described in Interface Driven Controllers article with some additions.
As in the article, we can create an interface with the default annotations. Additionally, we can implement the default methods and enforce the implementation of the certain methods in the service layer using some generics like this:
#RestController
#RequestMapping("/default")
public interface BasicRestController<ID, T, S extends BasicRestService<T, ID>> {
#NonNull S getService();
#GetMapping("/{id}")
default ResponseEntity<T> getById(#PathVariable ID id) {
return getService().getById(id)
.map(ResponseEntity::ok)
.orElseThrow(RecordNotFoundException::new);
}
#GetMapping
default ResponseEntity<List<T>> getAll() {
List<T> results = getService().getAll();
return ResponseEntity.ok(results);
}
}
public interface BasicRestService<T, ID> {
Optional<T> getById(ID id);
List<T> getAll();
}
And then use it in the controller like this:
#RestController
#RequestMapping("/bars")
#RequiredArgsConstructor
public class BarController implements BasicRestController<Long, Bar, BarService> {
private final BarService barService;
#Override
public #NonNull BarService getService() {
return barService;
}
}
Minimal working example can be found here: https://bitbucket.org/kasptom/stack-73744318-interface-driven-controller

How can i treat UserNotFound exception on endpoints unit tests?

I'm working on a spring boot CRUD RESTful API with an User entity that consists of two parameters : name and id. My test framework is JUnit.
The problem i'm facing is that i don't know how to treat a throwable UserNotFound exception on my services unit tests.
I have possible "User not found by {id}" exceptions in my "List user by id", "Delete user by id" and "Update user by id" as you can see (i'll only list two endpoints to make this shorter) :
#Service
public class DeleteUserService {
#Autowired
UserRepository repository;
public void deleteUser(Long id) {
Optional<User> userOptional = repository.findById(id);
if (!userOptional.isPresent()) {
throw new UserNotFoundException(id);
} else {
repository.deleteById(id);
}
}
}
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public Optional<User> listUser(Long id) {
Optional<User> user = repository.findById(id);
if (!user.isPresent()) {
throw new UserNotFoundException(id);
} else {
return repository.findById(id);
}
}
}
Nothing wrong so far, my endpoints are working fine.
The UserNotFound code is :
#ControllerAdvice
public class UserNotFoundAdvice {
#ResponseBody
#ExceptionHandler(UserNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
String userNotFoundHandler(UserNotFoundException ex) {
return ex.getMessage();
}
}
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super("Could not find user with id " + id + ".");
}
}
The unit tests (the main reason this is being written) :
#RunWith(MockitoJUnitRunner.class)
public class DeleteUserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private DeleteUserService deleteUserService;
#Test
public void whenGivenId_shouldDeleteUser_ifFound(){
User user = new User();
user.setId(89L);
deleteUserService.deleteUser(user.getId());
verify(userRepository).deleteById(user.getId());
}
}
#RunWith(MockitoJUnitRunner.class)
public class DetailUserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private DetailUserService detailUserService;
#Test
public void whenGivenId_shouldReturnUser_ifFound() {
User user = new User();
user.setId(89L);
Optional<User> userMock = Optional.of(user);
when(userRepository.findById(user.getId())).thenReturn(userMock);
Optional<User> expected = detailUserService.listUser(user.getId());
assertThat(expected).isSameAs(userMock);
verify(userRepository).findById(user.getId());
}
}
As you can see, there's something missing in these unit tests code which is the behavior of the UserNotFound. Perhaps it is not properly mocked or something else's missing in the unit tests code??
Would really appreciate if someone could help me with this one! Sorry if the post's too long, i tried my best to explain it!
If I understand you right you need to test the behavior when the user is not found and you throw an exception.
Here is the link about how to test exception: https://www.baeldung.com/junit-assert-exception
And also additionally you can verify that delete by id or find by id weren't invoked:
verify(userRepository, never()).findById(user.getId());
or
verify(userRepository, Mockito.times(0)).findById(user.getId());
and for the deleteById the same
To test that exception handlers were invoked and worked correctly you need integration tests.

where to throw the HTTP Runtime Exception

Let's say I have the following runtime exception:
#ResponseStatus(HttpStatus.EXPECTATION_FAILED)
public class ExpectationsFailedException extends RuntimeException {
public ExpectationsFailedException(String message) {
super(message);
}
}
My question is if it is ok to throw the previous HTTP exception in my service layer or should I throw it from my controller:
#Service
public class UserService {
#Autowired
...
public void addUser(final String email, final String username, final String password){
if(parameters_are_not_valid){
throw new ExpectationsFailedException("Invalid input");
}
}
}
The controller exception throwing solution would be the following:
#Service
public class UserService {
#Autowired
...
public void addUser(final String email, final String username, final String password) throws InvalidInputParameters {
if(parameters_are_not_valid){
throw new InvalidInputParameters("Invalid input");
}
}
}
and in my controller
#RestController
public class XController{
#Autowired
private UserService userService;
#RequestMapping(value = "/addUser", method = RequestMethod.POST)
public void addUser(#Valid #RequestBody SignUpForm form, BindingResult bindingResult){
if(bindingResult.hasErrors()){
throw new ExpectationsFailedException("Input parameters conditions were not fulfilled");
}
try {
userService.addUser(...);
}
catch(InvalidInputParameters ex){
throw new ExpectationsFailedException("Invalid service input parameters");
}
}
}
Which of those solutions is preferred? Why? I have a feeling that I should not throw HTTP exceptions in my services because I may use that services in other contexts which may not be related to HTTP.
I would go with the second one.
What do you think?
I agree with your last statement. Your service layer should be independent of HTTP or frontent frameworks (#ResponseStatus is Spring MVC annotation and therefore it's not the best practice to use it in your service layer).
However you don't have to throw one exception in service layer, catch it in controller and rethrow another exception annotated with #ResponseStatus. Just add exception handler for the service exception and return appropriate response status from it. You have plenty of options, for instance #ExceptionHandler:
#ResponseStatus(HttpStatus.EXPECTATION_FAILED)
#ExceptionHandler(InvalidInputParameters.class)
public void handle() {
// Do nothing, just return the status
}
You can put this code to #ControllerAdvice annotated class to enable it for all controllers or just in you controller if it's not needed elsewhere.

Spring Data Rest / Spring Hateoas Custom Controller - PersistentEntityResourceAssembler

I'm attempting to add some additional business logic to the auto-generated endpoints from the RepositoryRestResource. Please see the code below:
Resource:
#RepositoryRestResource(collectionResourceRel="event", path="event")
public interface EventRepository extends PagingAndSortingRepository<Event, Long> {
}
Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private PagedResourcesAssembler<Event> pagedResourcesAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<PersistentEntityResource> getEvents(Pageable pageable,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Page<Event> events = eventRepository.findAll(pageable);
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
}
}
I've looked at the following two stackoverflow articles:
Can I make a custom controller mirror the formatting of Spring-Data-Rest / Spring-Hateoas generated classes?
Enable HAL serialization in Spring Boot for custom controller method
I feel like I am close, but the problem that I am facing is that:
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
returns an error saying:
"The method toResource(Page<Event>, Link) in the type PagedResourcesAssembler<Event> is not applicable
for the arguments (Page<Event>, PersistentEntityResourceAssembler)".
The toResource method has a method signature that accepts a ResourceAssembler, but I'm not sure how to properly implement this and I can't find any documentation on the matter.
Thanks in advance,
- Brian
Edit
My issue was that I thought I could override the controller methods that are auto-created from #RepositoryRestResource annotation without having to create my own resource and resource assembler. After creating the resource and resource assembler I was able to add my business logic to the endpoint.
Resource:
public class EventResource extends ResourceSupport {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Resource Assembler:
#Component
public class EventResourceAssembler extends ResourceAssemblerSupport<Event, EventResource> {
public EventResourceAssembler() {
super(EventController.class, EventResource.class);
}
#Override
public EventResource toResource(Event entity) {
EventResource eventResource = createResourceWithId(entity.getId(), entity);
eventResource.setName(entity.getName());
return eventResource;
}
}
Updated Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private EventResourceAssembler eventResourceAssembler;
#Autowired
private PagedResourcesAssembler<Event> pageAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<EventResource> getEvents(Pageable pageable) {
Page<Event> events = eventRepository.findAll(pageable);
// business logic
return pageAssembler.toResource(events, eventResourceAssembler);
}
}
The thing I don't like about this is that it seems to defeat the purpose of having a RepositoryRestResource. The other approach would be to use event handlers that would get called before and/or after the create, save, delete operations.
#RepositoryEventHandler(Event.class)
public class EventRepositoryEventHandler {
#HandleBeforeCreate
private void handleEventCreate(Event event) {
System.out.println("1");
}
}
There doesn't seem to be any events for the findAll or findOne operations. Anyways, both these approaches seem to solve my problem of extending the auto generated controller methods from RepositoryRestResource.
It requires a PagedResourcesAssembler, Spring will inject one for you if you ask.
public PagedResources<Foo> get(Pageable page, PagedResourcesAssembler<Foo> assembler) {
// ...
}
In this case the resource is Foo. It seems in your case the resource you're trying to return is an Event. If that's so, I would expect your code to look something like:
private ResourceAssembler<Event> eventAssembler = ...;
public PagedResources<Event> get(Pageable page, PagedResourcesAssembler<Event> pageAssembler) {
Event event = ...;
return eventAssembler.toResource(event, pageAssembler);
}
You provide the ResourceAssembler<Event> that tells Spring how to turn Event into a Resource. Spring injects the PagedResourcesAssembler<Event> into your controller method to handle the pagination links. Combine them by calling toResource and passing in the injected pageAssembler.
The final result can be returned simply as a body as above. You could also use things like HttpEntity to gain more control over status codes and headers.
Note: The ResourceAssembler you provide can literally be something as simple as wrapping the resource, such as Event, with a Resource object. Generally you'll want to add any relevant links though.
To hack it you can use just PagedResourcesAssembler<Object> like:
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedModel<PersistentEntityResource> getEvents(
Pageable pageable,
PersistentEntityResourceAssembler persistentAssembler,
PagedResourcesAssembler<Object> pageableAssembler
) {
return pageableAssembler.toModel(
(Page<Object>) repository.findAll(pageable),
persistentAssembler
);
}

Difference between two #RequestMapping annotations

I am pretty new in Spring MVC and I have the following doubt.
In a controller, I have a method annotated in this way:
#Controller
#RequestMapping(value = "/users")
public class UserController {
#RequestMapping(params = "register")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
So this method handle HTTP Request toward the URL /users?register where register is a parameter (because the entire class handle request toward /users resource).
Is it the same thing if, instead using the params = "register" I use the following syntaxt:
#Controller
public class UserController {
#RequestMapping("/users/{register}")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
I have deleted the mapping at class level and I use #RequestMapping("/users/{register}").
Is it the same meaning of the first example?
NO, they are completely different constructs:
Code 1
#Controller
#RequestMapping(value = "/users")
public class UserController {
#RequestMapping(params = "register")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
In this case, createForm method will be called when a HTTP request is made at URL /users?register. Quoting from Spring Javadoc, it means this method will be called whatever the value of the register HTTP parameter; it just has to be present.
"myParam" style expressions are also supported, with such parameters having to be present in the request (allowed to have any value).
Code 2
#Controller
public class UserController {
#RequestMapping("/users/{register}")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
In this case, #RequestMapping is declaring register as a PathVariable. The method createForm will be called if a HTTP request is made at URL /users/something, whatever the something. And you can actually retrieve this something like this:
#RequestMapping("/users/{register}")
public String createForm(#PathVariable("register") String register, Model model) {
// here "register" will have value "something".
model.addAttribute("user", new Customer());
return "user/register";
}

Categories