Mapping DTO with circular self dependency - java

I have a entity user with self dependency. When i Map this entity to DTO I have the problem of circular dependency. .
User.class:
public class User {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(name = "first_name")
private String firstName;
#NotNull
#Column(name = "last_name")
private String lastName;
#JsonBackReference
#ManyToMany(
private List<User> friedns_of = new ArrayList<>();
#ManyToMany(cascade = CascadeType.ALL,
mappedBy = "followers")
private List<User> friends = new ArrayList<>();
UserMapper method in UserMapper:
public static UserResponse toUser(User user) {
UserResponse userResponse = new UserResponse();
userResponse.setId(user.getId());
userResponse.setFollowers(user.getFollowers().stream().map(UserMapper::toUser).toList());
userResponse.setFollowing(user.getFollowing().stream().map(UserMapper::toUser).toList());
return userResponse;
}
When i run the method toUser() I get stackOverFlowError exception caused by the infinite circular dependency. Any advise how to solve this?

One way to resolve this is to model the 'follows' relationship as a separate entity:
#Table(name="user_followers")
public class Follows {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(name = "follower_Id")
private User follower;
#NotNull
#Column(name = "user_id")
private User user;
}
Then you could give your user two one-to-many lists of these entities:
public class User {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "user_id")
private List<Follows> followers;
#OneToMany(mappedBy = "follower_Id")
private List<Follows> following;
}
EDIT: instead of the id field in Follows you could use the user_id and follower_id as a composite primary key using #Embeddable. Omitted here for brevity. See here for more details: https://www.baeldung.com/jpa-many-to-many

Since you already have a DTO of UserResponse, you are on the right path towards a correct solution. My suggestion would be to avoid #ManyToMany on an entity level, and manage followers on a service level.
This means you will have to split relation ManyToMany join column into a separate entity, such as UserFollowsEntity with fields userId and followsUserId. Then remove followers and following lists from your User entity entirely.
Now when creating UserResponse in a service, you will have to
Select the actual user from repository – userRepository.findById(userId)
Select followers – userFollowsRepository.findByFollowsUserId(userId)
Select following – userFollowsRepository.findByUserId(userId)
It is a good practice to try and avoid bidirectional in entities relationships entirely if possible.
EDIT: This will give you two lists: followers and following. You will probably want to know their user names, so what you can do is to merge followers and following lists into one, then extract all user ids from that list. Then query user repository with a list of those IDs, and just attach the required user information to your response model.
Yes it does sound like a bit more work compared to the seeming simplicity of utilizing JPA annotations, but this is the best way to avoid circular dependency as well as decouple the Follower functionality from your user entity.

Related

How to paginate entities with OneToMany relationship with Fetch without warning firstResult/maxResults specified with collection fetch?

I would like to be able to create pagination for pulling all customers from the database (MYSQL), but I encountered a hibernate n+1 problem, which I then solved, but I encountered another problem: 2023-02-09 16:57:04.933 WARN 11660 --- [io-8080-exec-10] o.h.h.internal.ast.QueryTranslatorImpl : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
This problem I tried to solve with EntityGraph, but still nothing. Then I tried to use two Query, which collected the id and then used the IN clause, but this caused a huge sql query, which led to the generation of many "IN" which, with a huge dataset, can be problematic.
I am currently in a quandary and do not know how to solve this problem. I would like the figures to be fetched along with the customers, but I have no idea how to do it in such a way that the pagination works properly
I want to return CustomerDTO who have numberOfCreatedFigures attribute which is mapping from method in customer entity. This method is returning a size of customer figures.
I am using lombok for args/getters/setters. I've been trying to do everything, but nothing seems to fix the issue.
Config class with a mapper
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.createTypeMap(Customer.class, CustomerDTO.class)
.addMappings(mapper -> mapper
.map(Customer::numberOfCreatedFigures, CustomerDTO::setNumberOfFigures));
return modelMapper;
}
Customer class
public class Customer implements UserDetails, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(message = "Your name cannot be blank")
private String name;
#NotBlank(message = "Your name cannot be blank")
private String surname;
#NotBlank(message = "Your login cannot be blank")
private String login;
#NotBlank(message = "Your password cannot be blank")
private String password;
#Enumerated(EnumType.STRING)
private Role role;
private Boolean locked = false;
private Boolean enabled = true;
#OneToMany(mappedBy = "createdBy",
cascade = {CascadeType.MERGE, CascadeType.PERSIST},
fetch = FetchType.LAZY,
orphanRemoval = true)
#ToString.Exclude
private Set<Figure> figures = new HashSet<>() ...;
Figure class
public abstract class Figure implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(updatable = false, insertable = false)
private String figureType;
#Version
private Integer version;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "created_by_id")
#CreatedBy
#ToString.Exclude
private Customer createdBy;
#CreatedDate
private LocalDate createdAt;
#LastModifiedDate
private LocalDate lastModifiedAt;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "last_modified_by_id")
#LastModifiedBy
#ToString.Exclude
private Customer lastModifiedBy;
private Integer numberOfModification = 0 ...;
CustomerDTO class
public class CustomerDTO {
private Long id;
private String name;
private String surname;
private String login;
private Integer numberOfFigures;
private Role role;}
Method from Customer Controller
#GetMapping
public ResponseEntity<Page<CustomerDTO>> listAll(#PageableDefault Pageable pageable) {
return new ResponseEntity<>(customerService.listAll(pageable)
.map(customer -> modelMapper
.map(customer, CustomerDTO.class)), HttpStatus.OK);
}
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Customer.class)
public interface CustomerDTO {
#IdMapping
Long getId();
String getName();
String getSurname();
String getLogin();
#Mapping("SIZE(figures)")
Integer getNumberOfFigures();
Role getRole();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CustomerDTO a = entityViewManager.find(entityManager, CustomerDTO.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<CustomerDTO> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
You could load the customers with the figures relationship eagerly initialized.
For this case, an entity graph would be suitable. You'd need to create a new repository method like this:
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
#EntityGraph(attributePaths = "figures")
List<Customer> findWithFiguresBy(Pageable pageable);
}
Then, you'd need call this repository method when searching instead of the one you are using now. With this approach, your figures relationship can remain lazily fetched (which is generally important as eager fetching is a code smell), but whenever you need to fetch customers with the figures eagerly loaded, you can use this method.
If you want to lear more about entity graphs, I recommend these articles:
JPA Entity Graph by Hibernate maintainer Vlad Mihalcea
JPA Entity Graph by Baeldung
Side note: if you had more than one association which needs to be loaded eagerly, you couldn't use an entity graph for that as it would result in a MultipleBagFetchException. Instead, you would load your parent entities as usual and then collect all ids into a list (say customerIds). Then, you'd need to load all child associations (say figures and otherFigures) by the customer id (JPQL example: select f from Figure f where f.customer.id in :customerIds) and place the figures in a Map<Long, List<Figure> (where the Long parameter is the customer id). Your mapper logic would then need to use the entities from the Maps for the DTOs instead of directly from the parent entity.

Bidirectional OneToMany JPA mapping with eager fetch on both sides worked [duplicate]

This question already has answers here:
Problem with LazyInitializationException
(2 answers)
Closed 4 months ago.
I have 3 tables in the DB and 3 JPA entities respectively in Java application.
#Data
#Entity
public class Fraud {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "fraud_type")
private String fraudType;
#Column(name = "fraud_value")
private String fraudValue;
#OneToMany(mappedBy = "fraud", fetch = FetchType.EAGER)
private List<FraudActionEntity> fraudActions;
}
#Data
#Entity
public class FraudActionEntity {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "fraud_id")
private Fraud fraud;
#ManyToOne
#JoinColumn(name = "action_id")
private Action action;
#Column(name = "enabled")
private Boolean enabled;
}
#Data
#Entity
public class Action {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "attribute_key")
private String attributeKey;
#Column(name = "attribute_value")
private String attributeValue;
}
#Repository
public interface FraudRepository extends JpaRepository<Fraud, Integer> {
public Fraud findByFraudTypeAndFraudValue(String fraudType, String fraudValue);
}
My use case
On a certain type of fraud, I want to traverse all the actions that triggers from that type of fraud and act on them.
Access code
Fraud fraud = fraudRepository.findByFraudTypeAndFraudValue("Type", "Value");
log.info(fraud.getFraudActions().get(0).getAction());
When I above code runs, everything works OK. I get the fraud and fraudActions associations as well, without getting any error.
I was under the impression that as both entities Fraud and FraudActionEntity are fetching each other eagerly, so it should give some error like cyclic fetch/infinite fetch loop, but it didn't!
Why did it work? And when exactly will give it error like cyclic fetch error OR infinite fetch loop error? And if it does give a cyclic fetch error, can we fix it using lazy fetch at #ManyToOne side as given below:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fraud_id")
private Fraud fraud;
Update: A simple and very effective work-around towards the LazyInitializationException is to annotate your method with #Transactional annotation. This will create and maintain the transaction while the method is being executed, thereby allowing your code to make the necessary calls to the DB's lazy init objects. Learn more about it here.
The return type of your JPA repository method should be a List of the Entity object, since the result could be more than one row (that is probably why you are getting the null of the fraud variable).
Regarding the Fetch strategy, you could use Eager on that particular association or maybe other strategies. One possible solution would be to make a second query in case you need the lazy-loaded FraudAction list of objects.
Also, as a side-note avoid using lombok data annotation, and always make sure that you have a NoArgsConstructor in your Entity/DTO classes (in your case #Data adds that by accident since it includes #RequiredArgsConstructor and you do not have any final variables.

How to solve circular reference in JPA associations?

I know this kind of question was answered many times and there are solutions to it, however, none of them worked for me. I tried #JsonIgnore, #JsonIgnoreProperties #JsonManagedReference/#JsonBackedReference, yet still the debugger shows that user has reference to authority, which has reference to user, which has reference to authority, which has reference to user ... Most importantly it doesn't throw any exceptions. However, I still wonder why does this happen, why it doesn't throw exceptions, and does it affect productivity
My entities are simple: there is a User
#Entity
#Table(name = "users_tb")
#NoArgsConstructor
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Authority> authorities;
}
and Authority
#Entity
#Table(name = "authorities_tb")
#NoArgsConstructor
#Getter
#Setter
public class Authority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
}
the code to retrieve users using JpaRepository<T, V>
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = userRepository.findUserByUsername(username).orElseThrow(
() -> new UsernameNotFoundException(ERR_USERNAME_NOT_FOUND));
return new CustomUserDetails(user);
}
The debugger output state before return from loadUserByUsername:
user = {User#10781}
> id = {Long#10784}
> username = "John"
> password = "$2a$10$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG"
> authorities = {PersistentBag#10788} size = 2
> 0 = {Authority#10818}
> id = {Long#10784}
> name = "READ"
> user = {User#10784}
> id = {User#10784}
> username = "John"
> password = "$2a$10$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG"
> authorities = {PersistentBag#10788} size = 2
> 0 = {Authority#10818}
...
Circular dependencies aren't a problem in themselves with JPA.
There are two potential problems with them:
From a software design perspective circular dependencies create a cluster of classes that you can't easily break up.
You can easily get rid of them in your case by making the relationship a unidirectional one and replace the other direction by a query, if you really have to.
Is it worth it in your case?
It depends how closely your two entities are really related.
I'd try to avoid bidirectional relationships, because it is easy to make mistakes, like not keeping both sides of the relationship in sync.
But in most cases I wouldn't sweat it.
Most software as way more serious design issues.
The other problem occurs when something tries to navigate this loop until its end, which obviously doesn't work. The typical scenarios are:
rendering it into JSON (or XML). This is what #JsonIgnore & Co takes care of by not including properties in the JSON.
equals, hashCode, toString are often implemented to call the respective methods of all referenced objects.
Just as the JSON rendering this will lead to stack overflows.
So make sure to break the cycle in these methods as well.
JPA itself doesn't have a problem with cycles because it will look up entities in the first level cache.
Assuming you load an Authority and everything is eagerly loaded, JPA will put it in the first level cache, before checking the referenced user id. If it is present in the cache it will use that instance.
If not it will load it from the database, put it in the cache and then check for the authorities ids in the cache. It will use the ones found and load the rest.
For those it will again check the user id, but those are the user we just loaded, so it is certainly in the cache.
Therefore JPA is done and won't get lost in a cycle.
It will just skip the annotated
Try not to use the Lombok annotation #Getter and #Setter. Then generate manually getters and setters and use #JsonIgnore on the class member field and the getter, and #JsonProperty on the setter.
#JsonIgnore
private List<Authority> authorities;
#JsonIgnore
// Getter for authorities
#JsonProperty
// Setter for authorities
You can simply annotate the duplicated field with #ToString.Exclude
In you case:
#Data // this includes getter/setter and toString
#Entity
#Table(name = "users_tb")
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#ToString.Exclude
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Authority> authorities;
}
#Data
#Entity
#Table(name = "authorities_tb")
#NoArgsConstructor
public class Authority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
}
More info: Lombok #Data and #ToString.Exclude

How to get Count of OneToMany field in JPA Enity?

How can we get the count of OneToMany field of JPA entity as querying count for each parent entity while fetching as a list is costly and there is no way in JPA Repository.
I want to get the number of likes and comments for each PostEntity. The field is Lazy fetch type and if I call likes.size() or comments.size() then it will load all of the comments and likes from database and there can be thousands of comments and likes.
I know I can create a seperate repo for likes and comments to get the counts but while calling method from PostRepository how to get the counts for each and every entity? What is the best and efficient way?
Parent Entity
#Entity
#Table(name = "posts")
#Getter
#Setter
public class PostEntity extends MappedSuperClassEntity<UserEntity> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Nullable
private String title;
#Nullable
private String postText;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private UserEntity user;
#Nullable
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "community_id")
private CommunityEntity community;
#OneToMany(fetch = FetchType.LAZY)
private List<CommentEntity> comments;
#OneToMany(fetch = FetchType.LAZY)
private List<LikeEntity> likes;
#Transient
private int numberOfLikes;
#Transient
private int numberOfComments;
}
I would like to get the likes and comments count for each PostEntity while querying for the list of posts.
My Repo
public interface PostsRepository extends PagingAndSortingRepository<PostEntity, Integer> {
#Query(value = "SELECT P FROM PostEntity P WHERE P.user.id = :userId ORDER BY P.createdDate DESC")
Page<PostEntity> getUserPosts(int userId, Pageable pageable);
#Query(value = "select P from PostEntity P where p.community.id = :communityId order by P.createdDate desc")
Page<PostEntity> getCommunityPosts(int communityId, Pageable pageable);
}
I searched for a lot and someone suggested to use #Formula annotation for custom queries on the entity field but #Formula is hibernate specific and don't know if it works with #Transient field. Is there any JPA specific way to do that as it's a common problem.
You need "LazyCollection" annotation with EXTRA option.
#OneToMany(fetch = FetchType.LAZY)
#LazyCollection(LazyCollectionOption.EXTRA)
private List<CommentEntity> comments;
This annotation would allow to access "size()" without loading.
You can check this article.
https://www.baeldung.com/hibernate-lazycollection
Sometimes, we're only concerned with the properties of the collection, and we don't need the objects inside it right away. For example, going back to the Branch and the Employees example, we could just need the number of employees in the branch while not caring about the actual employees' entities. In this case, we consider using the EXTRA option. Let's update our example to handle this case. Similar to the case before, the Branch entity has an id, name, and an #OneToMany relation with the Employee entity. However, we set the option for #LazyCollection to be EXTRA:
I try to add comment but i have no writing comment access because of reputation so i send an answer.

Make Hibernate ignore fields on certain HTTP requests

I have a class User defined as
#Entity
#Table(name = "users")
#JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class, property = "jsonUUID")
public class User implements Serializable, UserDetails
{
private static final long serialVersionUID = -7035881497059422985L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
protected String firstname;
protected String lastname;
protected String username;
protected ProfessionalCategory professional;
protected String companyName;
#Email
protected String email;
protected String password;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable
(
name = "role_user",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
protected Set<Role> roles;
}
When I perform a GET request on /users/{id} I want Hibernate to fetch the value from all the fields in user and return the "full" object. But when I perform a GET request on /users/ I want to return a list of users containing only firstname and lastname. I do not just want Jackson to ignore certain fields during serialization, I also want Hibernate not to fetch data it does not need (because fetching all the roles for each user can be very costly since I use a join table).
I know I could write my own SQL queries, but then I would loose the benefits of using Hibernate. So is there an elegant way of solving this problem?
The most elegant solution is to use Hibernate criteria and specify two different methods inside your DAO. One method will fetch a single user based on their ID, the other will fetch a list of all users with only first name and last name populated by using a ProjectionList.
public List<User> getAllUsers() {
Criteria query = sessionFactory.getCurrentSession().createCriteria(User.class);
query.setProjection(Projections.projectionList()
.add(Projections.property("firstName"), "firstName")
.add(Projections.property("lastName"), "lastName"))
.setResultTransformer(Transformers.aliasToBean(User.class));
return query.list();
}
The above code causes Hibernate to only fetch the firstName and lastName fields from the database, and then map the results back to your User class using the ResultTransformer. This method is less than ideal, because it is confusing that all the fields aren't populated.
The ideal solution would be to lazily load your collection of roles, so that Hibernate only loads it on request. For more information on how you can set this up, refer to my Q&A here.

Categories