Spring (Boot) validation annotations for different layers on the same class - java

Given a web application with Spring Boot, Spring MVC and Spring Data (with MongoDB as a database) and a one class used to represent request on multiple layers (REST, service, persistence).
Is it possible to declarative specify validation constraints on the fields of the class such that some of them would apply only for certain layers (or will be ignored by some) ?
Example:
Entity (getter and setter autogenerated)
public class User {
private String name;
#NotEmpty
private String role;
}
where #NotEmpty is JSR 303 anotation
REST API layer
role does not exist here
#RestController
public class RegisterController {
#Autowired
private UserService service;
#PostMapping
public User register(#Valid User u) {
return service.createAppUser(u);
}
}
Service layer
role is set by the implementation and is required by the persistence layer
#Service
public class UserService {
#Autowired
private UserRepo repo;
private User createAppUser(User u) {
u.setRole("APP_USER");
return repo.save(u);
}
}
where repo is Spring Data MongoRepository.
I can think of two approaches which solve this:
Introduce DTO object for REST API layer
Manual/ procedural validation; either using Spring Validator or something else, doesn't matter - simply nothing declarative
Both of which I don't like very much as they require lot of boilerplate and this is a trivial case.

you can use validation group and #Validated annotation.
like this:
Entity
#NotEmpty(groups = Create.class)
Method
public User register(#Validated(Create.class) User u) {
return service.createAppUser(u);
}

Related

(Spring Boot, JpaRepository, Postgresql) Why is repository interface is not instanced during runtime?

I'm making a small program using Spring, Maven and Hibernate. The current goal is to use JpaRepository to interact with a Postgresql database. However, when I try to call for it to list all entries in a table within the database, it spits out a NullPointerException. Online resources vary in their implementation, so it's been hard for me to understand what goes wrong.
My application can be summarized as follows:
Javatest3Application.java (Outermost layer, handles communication)
#SpringBootApplication
#EnableAutoConfiguration
#EnableJpaRepositories
#RestController
public class Javatest3Application {
//---VARIABLES---
private JavatestService service_handler = new JavatestService();
//---PUBLIC---
public static void main(String[] args) {
SpringApplication.run(Javatest3Application.class, args);
}
#PostMapping("/login")
public ResponseEntity<String> Login(#RequestBody Map<String, String> json_map) {
//>>Read json_map for account_name and account_pwd
//Ask Service layer to log user in
Long session_id = this.service_handler.OpenSession(account_name, account_pwd);
//>>Construct response, blah blah...
}
}
JavatestService.java (Middle layer, manages repository interaction)
#Service
public class JavatestService {
//---VARIABLES---
#Autowired
private JavatestRepository repo;
//---PUBLIC---
public JavatestService() {}
public Long OpenSession(String in_name, String in_pwd) {
//Call database for credentials
List<JavatestUser> user_listings = this.repo.findAll(); //<- THIS FAILS
//>>Go though list, blah blah...
}
}
JavatestRepository.java (Bottom layer, interface extention)
#Repository
public interface JavatestRepository extends JpaRepository<JavatestUser, Long> {
//List<JavatestUser> findAll(); <- Don't think I need to add this. I believe its already in JpaRepository
}
JavatestUser.java (Bottommost layer, DTO class for database entry)
#Entity
#Table(name = "javatest_table", schema = "javatest_schema")
public class JavatestUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long account_id;
private String account_name;
private String account_pwd;
public JavatestUser(){
}
//>>Getter and Setter functions, blah blah...
}
So, as far as I have understood it, we cannot instance objects of an interface. However, when using Spring, the program itself creates classes that implement the interface, and then hands such a derived class back to us via the #Autowired keyword.
Thus when we call the findAll() function, we use that derived class to fetch objects of the associated #Entity class.
From my research I've come to believe I might use the #Service keyword wrong, and that it perhaps should be a #Controller. However, as far as I can see, the are implementations of both alternatives, so my understanding of what differentiates them is somewhat lacking. Regardless of which I am using, the #Autowired doesn't seem to provide any JavatestRepository-derived object for me to call findAll() upon.
EDITS
Added #EnableJpaRepositories in accordance with Eugene Botyanovsky's suggestion.
You are probably missing annotation, which enables all your repositories:
#EnableJpaRepositories
It's similar to #EnableAutoConfiguration you used, but exactly for JPA repositories.

Spring mvc - which layer should convert entities to dtos (and vice versa)

In which layer should DTO/Entity conversion take place.
Having following structure in a Spring Mvc application:
Controller
Service
Repository
The approach I'm using now, where service layer is #Transactional.
#RestController
public class ExampleController {
#Autowired
private ExampleService exampleService;
#Autowired
private ExampleMapper exampleMapper;
#GetMapping("/examples")
public ResponseEntity<List<ExamleDto>> getAll() {
var examples = exampleService.getAll();
return ResponseEntity.ok(exampleMapper.examplesToExampleDtos(examples));
}
#PostMapping("/examples")
public ResponseEntity<Void> create(#RequestBody #Valid ExampleCreateDto createDto) {
var example = exampleService.create(createDto)
return ResponseEntity.created(URI.create("examples/" + example.getId()).build();
}
// PUT, DELETE, ...
}
#Service
#Transactional
public class ExampleService {
#Autowired
private ExampleRepository exampleRepository;
#Autowired
private ExampleMapper exampleMapper;
public List<Examle> getAll() {
var examples = exampleRepository.findAll();
return examples;
}
public void create(ExampleDto exampleDto) {
var example = exampleMapper.asExample(exampleDto);
return exampleRepository.save(example);
}
}
public interface ExampleRepository extends JpaRepository<Example, Long> {
Why I choose this aproach:
The service layer is transactional, so whenever we get back to the controller, all changes will be flushed (version field for example) will all be set.
It makes you think about your entitygraph, lets say you have a Person entity which has a list of Deparments. Lets say the PersonDto contains also the list of DeparmentDtos, it forces you to fetch all deparments before hand or you will run into a LazyInitializationException in the controller layer.
Which in my opinion is a good thing, because if you would perform the mapping in the service you would be doing N + 1 queries (N being the number of deparments) without realizing it.
Services who need each other to perform there business tasks, work on the entity model instead of the DTO model, which might have some validation (#NotNull, #Size, ...) which only supposed to be valided when it comes from the outside, but internally not all validations should be applied.
Business rules will still be checked in the service layer as part of the service method.
The only thing here is that for update/creates service still communicate by passing dtos iso of entities.
I googled this topic a lot, but couldn't find a definitive answer.

When and how to instantiate a Spring Bean in my Rest Api

First of all, I'm a relative noob to Spring Boot, so keep that in mind.
I've got a REST api in which I'm trying to minimize database calls for the same object and I've determined that using a Spring Bean scoped to the Request is what I want. Assuming that is correct, here is what I'm trying to do:
1) Controller takes in a validated PhotoImportCommandDto command
PhotoCommandController
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> importPhoto(#Valid #RequestBody PhotoImportCommandDto command){
...
}
2) PhotoImportCommandDto is validated. Note the custom #UserExistsConstraint which validates that the user exists in the database by calling a service method.
PhotoImportCommandDto
#Component
public class PhotoImportCommandDto extends BaseCommand {
#NotNull(message = "userId must not be null!")
#UserExistsConstraint
private Long userId;
...
}
What I would like to do is somehow set a Spring Bean of the user that is validated in the #UserExistsConstraint and reference it in various methods that might be called throughout a Http request, but I'm not really sure how to do that. Since I've never really created my own Spring Beans, I don't know how to proceed. I've read various guides like this, but am still lost in how to implement it in my code.
Any help/examples would be much appreciated.
You can use the #Bean annotation.
#Configuration
public class MyConfiguration {
#Bean({"validUser"})
public User validUser() {
User user;
//instantiate user either from DB or anywhere else
return user;
}
then you can obtain the validUser.
#Component
public class PhotoImportCommandDto extends BaseCommand {
#Autowired
#Qualifier("validUser")
private User validUser;
...
}
I don't really know how to make annotations in Java. Anyway, in Spring, checking where the User exists in the DataBase or not is one line of code:
userRepository.findOne(user) == null
That is accomplished by the Spring Data JPA project:
Create a JPA Entity User.
Set the spring.datasource.url and login/password in the
resources/application.properties.
Create this interface:
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
}
Note, Spring implements it behind the scences.
Inject this interface into your RestController (or any other Spring bean):
private UserRepository userRepository ;
**constructor**(UserRepository ur){
userRepository = ur;
}
Note, a Spring Bean is any class annotated #Component (this includes stereotype annotations like Controller, Repository - just look up the contents of an annotation, it may use #Component internally) or returned from a method which is annotated #Bean (can only be on the Component or Configuration class). A Component is injected by searching the classpath, Bean is injected more naturally.
Also note, injecting is specifying #Autowired annotation on field or constructor, on a factory method, or on a setter. The documentation recommends that you inject required dependencies into constructor and non-required into the setter.
Also note, if you're injecting into a constructor and it is clean by the arguments, you may omit #Autowired annotation, Spring will figure it out.
Call its method findOne.
So, you can do one of the following:
Inject the userRepository into the #RestController constructor (as shown above). I would do that.
Inject the userRepository into the #Service (internally #Component) class that will do this sorts of thing for you. Maybe you can play with it to create an annotation.
p.s. Use #PostMapping instead of #RequestMapping(method = RequestMethod.POST)
p.p.s. If ever in doubt, go to the official documentation page and just press CTRL-F: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ Note the current word, that will always take you to the latest version.
p.p.p.s Each Spring project has its own .io webpage as well as quick Get Started Guides where you can quickly see the sample project with explanations expecting you to know nothing.
Hope that helps! :)
Don't forget to mark the answer as accepted if you wish
Using Jose's input, I took a bit of a different route.
Here's what I did:
I created a ValidatedUser class:
#RequestScope
#Component
public class ValidatedUser {
private UserEntity user;
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
}
and I also created a wrapper class HttpRequestScopeConfig to capture all variables to use over the course of an Http Request to the api.
#Component
public class HttpRequestScopeConfig {
#Autowired
private ValidatedUser validatedUser;
...
public UserEntity getValidatedUser() {
return validatedUser.getUser();
}
public void setValidatedUser(UserEntity validatedUser) {
this.validatedUser.setUser(validatedUser);
}
...
}
In my UserExistsConstraintValidator (which is the impl of #UserExistsConstraint, I set the validatedUser in the httpRequestScopeConfig:
public class UserExistsConstraintValidator implements ConstraintValidator<UserExistsConstraint, Long> {
//private Log log = LogFactory.getLog(EmailExistsConstraintValidator.class);
#Autowired
private UserCommandService svc;
#Autowired
private HttpRequestScopeConfig httpRequestScope;
#Override
public void initialize(UserExistsConstraint userId) {
}
#Override
public boolean isValid(Long userIdField, ConstraintValidatorContext context) {
try {
UserEntity user = svc.findUserOfAnyStatus((Long) userIdField);
if (user != null) {
httpRequestScope.setValidatedUser(user);
return true;
}
} catch (Exception e) {
//log.error(e);
}
return false;
}
}
Now, I can access these variables throughout the rest of my service layers by autowiring HttpRequestScopeConfig where necessary.

Spring Multi datasource with similar schema

I have 4 databases with similar schema on PostgreSQL
My current code is like this
ressources
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
DAO
public interface AccountRepository extends JpaRepository<Account, Long>{}
Configuration
#Configuration
public class AccountServiceConfiguration {
#Autowired
private AccountRepository accountRepository;
#Bean
public AccountService accountService() {
return new AccountService(accountRepository);
}
}
Controller
#RestController
#RequestMapping("/accounts")
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(name = "/", method = RequestMethod.GET)
public Page<Account> getAccounts(Integer page, Integer size) {
return accountService.getAll(page, size);
}
}
Service
public class AccounttService {
public AccounttService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
public Page<Account> getAll(Integer page, Integer size) {
PageRequest pageRequest = new PageRequest(page, size);
return accountRepository.findAll(pageRequest);
}
}
I want to change like this
ressources
spring.db1.url=jdbc:postgresql://db1:5432/postgres
spring.db1.username=postgres1
spring.db1.password=postgres1
spring.db2.url=jdbc:postgresql://db2:5432/postgres
spring.db2.username=postgres2
spring.db2.password=postgres2
spring.db3.url=jdbc:postgresql://db3:5432/postgres
spring.db3.username=postgres3
spring.db3.password=postgres3
spring.db4.url=jdbc:postgresql://db4:5432/postgres
spring.db4.username=postgres4
spring.db4.password=postgres4
Controller
...
public Page<Account> getAccounts(Integer page, Integer size, string env) {
return accountService.getAll(page, size, env);
}
...
Service
public class AccounttService {
public AccounttService(Map<AccountRepository> mapAccountRepository) {
this.mapAccountRepository = mapAccountRepository;
}
public Page<Account> getAll(Integer page, Integer size, String env) {
PageRequest pageRequest = new PageRequest(page, size);
// search in specific env
}
}
How to load 4 data sources (may be on map) and search by environnement !
If i send env=db1 i want to run my request on db1
If you have other solution, i take it, but must use one repository and one entity to search in all databases.
Thank you :)
According to your comments you want a single Repository instance to switch between different schemata.
This won't work.
What you can do is provide a Facade for multiple Repository instance that delegates each call to on of the many instances according to some parameter/field/property.
But one way or the other you have to create a separate Repository instance with a different database connection for each.
What you are describing is called multi-tenancy using multiple databases.
To accomplish so you would need to manually configure the persistence layer and not to rely completely in Spring Boot auto-configuration capabilities.
The persistence layer configuration involves:
Hibernate, JPA and datasources properties
Datasources beans
Entity manager factory bean (in the case of Hibernate, with properties specifying this is a mult-tenant entity manager factory bean and tenant connection provider as well as tenant resolver)
Transaction manager bean
Spring Data JPA and transaction support configuration
In a blog post I recently published: Multi-tenant applications using Spring Boot, JPA, Hibernate and Postgres I cover in this exact problem with a detailed implementation.

Spring Data JPARepository and #Transactional For Multiple Operations inside One Method

The saveUser method doesn't save the user object name change when I have multiple operations inside one method. If I use #Transactional(propagation = Propagation.REQUIRED) on top of the saveUser service method, it works fine. When another class creates a new User object and sets all its values and calls the createUser method, it works fine. Why do I need #Transactional for the saveUser method? In what cases do I need to include #Transactional? I'm using Spring Data and JPA (Hibernate impl). Any ideas?
JPA Entity:
#Entity
public class User{
#Id
#GeneratedValue
private Long id;
#Column
private String name;
//getters/setters..etc
}
Spring Service:
#Service
public class UserServiceImpl{
#Autowired
UserRepository userRepository;
public void saveUser(Long id){
User user = userRepository.findById(id);
user.setName("newname");
userRepository.save(user);
}
public void createUser(User user){
userRepository.save(user);
}
}
Spring Data JPA/Hibernate Impl Repository:
public interface UserRepository extends JpaRepository<User, Long> {
}
The methods in JpaRepository are transactional by default (readonly for retrieving).
Now in your saveUser() method, you need #Transactional because you are retrieving an User by id and updating it and then again persisting to the database. Basically, #Transactional(readOnly=true) is used while reading else #Transactional is used in Spring Data JPA.
User user = userRepository.findById(id);
returns null to user if no user is found and user.setName("newname"); will give NullPointerException.
You need transactions if you update the database state (insert/update/delete) otherwise you'll end up having this behaviour.
Even if you do read-only operations in your methods, you should annotate them with #Transactional(readOnly=true) so Spring can optimize the transactional resource.

Categories

Resources