One Transaction for Hibernate Validation and Spring controller - java

I am trying to implement the registration controller for a Rest API. I have read about where to place #Transactional quite a bit. (Not at DAO level but at the services maybe orchestrated). In my use case I want not only services but also a hibernate validation to use the same transaction.
This is the code of the controller:
#Autowired
private UserService userService;
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
#Transactional
public DefaultResponse register(#Valid RegisterIO registerIO, BindingResult errors) {
DefaultResponse result = new DefaultResponse();
if (errors.hasErrors()) {
result.addErrors(errors);
} else {
userService.register(registerIO);
}
return result;
}
I have written an custom contraint annotation, which validates an attribute of the parameter registerIO. Both, this validator and userService.register(registerIO); access the database (check if the email address is already in use).
Therefore I want both methods use the same Hibernate session and transaction.
This approach results in the following exception:
org.hibernate.HibernateException: No Session found for current thread
org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)
org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:941)
The problem is the #Transactional annotation. When I place this annotation at the methods witch call the database everything works find but two transactions are startet. I suspect that when i place it at the register Method the hibernate validation is performed before #Transactional starts the transaction for this method.
I developed the following functional workaround but I am not happy with it. This codes does not use the #Valid annotation but calls the validator by itself:
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
#Transactional
public DefaultResponse register( RegisterIO registerIO, BindingResult errors) {
DefaultResponse result = new DefaultResponse();
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Validator validator = vf.getValidator();
Set<ConstraintViolation<RegisterIO>> valResult = validator.validate(registerIO);
I try to summarise my question:
Using Spring MVC and Hibernate-Validation together with #Valid and #Transactional, how is it possible to encapsulate the whole request into one transaction?
Thank you :)

Your workaround could be improved by using a single Validator and injecting it intp the controller. Have you tried:
#Autowired
private Validator validator;
This way you skip the overhead of creating the validator on each request.
You should also be careful with race conditions. While you are checking the database whether a given email exists another request can create this record, so that you still get an exception at the time you insert the data.

Related

#Valid bean validation

Consider the two frameworks shown below. Here I need to validate the bean
Controller
In controller I m using #Valid and does the java validation. Works fine
#RequestMapping("")
void testIt(#Valid #RequestBody User user){
}
Normal Spring application without controller
Is there any way to do validation here. Its not a controller and #Valid doesn't work here.
Anyways to use #Valid or any similar type of validation for normal function?
void testIt(#Valid User user){
}
You can enable method validation by declaring beans of type org.springframework.validation.beanvalidation.MethodValidationPostProcessor and org.springframework.validation.beanvalidation.LocalValidatorFactoryBean and annotating the class containing testIt() with the #Validated annotation.
#Validated
#Component
public class TestIt {
public void testIt(#Valid User user) {
...
}
}
ConstraintViolationException will be thrown if validation errors occur when calling testIt(). Also, make sure you have Hibernate Validator in your classpath.

SpringBoot, JPA and Hibernate Unit-Test for Entities layer

I'm implementing a project using SpringBoot, JPA and Hibernate.
I implemented the DB entities layer with JPA repository.
I'm interested to understand the best practice to write unit-tests for this layer.
Point number one: for this layer, from your point of view, it's necessary to use an integrated DB or it' necessary to mock using, for example, Mockito?
My idea, for this layer, it's, for example, to test the entity structure: check fields validation for example, insert and retrieve some data. In this way, I think I could cover the tests for this entire data-layer.
I'm trying to understand these best practices and, in the mean time, I tried to write a first example of the test:
#ActiveProfiles("test")
#DisplayName("Test Item JPA Entity")
#DataJpaTest
#AutoConfigureTestDatabase( replace = AutoConfigureTestDatabase.Replace.NONE )
public class ItemEntityTest {
#Autowired
MyEntityRepository repo;
#Test
#Transactional
public void testEntityCreation() {
Entity e = new Entity();
e.setMyField1("A");
e.setMyField1("A");
//e.setMandatoryField("C")
repo.save(e);
}
}
Unfortunately, In this case, I notiest that the fields validation is not applied (#NotNull or #NotEmpty, or #Column(nullable=false), etc ... If I try to save the entity into my application the validation works fine... the exceptions are raised). Why?
Also some "automatic fields" (for example creation time and last modification time) are not filled.
Is this the correct path? Ho to test my entities definition?
As mentioned in that answer, the problem is that with #DataJpaTest spring will use TestEntityManager and the transactional annotation will override the default auto commit and rollback behaviour by spring boot.
So your test method will pass and assuming the hibernate is the ORM being used here, when the flushing will take place finally, hibernate's pre-insert event will fire and validations will be applied, but your test case will pass till then so it will produce false positive (as the terms used in spring docs)
Solution: You would need to inject entity manager in your test and flush manually it so that hibernate pre-insert event triggers before your test completes.
#ActiveProfiles("test")
#DisplayName("Test Item JPA Entity")
#DataJpaTest
#AutoConfigureTestDatabase( replace = AutoConfigureTestDatabase.Replace.NONE )
public class ItemEntityTest {
#Autowired
MyEntityRepository repo;
#Autowired
private TestEntityManager em;
#Test
#Transactional
public void testEntityCreation() {
Entity e = new Entity();
e.setMyField1("A");
e.setMyField1("A");
//e.setMandatoryField("C")
repo.save(e);
em.flush();
}
}
This must trigger your validations on the entity applied, this is documented in spring framework docs.
Please notice that the documentation is about spring framework and uses session factory, but the concept is same
You may check the spring boot docs as well, which points to the spring framework docs for this behaviour.

Why is #Validated required for validating Spring controller request parameters?

With the following validation setup in an annotated MVC controller:
#RestController
#RequestMapping("/users")
#Validated // <-- without this, the #Size annotation in setPassword() has no effect
public class UserController {
#PutMapping("/{id}/password")
public void setPassword(#PathVariable long id, #RequestBody #Size(min = 8) String password) {
/* ... */
}
#PutMapping("/{id}/other")
public void setOther(#PathVariable long id, #RequestBody #Valid MyFormObject form) {
/* ... */
}
}
#Validated on the controller is required for the method parameter since it's not a "complex" object. In comparison, the #Valid annotation on the setOther method works without the #Validated annotation.
Why is #Validated required? Why not enable it by default? Is there a cost to its use?
edit
Note that Difference between #Valid and #Validated in Spring is related (I read it before asking this), but it doesn't address the why in my question.
Validation of objects is done by Hibernate Validator using the annotations from Jakarta Bean Validation 2.0. Something needs to trigger hibernate validator to run.
SpringMVC calls the controller methods when it sees a parameter with #Valid it will pass that object to hibernate validator. Hibernate validator will
Examine the class of the object to figure out what validation rules have been put on the class fields
Execute the validation rules against the fields marked up with validation annotations.
So in this case
#PutMapping("/{id}/other")
public void setOther(#PathVariable long id, #RequestBody #Valid MyFormObject form) {
/* ... */
}
MyFormObject has annotations on it that the hibernate validator can find to validate the object.
In this case
#PutMapping("/{id}/password")
public void setPassword(#PathVariable long id, #RequestBody #Size(min = 8) String password) {
/* ... */
}
java.lang.String does not have any annotations defined on it for hibernate validator to discover.
#Valid comes from the Bean validation package javax.validation.Valid while #Validated comes from Spring org.springframework.validation.annotation.Validated
#Validated annotation activates the Spring Validation AOP interceptor and it will examine method parameters to see if they have any validation annotations on them, if they do then Spring will call hibernate validator with each specific annotation for example #Size(min = 8) String password means call hibernate size validator and pass the value of the parameter password in this case hibernate validator does not need to scan java.lang.String to see if it has validation annotations on it. #Validated works on any spring #Component you can use it on #Service classes for example.
There is extra overhead for using #Validated similar to using #Transactional so that is why you have to opt into it. In the case of javax.validation.Valid Spring MVC needs to check the annotations on the controller method parameters so when it sees #Valid it is easy for it to send that object the Hibernate Validator without needing to add an AOP interceptor.

Can spring execute a factory bean on #RequestBody

If I have a Spring webapp, with an endpoint like so:
#Autowired
UnpublishedStuffFactory unpublishedStuffFactory;
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
#ResponseBody
public OutputStuff update(#RequestBody Stuff stuff, #PathVariable String id) {
UnpublishedStuff uStuff = unpublishedStuffFactory.get(stuff);
//handle the request
}
UnpublishedStuffFactory.java:
#Component
public class UnpublishedStuffFactory {
#Autowired
//some autowired beans
public UnpublishedStuff get(Stuff stuff) {
return new UnpublishedStuff(stuff, /*some autowired beans*/);
}
}
So Stuff is my domain object (simple POJO). OutputStuff is something I generate (some metadata etc) and return if update was ok.
It all works fine, however I would like spring to automatically execute this factory method when request comes in, and inject UnpublishedStuff to the update method, instead of injecting Stuff.
Basically UnpublishedStuff is kind of a decorator around Stuff that adds functionality via other autowired beans (from the factory) to validate, publish etc to Stuff (I do not want this functionality in Stuff, I want it to stay a basic POJO, hence the decorator).
Is there a way to do this? Or maybe there is a better approach to this stuff?
Thanks for help :)

Spring-Data Calling Save on Entity from Controller

I am using Spring Data within Spring Boot. I have a simple controller method that accepts an instance of an entity I'd like to update in my database. However, after calling repository.save() the object isn't persisted, however, if I query for the object (from the Id of the incoming entity) I have no problem, and the update persists.
Also the HTTP response is a 405 that the method doesn't work?
Controller:
#Transactional
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(value = "/account/{id}", method = RequestMethod.PUT)
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Account updateAccount(#PathVariable("id") String id, #RequestBody Account account) {
return accountService.updateAndValidateAccountProfile(account);
}
}
Service:
#Override
public Account updateAndValidateAccountProfile(Account account) {
// calling findOne() returns the object ... and the save() works fine
//Account currentAccount = accountRepository.findOne(account.getId());
return accountRepository.save(account);
}
Repository:
interface AccountRepository extends CrudRepository<Account, Long> {
}
Is there something about the way I need to identify the object to Spring Data. I see the select statements fire out of hibernate, and I know the merge should happened on the primary key (id) within the Spring Data call to .save() - so I shouldn't need to query the object and manually assign the updated values correct??
Have you enabled transaction management? You need to use #EnableTransactionManagement on your config class otherwise #Transactional has no effect.
A spring tutorial on Managing Transactions
As a side note: making the controller layer transactional is usually not a good idea, especially when you have a proper separation of controller, service and repository layers. Consider moving #Transactional into the service layer.
Since you are using JPA, also make sure you have a JpaTransactionManager configured:
#Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
You might try extending the JpaRepository interface instead of CrudRepository then you can use saveAndFlush(entity) to make sure the changes are immediately flushed after merge.

Categories

Resources