I want to be able to dynamically load the relations of my entity, depending on which RestService got called.
Entity classes:
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
private Buyer buyer;
// some more attributes
}
#Entity
public class Buyer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// some more attributes
}
RestController class:
#GetMapping
public Iterable<Order> getAll() {
// here I want JPA to NOT load the buyers for the order
return orderRepository.findAll();
}
#GetMapping("/{id}")
public Order get(#PathVariable("id") String id) {
// here I want JPA to load the buyers for the order
return orderRepository.findById(Long.parseLong(id)).orElseThrow();
}
None of the two fetchtypes LAZY and EAGER or json annotations (like #JsonIgnore, #JsonIdentityInfo, #JsonManagedReference and #JsonBackReference) seem to make this possible as far as I understood and tried.
If this is not possible, maybe someone can explain how to solve this problem then. On the one hand I sometimes need those relations in my frontend to display some values and on the other hand when I always load them I get huge performance problems or infinity recursions.
I don't think JPA supports your use case directly.
One option is to create the same entity twice - one with eager and the other with lazy. Switch them in the methods.
Another option is to use a DTO (Data Transfer Object) as the response, instead of the entity class itself. You will have to write a mapper logic to convert an entity to DTO though.
Related
I am doing a REST API using springboot and JPA.
I am trying to lazy fetching an entity in a One to Many relationship. Teacher to courses.
I can see the sql statements done by JPA as I have the debuging option on.
In the controller, when calling a path all works great, but I can see that JPA is executing two queries. One for the teacher and another one for its courses. As I know, the lazy loading does not query until the data is required and I am not requiring it.
I have checked and conirmed that in the controller, when I retrieve the teacher data JPA does not query for the courses, but AFTER the return statement of the controller, somewhere, the courses are required and it loads everything when I call the teacher info from postman with a GET call.
It seems as if the LAZY loading is working correctly, but after the controller JPA loads the course list. If I do the EAGER fetching everything is loaded before the return statemnt.
I am not writing any code as I guess the question is more theorical than practical.
Does anyone know how this works?
Thank you so much!!!!
EDIT:
Teacher table
#Entity
#Table(name="profesores")
public class Profesor implements Serializable{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="nombre")
private String nombre;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "profesor_id", referencedColumnName = "id")
private List<Curso> cursos = new ArrayList<>();
}
Course Table
#Entity
#Table(name = "curso")
public class Curso implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long curso_id;
private String nombre;
#Column(name="profesor_id")
private Long profesorId;
}
Controller
#GetMapping("/profesor/{id}")
public ResponseEntity<?> getProfesor(#PathVariable(value = "id") Long id){
Profesor p = profesorService.findById(id);
if(p!=null) {
ResponseEntity<?> re = new ResponseEntity<>(p, HttpStatus.OK);
//Just one query executed. I don't know the courses yet
return re;
}
else {
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
}
After the return re; statement, somewhere, the courses are retrieved and JPA queries for them. I don't know what does the controller call, as I do directly from PostMan.
After returned Entity Profesor is serialized for response when serializer try to access courses to serialized for response then JPA load courses also. To solve this issue, You can create a response class for response (without courses field)
public class ProfesorResponse {
private Long id;
private String number;
...constructor
}
then map your entity in response object and return it.
Profesor p = profesorService.findById(id);
ProfesorResponse response = new ProfesorResponse(p.getId(), p.getNumber());
I'm writing a Spring Application, which has two entities that are related by a one to many relationship, lets call them mother and kid.
When I create a mother entity via POST request, I want a kid entity be created automatically. Using the #OneToMany and #ManyToOne annotations, that works fine. At least, as long as I provide the kid information within the MotherService.
Here is my code
Mother.java
#Entity
#Table(name="mother")
public class Mother{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#OneToMany(mappedBy = "mother", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Kid> kidList = new ArrayList<>();
//constructor, getter, setter
private void addKid(Kid kid) {
this.kidList.add(kid);
kid.setMother(this);
}
}
Kid.java
#Entity
#Table(name="kid")
public class Kid{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "mother_id", nullable=false)
private Mother mother;
//constructor, getter, setter
}
MotherController.java
#RestController
#RequestMapping("mothers")
public class MotherController {
#Autowired
private MotherService motherService;
MotherController(MotherService motherService) {
this.motherService = motherService;
}
#PostMapping
Mother createMother(#RequestBody Mother mother) {
return this.motherService.createMother(mother);
}
}
MotherService.java
#Service
public class MotherService {
private MotherRepository motherRepository;
#Autowired
public MotherService (MotherRepository motherRepository) {
super();
this.motherRepository= motherRepository;
}
public Mother createMother(Mother mother) {
Kid kid = new Kid("Peter");
mother.addKid(kid);
return this.motherRepository.save(mother);
}
}
The repositories for mother and kid extend the JpaRepository without any custom methods so far.
My POST request is something like (using Postman)
{
"name":"motherName"
}
Now a mother is created with a name "motherName" and a kid with the name of "Peter".
My idea: Using a DTO
I now try to implement a DTO, that contains the mothers name and the kids name, map this information in the MotherService to the entities and save them via the corresponding repository, so I can define both names in the POST request.
motherDto.java
public class mother {
private String motherName;
private String kidName;
//getter, setter
}
So when I POST
{
"motherName":"Susanne",
"kidName":"Peter"
}
or even better
{
"mother": {
"name":"Susanne"
},
"kid": {
"name":"Peter"
}
}
a mother with name Susanne and a kid with name Peter are created.
My question is
How do I map a DTO to two entities?
Or do I not get something right? Is there an easier way to achieve my goal?
I know this is old and probably long solved, but let me offer a different take on the subject.
Another option would be to design a DTO solely for the purpose of creating the two entities you mentioned. You could call this MotherChildCreationDTO or something like that so the name already conveys its use and maybe create a REST-target consuming the DTO.
Asymmetric DTOs (receiving and sending) are an established pattern, and the DTOs are closely coupled to the REST controller any way.
First solution:
You can don't use DTO and send your JSON with same structure of Mother and kids and Jackson in Spring MVC deserialize it correctly for you.
{
id:2,
name:'sarah'
kidList:[{id:546,name:'bob'},{id:478,name:'tom'}]
}
Second solution:
If you want to different structure in JSON and Models and you can use Jackson annotation like #JsonProperty or #JsonDeserialize. Read this like for more information.
Third solution:
You can use DozzerMapper for complex mapping between your DTO and your Model. you define XML's file for mapping each model to your DTO and DozzerMapper map your DTO to your models.Read this link for more information.
You have 2 ways:
Map DTO to entities by yourself. In this case, you should create custom mapper and define how exactly DTO should be converted to entity. Then just inject and use your custom mapper in service.
Use one of existing mapper libraries. For example, good candidates are MapStruct and ModelMapper. You can find usage examples in corresponding getting started guides.
I have the following simple application
Users Entity
#Entity
public class Users implements Serializable {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
private Set<UserRoleUser> userRoleUser;
// GETTERS AND SETTERS
}
UserRole Entity
#Entity
public class UserRole implements Serializable {
#Id
#GeneratedValue
private long id;
private String roleName;
#OneToMany(mappedBy = "userrole", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<UserRoleUser> userRoleUser;
// GETTERS AND SETTERS
}
UserRoleUser Many to many resolver class
#Entity
public class UserRoleUser implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "fk_userId")
private Users user;
#ManyToOne
#JoinColumn(name = "fk_userroleId")
private UserRole userrole;
// GETTERS AND SETTERS
}
UserRoleUserRepository
#Repository
#Transactional
public interface UserRoleUserRepository extends JpaRepository<UserRoleUser, Long>, QueryDslPredicateExecutor<UserRoleUser>{
}
Main Application class
#SpringBootApplication
#Configuration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
UserRoleUserRepository userRoleUserRepository = context.getBean(UserRoleUserRepository.class);
Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));
for (UserRoleUser userRoleUser : findAll) {
userRoleUserRepository.delete(userRoleUser);
}
}
}
On running the main application, the database records in the UserRoleUser table are not being deleted. What could be the issue? I am using Spring Data and QueryDsl.
I have also tried putting the delete functionality on a Controller but still doesn't work.
#RestController
#RequestMapping("/api")
public class DeleteController {
#Autowired
UserRoleUserRepository userRoleUserRepository;
#GetMapping("/delete")
public String delete() {
Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));
for (UserRoleUser userRoleUser : findAll) {
userRoleUserRepository.delete(userRoleUser);
}
return new Date().toString();
}
}
If you need to use the given methods provided by CrudRepository, use the JpaRepository.deleteInBatch(). This solves the problem.
The problem is the entities are still attached and will not be deleted until they become detached. If you delete by their id instead of the entity itself, it will delete them.
One thing I noticed is you are deleting the users one at a time which could lead to a database performance hit as the query will be recreated each time. The easiest thing to do is to add all the ids to a set then delete the set of ids. Something like this:
Set<Integer> idList = new HashSet<>();
for (UserRoleUser userRoleUser : findAll) {
idList.add(userRoleUser.getId());
}
if (!idList.isEmpty()) {
userRoleUserRepository.delete(idList);
}
then in your repository add the delete method
#Modifying
#Query("DELETE FROM UserRoleUser uru WHERE uru.id in ?1")
#Transactional
void delete(Set<Integer> id);
The reason why the child objects (UserRoleUser) are not being deleted upon userRoleUserRepository.delete(userRoleUser) call is that each UserRoleUser points to a Users which in turn holds a #OneToMany reference Set<UserRoleUser> userRoleUser.
As described in this StackOverflow answer, what your JPA implementation (e.g. Hibernate) effectively does is:
The cache takes note of the requested child exclusion
The cache however does not verify any changes in Set<UserRoleUser>
As the parent #OneToMany field has not been updated, no changes are made
A solution would go through first removing the child element from Set<UserRoleUser> and then proceed to userRoleUserRepository.delete(userRoleUser) or userRepository.save(user)
In order to avoid this complication two answers have been provided:
Remove element by Id, by calling userRoleUserRepository.deleteById(userRoleUser.getId()) : in this case the entity structure (and therefore the parent) is not checked before deletion. In the analog case of deleteAll something more convoluted such as userRoleUserRepository.deleteByIdIn(userRoleUserList.stream().map(UserRoleUser::getId).collect(Collectors.toList())) would have to be employed
Convert your CrudRepository to a JpaRepository and use its deleteInBatch(userRoleUserList) method. As explained in this article and this StackOverflow answer the deleteInBatch method tries to delete all records at once, possibly generating a StackOverflow error in the case the number of records is too large. As repo.deleteAll() removes one record at a time this error it minimizes this risk (unless the call is itself inside a #Transactional method)
According to this StackOverflow answer, extra care should be used when recurring to deleteInBatch as it:
Does not cascade to other entities
Does not update the persistence context, requiring it to be cleared (the method bypasses the cache)
Finally , as far as I know , there is no way this could be done by simply calling userRoleUserRepository.delete(userRoleUser) without first updating the parent object. Any updates on this (whether by allowing such behaviour through annotations, configuration or any other means) would be a welcome addition to the answer.
Suppose I have these 2 entity classes:
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
List<Order> orderList;
/* getters and setters */
}
#Entity
public class Order {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#ManyToOne
private User user;
/* getters and setters */
}
And this repository:
public interface OrderRepository extends CrudRepository<Order, Long> {
List<Order> findByUser(User user);
}
Now if I have user which is a managed entity and want to get list of orders that belong to that user I can do that in 2 ways:
Method 1:
#Autowired
OrderRepository orderRepository;
List<Order> orderList = orderRepository.findByUser(user);
Method 2:
List<Order> orderList = user.getOrderList();
Is there any difference between these 2 methods? When should I use method 1 and when should I use method 2?
Thanks
Depending on the exact configuration of fetch strategy and lazy loading, there might be a difference in when and how the data is actually loaded from the database. But this is not what should drive your decision, which access-path you are using.
A very central idea behind Spring Data is that of the Aggregate as described in Domain Driven Design. There should be one Repository per Aggregate. So the question becomes: Are User and Order part of the same Aggregate? If one assumes that you adhered to the idea behind Spring Data, the fact that there are repositories for User and Order means, they are both Aggregate Roots and therefore can't be part of the same Aggregate. Also, therefore I would not use the reference and actually remove it (and I most certainly would remove the Cascade.All
But you obviously didn't make the design decision based on that argument, so you might as well decide that both shall be part of the same Aggregate and drop one of the repositories.
I have a JPA entity that contains collections of another entities instances.
I need to remove some of the instances from the collection and change other stuff, just for View and I don't want to change my database content.
What is the best way to do it?
Make a clone of my object and work with it.
Remove lazy load (or get all that I need from this main bean). Then close hibernate session, and work with the detached object.
Anything else?
UPDATE
My bean
#Entity
#Table(name = "client")
public class Client extends AbstractPersistentEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CLIENTS_SEQ")
#SequenceGenerator(name = "CLIENTS_SEQ", sequenceName = "clients_seq")
private Integer id;
#NotEmpty
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "clientId")
private Collection<ContactPhones> contactPhonesCollection;
}
And I want to remove some of ContactPhones for view. But it can be much complicated, may be in ContactPhones will be another collection and I want to remove it. Something like that.
If you don't want to actually remove any row from the database, in my opinion the best choice is to detach the entity from the session and work with it as with any other Java object.