I have a controller method which retrieves an User, then I've got mapped their UserConfig, and then with that UserConfig I retrieve the MainBrands (lazy collection of UserConfiguration).
Let me clarify this:
User Entity:
#Entity
#Table(name = "app_user")
public class User extends BaseEntity {
private UserConfig userConfig;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
public UserConfig getUserConfig() {
return userConfig;
}
//more props..
}
UserConfig Entity:
#Entity
#Table(name = "user_config")
public class UserConfig extends BaseEntity {
private Set<MainBrand> mainBrands;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(...)
public Set<MainBrand> getMainBrands() {
return mainBrands;
}
//more props..
}
And my UserService:
public interface UserService {
public User getById(Long id);
}
So my question is about "best practices" of transactional annotations. I have read more than once, that put #Transactional at Controller level, is bad practice. But in this case I wanna do at Controller:
#RequestMapping(method = RequestMethod.GET, value = "/")
public ModelAndView getMainPage(Long userId) {
ModelAndView = new ModelAndView("/home");
//do stuff
User user = userService.getById(userId);
//some stuff with user
modelAndView.addObject("username", user.getUsername());
//...
List<String> brandsNames = new ArrayList<>();
for(MainBrand mainBrand : user.getUserConfig().getMainBrands()){
brandsNames.add(mainBrand.getName());
}
}
That will fail if don't put the #Transactional annotation at Controller level, because of LazyInitializationException.
So, that's the choices that I've thinked out:
1) With the user make a call to an "UserConfigService" (it's not created now) like userConfigService.getUserConfigByUserId(userId): that's make me think that if I already have the binding at User class, why I would call it again? And I am just creating a new service only for this method.
2) Put the #Transactional annotation at controller level: which makes another problem for my, but it doesn't care in this post.
3) Call the getUserConfig() & getUserConfig().getMainBrands() at UserService so then the collection get initialized: don't like because whenever I use the getById it will initialize the collection even if I do not need it.
So what it would be a good practice for this case? On internet there are always perfect and beautiful examples, but when we start to give some business logic to the project, it turns hard to have a clean code.
Thanks, and sorry for my english.
LazyInitializationException is not related to transactional , it is related to relationship between objects, if your object has a lazy relation,you must fetch your MainBrands objects in your userService.getById(userId) query method before you return your user.
Transactional annotation must be in service class, you can create as many service classes as you need.
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 development project using Spring Data JPA and MapStruct to map between Entities and DTOs. Last week I decided it was time to address the FetchType.EAGER vs LAZY issue I have postponed for some time. I choose to use #NamedEntityGraph and #EntityGraph to load properties when needed. However I am stuck with this LazyInitializationExeption problem when doing the mapping from entity to dto. I think I know where this happens but I do not know how to get passed it.
The code
#NamedEntityGraph(name="Employee.full", ...)
#Entity
public class Employee {
private Set<Role> roles = new HashSet<>();
}
#Entity
public class Role {
private Set<Employee> employees = new HashSet<>();
}
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
#EntityGraph(value = "Employee.full")
#Override
Page<Employee> findAll(Pageable pageable);
}
#Service
public class EmployeeService {
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest); // ok
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp, new CycleAvoidMappingContext()); // this is where the exception happens
return dtos;
}
}
// also there is EmployeeDTO and RoleDTO classes mirroring the entity classes
// and there is a simple interface EmployeeMapper loaded as a spring component
// without any special mappings. However CycleAvoidingMappingContext is used.
I have tracked down the LazyInitializationException to happen when the mapper tries to map the roles dependency. The Role object do have Set<Employee> and therefore there is a cyclic reference.
When using FetchType.EAGER new CycleAvoidingMappingContext() solved this problem, but with LAZY this no longer works.
Does anybody know how I can avoid the exception and at the same time get my DTOs mapped correctly?
The problem is that when the code returns from findAll the entities are not managed anymore. So you have a LazyInitializationException because you are trying, outside of the scope of the session, to access a collection that hasn't been initialized already.
Adding eager make it works because it makes sure that the collection has been already initialized.
You have two alternatives:
Using an EAGER fetch;
Make sure that the entities are still managed when you return from the findAll. Adding a #Transactional to the method should work:
#Service
public class EmployeeService {
#Transactional
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest);
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp, new CycleAvoidMappingContext());
return dtos;
}
}
I would say that if you need the collection initialized, fetching it eagerly (with an entity graph or a query) makes sense.
Check this article for more details on entities states in Hibernate ORM.
UPDATE: It seems that this error happens because Mapstruct is converting the collection even if you don't need it in the DTO.
In this case, you have different options:
Remove the field roles from the DTO. Mapstruct will ignore the field in the entity because the DTO doesn't have a field with the same name;
Create a different DTO class for this specific case without the field roles;
Use the #Mapping annotation to ignore the field in the entity:
#Mapping(target = "roles", ignore = true)
void toDTO(...)
or, if you need the toDTO method sometimes
#Mapping(target = "roles", ignore = true)
void toSkipRolesDTO(...) // same signature as toDTO
The following is just an exmaple for context not the actual implementation. Using Spring MVC, I have the following Model which has validation based on annotations.
#Entity
public class Customer {
#Id private int id;
#NotNull private String name;
}
And the following DTO used to map the data received in the request's body in the Controller's createNewCustomer function.
public class CustmerDTO{
private String name;
}
In my Controller I am using the modelMapper to convert the customerDTO to a new domain model Customer object. Based on the #NotNull annotation, if the name property of the recieved object (customerDTO) is empty the ConstraintViolationException is thrown.
public class CustomerController {
#Autowired private CustomerService customerService;
#Autowired private ModelMapper modelMapper;
#PostMapping(value = "/customer")
public Customer createNewCustomer (#RequestBody CustomerDTO customerDTO) {
try {
Customer newCustomer = modelMapper.map(customerDTO, Customer.class);
return customerService.saveCustomer(newCustomer);
}
catch (ConstraintViolationException e) {
throw new CustomerMissingInformation();
}
}
}
As you can see here I handle validation for the Customer in the Controller, which by definition is not a good habit, as Controllers in MVC are part of the presentation layer, and should have no clue about how to perform data validation, also I want to keep my controllers as light as possible.
Can I keep this design or is there any good approach to move my validation in the Service layer while keeping validation annotations and also allowing the Controller to be able to receive representation objects(DTOs) and convert them to domain models?
Looking more into this, I came to the following conclusion. Validation for persisting an object should happen as soon as possible and at property level, as you don't want your persistence layer or any mappers that you might be using deal with null when it is not expected. Everything that goes past property validation, shall be approached from a contextual validation perspective, and be passed to methods that hold logic for a given context.
Is this order valid to be filled, is this customer valid to check in to the hotel. So rather than have methods like isValid have methods like isValidForCheckIn.
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 have an entity named Commercial. I have an Category entity where the list of commercial categories are hold. For each category there is an separate entity extending Commercial(like RestaurantCommercial, PhotoStudioCommercial etc. total up to 20 entities) with JOINED inheritance strategy.
Commercial entity holds up general properties like title, text contactnumber of some company's commercial, while RestaurantCommercial and PhotoStudioCommercial holds additional specific properties concerned with that category.
The problem is that writing a separate dao and controller for each entity is a bit plenty of work, so I am searching for a neat way to handle this issue.
I need an unified controller and may be the DAO for handling the form control and persisting new instances of the entities that extend Commercial.
Here is approximately what I was thinking about:
#RequestMapping(value={"/vendor/commercial/{categoryName}/new"}, method=RequestMethod.GET)
public String showNewCommercialForm(#PathVariable("categoryName") String categoryName,
Map<String, Object> map) {
Category category = categoryDAO.getCategoryByServiceName(categoryName);
Class clazz = Class.forName(category.getClassName()); //here className is smth like domain.commercial.RestaurantCommercial
map.put("commercialInstance", clazz.newInstance());
//separate view for each category of commercial
return "vendor/commercial/"+categoryName+"/new";
}
And I was thinking for a similar controller for saving form data even if I would have to write a sperate binder for this stuff.
So the question is: What would you suggest to handle this issue or what would be the best practice if you had already faced similar need(Generics, Reflection or smth else)? Or if that would be worthy or not and why?
Thanks in advance.
I create a Dao interface for such cases:
public interface Dao<T extends Commercial> {
T getAll();
}
After that an abstract Dao implementation, for example hibernate based:
public CommonHibernateDao<T extends Commercial> implements Dao<T>
{
private final Class<T> entityType;
protected CommonHibernateDao(Class<T> entityType) {
this.entityType = entityType;
}
public List<T> getAll() {
// hibernate get all implementation
}
}
And RestaurantCommercial Dao interface and implementation:
public interface RestaurantCommercialDao extends Dao<RestaurantCommercial> {
}
public class HibernateRestaurantCommercialDao extends CommonHibernateDao<RestaurantCommercial> implements RestaurantCommercialDao {
public HibernateRestaurantCommercialDao() {
super(RestaurantCommercial.class);
}
}
All implementation goto CommonHibernateDao. In it's subclasses only constructor calling neaded. Basically you can do it with reflection but as for me it is not clear.
For controller (something like RESTfull API):
#Controller
public class YourController() {
#RequestMapping(value = "/restapi/{entityType}")
public String postEntity(HttpServletRequest request, #PathVariable(value = "entityType") String entityType) {
// If enetity name will be packegae + class name
Object beanInstance = Class.forName(entityName);
ServletRequestDataBinder binder = new ServletRequestDataBinder(beanInstance);
binder.bind(request);
// Here by type of entity you can get DAO and persist
}
}
If form input names will be same to your bean names - binding will do all automatically.