Please, look at the two code examples bellow which I'm going to use in my Spring Boot project. They both do merely the same thing - add a new object into users table, represented by User entity with username defined as #Id and a unique constraint imposed on email column (there are some other columns as well, but they are not shown here for brevity). Note: I can't simply use save() method from CrudRepository, because it merges existing record with new object if they both have the same username value. Instead, I need to insert a new object with appropriate exception thrown for duplicate data persistence.
My question is about which option should be given a favor. With EntityManager, I don't need to construct SQL statement. Apart from that obvious observation, are there any advantages which one method may offer over the other (especially, in the matter of performance and resources consumption)?
Also, when I read latest books and tutorials about data persistence in Spring Boot, they mainly focus on Spring Data JPA. For example, the 5th edition of "Spring in Action" has no word about Hibernate's EntityMnager. Does it mean that dealing with Hibernate directly can be regarded as kind of "old school" and should generally be avoided in modern projects?
Option #1: Hibernate's EntityManager
#RestController
#RequestMapping(path = "/auth/register", produces = "application/json")
#Transactional
public class RegistrationController {
#PersistenceContext
EntityManager entityManager;
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Map<String, String> registerNewUser(#RequestBody #Valid User newUser) {
try {
entityManager.persist(newUser);
entityManager.flush();
} catch (PersistenceException ex) {
// parse exception to find out which constraints have been
// broken - either it's duplicate username, email or both
String message = parseExceptionForConstraintNames(ex);
throw new ResponseStatusException(HttpStatus.CONFLICT, messsage);
}
return Collections.singletonMap("message", "Success...");
}
}
Option #2: custom #Query from CrudRepository
#RestController
#RequestMapping(path = "/auth/register", produces = "application/json")
public class RegistrationController {
private final UsersRepository usersRepository;
#Autowired
public RegistrationController(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Map<String, String> registerNewUser(#RequestBody #Valid User newUser) {
try {
usersRepository.insert(newUser);
} catch (DataIntegrityViolationException ex) {
// parse exception to find out which constraints have been
// broken - either it's duplicate username, email or both
String message = parseExceptionForConstraintNames(ex);
throw new ResponseStatusException(HttpStatus.CONFLICT, message);
}
return Collections.singletonMap("message", "Success...");
}
}
public interface UsersRepository extends CrudRepository<User, String> {
#Modifying
#Transactional
#Query(nativeQuery = true, value = "INSERT INTO users (username, email) " +
"VALUES (:#{#user.username}, :#{#user.email})")
void insert(#Param("user") User newUser);
}
See this answer for Using JPA repository vs Entity Manager.
Best practice is to not use Repository directly. use Service layer between controller and repository where you can implement the logic for duplicate entries by checking if the record already exist in DB using findByUsername(String username); throw exception if it already exist else save() the object in DB
With the given requirements, the username filed in the entity never qualifies for the #Id.
Why can't u add an explicit id field with some sequence generator for the id filed and just keep the username marked with unique constraint only.
Related
I'm currently playing around on Spring boot 1.4.2 in which I've pulled in Spring-boot-starter-web and Spring-boot-starter-jpa.
My main issue is that when I save a new entity it works fine (all cool).
However if I save a new product entity with the same id (eg a duplicate entry), it does not throw an exception. I was expecting ConstrintViolationException or something similar.
Given the following set up:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
ProductRepository.java
#Repository
public interface ProductRepository extends JpaRepository<Product, String> {}
JpaConfig.java
#Configuration
#EnableJpaRepositories(basePackages = "com.verric.jpa.repository" )
#EntityScan(basePackageClasses ="com.verric.jpa")
#EnableTransactionManagement
public class JpaConfig {
#Bean
JpaTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
Note JpaConfig.java and Application.java are in the same package.
ProductController.java
#RestController
#RequestMapping(path = "/product")
public class ProductController {
#Autowired
ProductRepository productRepository;
#PostMapping("createProduct")
public void handle(#RequestBody #Valid CreateProductRequest request) {
Product product = new Product(request.getId(), request.getName(), request.getPrice(), request.isTaxable());
try {
productRepository.save(product);
} catch (DataAccessException ex) {
System.out.println(ex.getCause().getMessage());
}
}
}
and finally Product.java
#Entity(name = "product")
#Getter
#Setter
#AllArgsConstructor
#EqualsAndHashCode(of = "id")
public class Product {
protected Product() { /* jpa constructor*/ }
#Id
private String id;
#Column
private String name;
#Column
private Long price;
#Column
private Boolean taxable;
}
The getter, setter and equalsHashcode.. are lombok annotations.
Miscellaneous:
Spring boot : 1.4.2
Hibernate ORM: 5.2.2.FINAL
This issue happens regardless if I annotate the controller with or without #Transactional
The underlying db shows the exception clearly
2016-11-15 18:03:49 AEDT [40794-1] verric#stuff ERROR: duplicate key value violates unique constraint "product_pkey"
2016-11-15 18:03:49 AEDT [40794-2] verric#stuff DETAIL: Key (id)=(test001) already exists
I know that is better (more common) to break the data access stuff into its own service layer instead of dumping it in the controller
The semantics of the controller aren't ReST
Things I've tried:
Spring CrudRepository exceptions
I've tried implementing the answer from this question, unfortunately my code never ever hits the DataAccesException exception
Does Spring JPA throw an error if save function is unsuccessful?
Again similar response to the question above.
http://www.baeldung.com/spring-dataIntegrityviolationexception
I tried adding the bean to my JPAconfig.java class that is:
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
But nothing seemed to happen.
Sorry for long post, ty in advance
My solution is a lot cleaner. Spring Data already provides a nice way for us to define how an entity is considered to be new. This can easily be done by implementing Persistable on our entities, as documented in the reference.
In my case, as is the OP's, the IDs come from an external source and cannot be auto generated. So the default logic used by Spring Data to consider an entity as new if the ID is null wouldn't have worked.
#Entity
public class MyEntity implements Persistable<UUID> {
#Id
private UUID id;
#Transient
private boolean update;
#Override
public UUID getId() {
return this.id;
}
public void setId(UUID id) {
this.id = id;
}
public boolean isUpdate() {
return this.update;
}
public void setUpdate(boolean update) {
this.update = update;
}
#Override
public boolean isNew() {
return !this.update;
}
#PrePersist
#PostLoad
void markUpdated() {
this.update = true;
}
}
Here, I have provided a mechanism for the entity to express whether it considers itself new or not by means of another transient boolean property called update. As the default value of update will be false, all entities of this type are considered new and will result in a DataIntegrityViolationException being thrown when you attempt to call repository.save(entity) with the same ID.
If you do wish to perform a merge, you can always set the update property to true before attempting a save. Of course, if your use case never requires you to update entities, you can always return true from the isNew method and get rid of the update field.
The advantages of this approach over checking whether an entity with the same ID already exists in the database before saving are many:
Avoids an extra round trip to the database
We cannot guarantee that by the time one thread has determined that this entity doesn't exist and is about to persist, another thread doesn't attempt to do the same and result in inconsistent data.
Better performance as a result of 1 and having to avoid expensive locking mechanisms.
Atomic
Simple
EDIT: Don't forget to implement a method using JPA callbacks that sets the correct state of the update boolean field just before persisting and just after loading from the database. If you forget to do this, calling deleteAll on the JPA repository will have no effect as I painfully found out. This is because the Spring Data implementation of deleteAll now checks if the entity is new before performing the delete. If your isNew method returns true, the entity will never be considered for deletion.
I think you are aware of CrudRepository.save() is used for both insert and update. If an Id is non existing then it will considered an insert if Id is existing it will be considered update. You may get an Exception if your send the Id as null.
Since you don't have any other annotations apart from #Id on your id variable, The Unique Id generation must be handled by your code Or else you need to make use of #GeneratedValue annotation.
To build upon Shazins answer and to clarify. the CrudRepositroy.save() or JpaRespository.saveAndFlush() both delegate to the following method
SimpleJpaRepository.java
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Hence if a user tries to create a new entity that so happens to have the same id as an existing entity Spring data will just update that entity.
To achieve what I originally wanted the only thing I could find was to drop back down to JPA solely, that is
#Transactional
#PostMapping("/createProduct")
public Product createProduct(#RequestBody #Valid Product product) {
try {
entityManager.persist(product);
entityManager.flush();
}catch (RuntimeException ex) {
System.err.println(ex.getCause().getMessage());
}
return product;
}
Here if we try to persist and new entity with an id already existing in the database it will throw will throw the constraint violation exception as we originally wanted.
Note that there are 3 scenarios here:
1. Setting ID manually
If there is no choice(like the OP), i.e if you are setting your own id "manually", Spring Data JPA is assuming that you want to check if there are duplicates(hence the SELECT), so it will do a "(i)SELECT + (ii)INSERT" if there is no existing record or a "(i)SELECT + (ii)UPDATE" if there is already an existing record.
In short, 2 SQLs!
2. Use an ID Generator
Cleaner & better, for example:
#Id
#GeneratedValue(generator = "my-uuid")
#GenericGenerator(name = "my-uuid", strategy = "uuid2")
private UUID id;
Result: there is ALWAYS only 1 INSERT statement.
3. Implement Persistable and isNew()
This has already been brilliantly answered by #adarshr, but is also more painful, i.e to implement Persistable(instead of Serializable), and implement the isNew() method.
Result: Also, 1 INSERT statement.
According to Spring Data documentation Spring persists an entity if does not exists or merge, this means update, the existing one:
Saving an entity can be performed via the CrudRepository.save(…)-Method. It will persist or merge the given entity using the underlying JPA EntityManager. If the entity has not been persisted yet Spring Data JPA will save the entity via a call to the entityManager.persist(…)-Method, otherwise the entityManager.merge(…)-Method will be called.
I have a UserEntity model and corresponding UserRepository. Similarly I have BookEntity model and BookRepository. Book has #ManyToOne relation with User.
So in Book entity, there is a field that maps to User entity's id as FK using #ManyToOne annotation. When I want to retrieve list of books based on a User, I call the method List<BookEntity> findByUser(UserEntity user); from BookRepository, using Spring Data JPA.
I can load the current authenticated user with help of Spring security but this gives me a UserDetails object. What I need is UserEntity object so that I can pass it to BookRepository methods.
Right now, what am doing is calling Optional<UserEntity> findByUsername(String username); from UserRepository to get UserEntity and to do this, I'm getting the username from UserDetails instance provided by Spring security. And then passing this UserEntity model to BookRepository methods.
I want to know if this the right way to load UserEntity model for passing into BookRepository or is there any other way to do so? If not, am I mapping Books to User incorrectly?
Any help would be highly appreciated.
In your BookRepository do something like
#Query("FROM Book b where b.user.userName = :userName")
List<Book> findBooksForUser(#Param("userName") String userName);
You can get current user username in a controller like
#RequestMapping(value = "/books", method = RequestMethod.GET)
#ResponseBody
public String getBooks(Authentication authentication) {
if(authentication != null){
//Call bookRepo.findBooksForUser(authentication.getName())
}
....
}
The problem is that one day we discovered that if we're saving an object in spring boot repository, another objects that are changed in the same method are also updated and persisted in the database.
The curiosity is massive to find out why does this actually happen. I created sample project using Spring Initializr and some template code to show the actual situation (tried to keep the number of dependencies as low as possible).
Using Spring boot version 1.5.11 (SNAPSHOT) and project has following dependencies:
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.mariadb.jdbc:mariadb-java-client:2.1.0')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Now to the point:
Project has two entities, Pet:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Pet.class)
public class Pet {
#Id
#GeneratedValue
private long id;
private String type;
public Pet() {}
public String getType() { return type; }
public void setType(String type) { this.type = type; }
}
and User:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = User.class)
public class User {
#Id
#GeneratedValue
private long id;
private String name;
public User() {}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Both entities also have repositories, Pet:
#Repository
public interface PetRepository extends CrudRepository<Pet, Long> {
Pet findPetById(Long id);
}
User:
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findUserById(Long id);
}
And one simple service where the magic actually happens ( I have pre-saved one Pet and one User object, with different name and type)
#Service
public class UserService {
#Autowired
UserRepository userRepository;
#Autowired
PetRepository petRepository;
public User changeUserAndPet() {
User user = userRepository.findUserById(1L);
Pet pet = petRepository.findPetById(1L);
user.setName("Kevin");
pet.setType("Cow");
userRepository.save(user);
return user;
}
}
Right after calling userRepository.save(user); the Pet object is also updated in the database with new type of 'Cow'. Why exactly does this happen if I only saved the User object? Is this intended to be like this?
There's also one simple controller and simple test endpoint to call the service method which most likely is not important to the question, but I'll still add it here for the sake of completeness.
#RestController
public class UserController {
#Autowired
UserService userService;
#RequestMapping(value = "/test", method = RequestMethod.GET)
public User changeUserAndPet() {
return userService.changeUserAndPet();
}
}
Any explanation / tips are appreciated and feel free to ask extra information / code in github.
The Spring Data repository is a wrapper around the JPA EntityManager. When an entity is loaded, you get the instance, but a copy of the object is stored inside the EntityManager. When your transaction commits, the EntityManager iterates all managed entities, and compares them to the version it returned to your code. If you have made any changes to your version, JPA calculates which updates should be performed in the database to reflect your changes.
Unless you know JPA quite well, it can be tricky to predict when calls are propagated to the database, since flush() is called internally. For instance every time you do a query JPA performs a pre-query flush, because any pending inserts must be send to the database, or the query would not find them.
If you defined a transaction using #Transactional on you method, then pet would be updated even if the user was not saved. When you don't have a transaction, the call to save must trigger the EntityManager to propagate your update to the database. It's a bit of a mystery to me why this happens. I Know that Spring creates the EntityManager inside OpenEntityManagerInViewInterceptor before the Controller is called, but since the transaction is not explicit, it must be created implicitly and there could potentially be multiple transactions.
I always encourage developers to use explicit transactions in Spring, and qualify them with readonly when appropriate.
That's how JPA and the EntityManager works. If you lookup an entity through the repository, it is attached to the EntityManager as managed entity. Any changes that you do to that object, are picked up when a flush is executed by the EntityManager. In fact, you wouldn't even need to call the save method on the repository in your case.
You can find more information about the lifecycle of JPA entities e.g. here: https://dzone.com/articles/jpa-entity-lifecycle
I'm rewriting an application, this time using a RESTful interface from Spring. I'm presuming that server-side authorization is best. That is:
Supppose user 1 works this REST repository. He/she accesses mysite.com/heroes/1 and gets the (id = 1) hero from the hero table.
User 2 doesn't have rights to see the (id = 1) hero, but could craft a cURL statement to try anyway. I claim the server should prevent user 2 from accessing the (id = 1) hero.
I believe that the server can extract a JWT payload that gives me the user name or password (I put it in there). From that payload the server fetches the user's account and knows what heroes he/she is entitled to see.
I have already accomplished this goal through services and DAO classes. However, the Spring Boot and JPA tutorials I see promote using CrudRepository implementations to reduce coding. I'd like to know how to do my filtering using this technology.
Here is an example from the web:
#RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
}
When mysite.com/heroes/1 is accessed it automagically returns the data from hero (id = 1). I'd like to instruct it to let me choose which ID values to permit. That is, at runtime a query parameter is provided to it through code.
As a test I provided this code:
#RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
#Query ("from Hero h where id in (1, 3, 5)")
public Hero get();
}
However, it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.
How do I get to my desired goal?
Thanks, Jerome.
UPDATE 5/13, 5:50 PM
My request is being misunderstood, so I further explain my intent.
Users 1 and 2 are ordinary users, accessing their accounts.
Each user must be confined to his/her own account.
A user can't cheat by crafting requests for other peoples' data.
Thus the need for the server to extract a user ID, or such, from a JWT token and apply it in code to whatever causes the /heroes query to work.
My original example originated with this tutorial. In it the only Java classes are Hero and HeroRepository. There are no explicit classes for DAO, services or controllers. The included Spring libraries let all of the /heroes fetching occur without further coding.
Thanks again for all of your interest and help. Jerome.
You can create a custom #Query, that uses informations (here: id) of the logged in user. With this solution an user have only access to an entity with the same id as he has.
#Override
#Query("SELECT h FROM Hero h WHERE h.id=?1 AND h.id=?#{principal.id}")
public Hero findOne(Long id);
You need to enable SpEl for #Query (link) and create an custom UserDetailsService (link) with custom UserDetails, that contains the id of the user, so you can do principal.id.
In the same way you should secure the findAll() method.
I have created HeroRepository to resolve all the queries up to my understanding.
I'd like to instruct it to let me choose which ID values to permit.
You can achieve the same using.
List<Hero> findByIdIn(List<Long> ids);
Or, if you prefer Query
#Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(#Param("ids") List<Long> ids);
it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.
I cannot see your Controller/Service methods, so I am assuming that findOne() is being called. You can prevent it using..
// Disallow everybody to use findOne()
default Hero findOne(Long id) {
throw new RuntimeException("Forbidden !!");
}
OR, if you want more control over your method invocations, you can also use #PreAuthorize from spring-security.
// Authorization based method call
#PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);
Summary
public interface HeroRepository extends CrudRepository<Hero, Long> {
// Disallow everybody to use findOne()
default Hero findOne(Long id) {
throw new RuntimeException("Forbidden !!");
}
// If u want to pass ids as a list
List<Hero> findByIdIn(List<Long> ids);
// Alternative to above one
#Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(#Param("ids") List<Long> ids);
// Authorization based method call
#PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);
}
PS: Note that I am returning Optional<Hero> from the method. Optional.empty() will be returned if query produces no results. This will force us to check if the value is present before doing any operation, thereby avoiding NullPointerException.
use this code for Controller : -
#RestController
#RequestMapping("/cities")
public class CityController {
private static final Logger logger = LoggerFactory.getLogger(CityController.class);
#Autowired
private CityService cityService;
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public RestResponse find(#PathVariable("id") Long id) {
.
.
}
use below code for Repo :-
public interface CityRepo extends JpaRepository<FCity, Long> {
#Query("select e from FCity e where e.cityId = :id")
FCity findOne(#Param("id") Long id);
}
use below code for service :-
#Service
#Transactional
public class CityService {
#Autowired(required = true)
private CityRepo cityRepo;
public FCity findOne(Long id) {
return cityRepo.findOne(id);
}
}
So i have a simple UsersDao
public interface UserDao extends JpaRepository<User, Long> {
}
And inside my user controller i want to do something like this :
#RequestMapping(value = "/register",method = RequestMethod.POST)
public void addUser(#RequestBody User user) {
//How do i check if user already exist with email instead of id
// i managed to do this but can i search on something else than the id
User user1 = userDao.findOne(1);
if (user.getEmail().equals(user1.getEmail()))
{
// And how should i give one error to the front end if the email
//already exist I'm using angular js
}
else {
userDao.save(user);
}
}
I also have some extra questions on this topic:
Somethings that are not clear are following. I have done a small tutorial on jpa but there they use:
EntityManager,
EntityTransaction
Note : when using EntityManagerFactory it goes as follow :
EntityManagerFactory emf = null,
//Then they use EntityManagerFactory
emf = Persistence.createEntityManagerFactory("SomeValue")
//where can i get "someValue" When using application .properties
//because in the example they use xml but can't find the right properties in application.properties
Or do i not need to use these in springboot
Sorry for all these question. I really want to get into spring but somethings are still a bit unclear at this point ;)
You can do the following:
Assuming User has an attribute email, define a method in the interface like this to generate a dynamic query:
public interface UserDao extends JpaRepository<User, Long> {
public User findByEmail(String email);
}
Then you can find a user by email. If null is returned, no user with the given email exists. Also, within the User entity class, you can define an annotation to ensure that email is unique like this:
public class User {
....
#Column(unique=true)
String email;
}
You have 2 options:
Use method User findByEmail(String email); in repository interface.
Use method like
#Query("SELECT COUNT(u.id) FROM User u WHERE u.email=:email) Long
countUsersWithEmail(String email);
Than it's obvious how to use rusults of these queries. I would use 2nd choice because of smaller overhead.
this can actually be done in two different ways. although #ufuoma's solution is valid, spring has the exists and Optional which are more flexible. i will give code examples of each.
in the repository interface, we will have these methods
boolean existsByEmail(String email);
Optional<User> findByEmail(String email);
then in your Service class we will have
public Optional<User> findByEmail(String email){
return baseUserRepository.findByEmail(email);
}
public boolean exist(String email){
return baseUserRepository.existsByEmail(email);
}
then in the controller class, we will have
if(baseUserSevice.exists==true){
return "User already exist";
}
or
Optional<baseUserEntity> user=baseUserService.findByEmail(user.getEmail);
if(user.isPresent()){
return "user already exists";
}
the exist method is most preferred since it's faster
you can use any combination with exists keyword
public interface UserDao extends JpaRepository<User, Long> {
public boolean existsByEmail(String email);
}