iam working on an app where the association between entities is as follows . Here comment is the owner and iteration and user are the inverse tables . The requirement is -
A user can have many comments similarly a comment can be given by multiple users .
Also an iteration can have multiple comments and a single comment can belong to multiple iterations .
The code is as follows -
Comments entity -
#Table(name="RCOMMENTS")
#Entity
public class RComment{
#Id
#Column(name="COMMENTID")
#GeneratedValue(strategy=GenerationType.AUTO)
private long commentid;
#Column(name="DESCRIPTION")
private String description;
#Column(name="TYPE")
private String type;
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(name = "COMMENTS_USERS", joinColumns = { #JoinColumn(name = "COMMENTID") },
inverseJoinColumns = { #JoinColumn(name = "USERID") })
private Set <RUsers> users = new HashSet<RUsers>();
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(name = "COMMENTS_ITERATIONS", joinColumns = { #JoinColumn(name = "COMMENTID") },
inverseJoinColumns = { #JoinColumn(name = "ITERATIONID") })
private Set <RIteration> iteration = new HashSet<RIteration>();
Users-
#Entity
#Table(name = "RUSER")
public class RUsers {
#Id
#Column(name = "USERID")
#GeneratedValue(strategy=GenerationType.AUTO)
private long userid;
#Column(name = "USERNAME")
private String username;
#Column(name = "PASSWORD")
private String password;
#ManyToMany(mappedBy="users")
private Set <RComment> comment ;
Iteration -
#Table(name = "RITERATION")
#Entity
public class RIteration {
#Id
#Column(name = "iterationid")
#GeneratedValue(strategy=GenerationType.AUTO)
private long iterationid;
private Date startdate;
private Date enddate;
private long iter;
#ManyToMany(mappedBy="iteration")
private Set <RComment> comment = new HashSet<RComment>();
Test code -
RUsers user = (RUsers) session.get(RUsers.class, this.userid);
Set<RComment> comments = user.getComment();
Now the issue that iam facing is the mapping tables are empty .
Also user.getComment() yields empty set
Before i start my web app my db entity tables prepopulated with dummy values . But during run time when i debug the code user.getcomment returns empty set .
Could anyone please help me out what could be the issue .
If you have an bidirectional relationship between your Entities, then Hibernate will pay attention read only one of the two sides when it stores them in the database.
The side of the relation that is the important one, is the one that's ManyToMany annotation does NOT contain the mappedBy variable. And that is the side where you need to assign the value:
RUsers user = ...
RComment comment = ...
comment.users.add(user); <--- that is the assignment hat hibernate will store!
user.comments.add(comment); <--- that assigment will NOT been stored by hibernate, but hibernate will load it from database
Related
I want to convert the following mapping on courseDetails to manyToMany.
This is because I get an exception Found shared references to a collection: com.xyz.courseDetails and I assume this happens because the relation is not actually one to many in the database, since there are some course_detail tuples that has multiple courses.
#Entity
#Table(name = "courses")
public class Course
{
#Column(name = "course_detail_id")
private Long extendedCourseDetailId;
...
#OneToMany(fetch = FetchType.LAZY, targetEntity = CourseDetail.class, cascade = CascadeType.ALL)
#JoinColumn(name="id", referencedColumnName="course_detail_id")
private List<CourseDetail> courseDetails = new ArrayList<>();
}
Simply changing the annotation to ManyToMany does not work, JPA somehow couldn't find the related columns. Why? How can I do this?
What do you think of this :
Let's assume the entity CourseDetail has as ID :
public class CourseDetail
{
#Id
#Column(name = "cd_id")
private Long courseDetailId;
So this non tested code might help you.
where the table "course__course_detail" will be automatically created to hold the relationship with 2 columns : "course_id" and "coursedetail_id".
#Entity
#Table(name = "courses")
public class Course
{
#Id
#Column(name = "c_id")
private Long courseId;
// #Column(name = "course_detail_id") // I comment because I dont understand the purpose
// private Long extendedCourseDetailId;
...
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "course__course_detail",
joinColumns = #JoinColumn(name = "course_id", referencedColumnName="c_id"),
inverseJoinColumns = #JoinColumn(name = "coursedetail_id", referencedColumnName="cd_id"),
)
private List<CourseDetail> courseDetails = new ArrayList<>();
}
PS: NOT TESTED
Feel free to tell me more in comments.
I want to combine column of different tables in one entity (object) but im getting a column null even it's not null.
I have two entities called Operation and CanceledOperation:
Operation:
#Entity
#Table(name = "OPERATIONS")
public class Operation{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#Column(name = "Date")
private Timestamp date;
#Transient
private String message;
// Other attributes and getters & setters
}
CanceledOperation:
#Entity
#Table(name = "CANCELED_OPERATIONS")
public class CanceledOperation{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "OPERATION_ID", nullable = false)
private Operation operation;
#Column(name = "RAINSON")
private String raison;
//getters & setters
}
I'm trying to join the operation and canceledIperation in order to display the raison for the canceled operation and null non canceled.
this is my native query in my repository:
#query(value = "SELECT op.* , co.raison as raison FROM operations op LEFT JOIN canceled_operations co ON op.ID = co.OPERATION_ID", countQuery = "SELECT count(*) FROM operations op LEFT JOIN canceled_operations co ON op.ID = co.OPERATION_ID")
Page<Operation> getAllOperations(Pageable pageable);
and this is how i call my repository:
final Page<Operation> operations= operationRepository.getAllOperations(pageable);
I tried many solution namely using the Object[] instead of Operation and I have also added an attribute called raison in the operation entity with #transient annotation, but even that I still get the raison is null.
can you please give me a hint to solve this issue.
thank in advance.
Well I found an easy way to do it, instead of doing a complicated query, I have used findAll() of each table then I affect the raison attribute from CanceledOperation to the message attribute of Operation using stream and map the two received lists from repositories.
In the CanceledOperation entity, can you give referencedColumnName = "ID" and try?
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "OPERATION_ID",referencedColumnName = "ID", nullable = false)
private Operation operation;
Note: Please don't mark it as duplicate before reading completely
Case : I have three classes named User, Post and Tag
User <-> Post (OneToMany Bi-directional)
Post <-> Tag (ManyToMany)
Solution I want :
Mapping should work like If i call getUserById, I should get posts
related to the user and tags related to the posts.
Same with Posts and Tags, If I call getPostById I should get the
user and tags and if I call getTagByName I should get all posts
related to tags
Solutions I have tried :
#JsonMappedReference, #JsonBackReference - Worked for read operations but failed for creating/writing
#JsonIdentityInfo - Did not worked
#JsonIgnore - Worked but I don't want to ignore as am not getting desired solution mentioned above
#ToString.Exclude, #EqualsAndHashCode.Exclude - Did not worked
Also tried with my own getters and setters and #ToString methods - Did not worked either
This is a springboot project
Here are my classes
User.java
#Entity
#Table(name = "user")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "username")
private String userName;
#Column(name = "password")
private String password;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "created_at")
#CreationTimestamp
private Timestamp createdAt;
#Column(name = "updated_at")
#UpdateTimestamp
private Timestamp updatedAt;
#OneToMany(
mappedBy = "user",
cascade = {CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private List<Post> posts;
Post.java
#Entity
#Table(name = "post")
#Data
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne(
cascade = {CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name = "user_id")
private User user;
#Column(name = "title")
private String postTitle;
#Column(name = "content")
private String postContent;
#Column(name = "status")
private String postStatus;
#Column(name = "created_at")
#CreationTimestamp
private Timestamp createdAt;
#Column(name = "updated_at")
#UpdateTimestamp
private Timestamp updatedAt;
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
#JoinTable(
name = "post_tag",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
private List<Tag> tags;
Tag.java
#Entity
#Table(name = "tag")
#Data
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String tagName;
#Column(name = "created_at")
#CreationTimestamp
private Timestamp createdAt;
#Column(name = "updated_at")
#UpdateTimestamp
private Timestamp updatedAt;
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
#JoinTable(
name = "post_tag",
joinColumns = #JoinColumn(name = "tag_id"),
inverseJoinColumns = #JoinColumn(name = "post_id"))
private List<Post> posts;
So with above classes I ran into infinite loop problem, If I use getUserById post object is user object is showing Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception. If call getAllPosts OR getAllTags tags object in post object is showing the same error or vice versa
I had a similar problem with #OneToMany and #ManyToOne relations. I'm simply going to explain the route I took to fix my code. Hopefully, it will make a difference.
Add #JsonBackReference to your user class, this should resolve your loop issue. Also remove cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH} line from all of your classes. Cascades were the reason I was unable to perform update method.
Please try the codes I provided below. You should be able to see Users as a part of the Post output stream when you test it. Also, you should be able to list users without encountering the loop problem. Sadly, I'm not so sure about the many-to-many relation since I have no experince on it.
User.java
#Entity
#Table(name = "user")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "username")
private String userName;
#Column(name = "password")
private String password;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "created_at")
#CreationTimestamp
private Timestamp createdAt;
#Column(name = "updated_at")
#UpdateTimestamp
private Timestamp updatedAt;
#JsonBackReference
#OneToMany(mappedBy = "user")
private List<Post> posts;
Post.java
#Entity
#Table(name = "post")
#Data
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#Column(name = "title")
private String postTitle;
#Column(name = "content")
private String postContent;
#Column(name = "status")
private String postStatus;
#Column(name = "created_at")
#CreationTimestamp
private Timestamp createdAt;
#Column(name = "updated_at")
#UpdateTimestamp
private Timestamp updatedAt;
#ManyToMany
#JoinColumn(name = "tag_id")
private List<Tag> tags;
Tag.java
#Entity
#Table(name = "tag")
#Data
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String tagName;
#Column(name = "created_at")
#CreationTimestamp
private Timestamp createdAt;
#Column(name = "updated_at")
#UpdateTimestamp
private Timestamp updatedAt;
#ManyToMany(mappedBy = "tags"))
private List<Post> posts;
I have removed all the mappings, I can get user, post and post tags in three different calls and its working fine i have tried all the mapping explained or shown above but am getting error while read/write operations and to avoid all those i have made the change so that it does not have any mapping
you can use data transfer object to swow that field what you neen, maybe you can replace array of users in json to array users ids like this
#Transactional
public PostDto savePost(Post post) {
Post save = postRepository.save(post);
return Optional.of(save).map(this::transformPostEntityToDto).orElse(null);
}
#Override
public List<PostDto> getAllPosts() {
return postRepository.findAll().stream()
.map(this::transformPostEntityToDto).collect(Collectors.toList());
}
private PostDto transformPostEntityToDto(Post post) {
return PostDto.builder()
.id(post.getId())
.createdAt(post.getCreatedAt())
.postContent(post.getPostContent())
.postStatus(post.getPostStatus())
.postTitle(post.getPostTitle())
.tags(Objects.nonNull(post.getTags())
? post.getTags().stream().map(this::transformTagEntityToDto).collect(Collectors.toList())
: Collections.emptyList())
.updatedAt(post.getUpdatedAt())
.user(Optional.ofNullable(post.getUser()).map(this::transformUserEntityToDto).orElse(null))
.build();
}
private TagDto transformTagEntityToDto(Tag tag) {
return TagDto.builder()
.id(tag.getId())
.createdAt(tag.getCreatedAt())
.tagName(tag.getTagName())
.updatedAt(tag.getUpdatedAt())
.postsIds(Objects.nonNull(tag.getPosts()) ? tag.getPosts().stream().map(Post::getId).collect(Collectors.toList())
: null)
.build();
}
private UserDto transformUserEntityToDto(User user) {
return UserDto.builder()
.createdAt(user.getCreatedAt())
.firstName(user.getFirstName())
.id(user.getId())
.lastName(user.getLastName())
.password(user.getPassword())
.updatedAt(user.getUpdatedAt())
.userName(user.getUserName())
.postsIds(Objects.nonNull(user.getPosts()) ? user.getPosts().stream().map(Post::getId).collect(Collectors.toList())
: null)
.build();
}
it is flexiable but requare several dto classes for views
Regarding the LazyInitializationException, it has to do with a 'fetch' mode and nothing to do with Jackson serialization. Take a look at this question: Default fetch type for one-to-one, many-to-one and one-to-many in Hibernate. Solutions for that are either setting loading to eager: https://www.baeldung.com/hibernate-lazy-eager-loading, or fetch joins: https://www.baeldung.com/jpa-join-types#fetch. If you are using hibernate without JPA abstraction over it, you can also take a look at this: Hibernate: best practice to pull all lazy collections
Regarding the infinite recursion problem when using jackson, take a look at: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion. #JsonManagedReference and #JsonBackReference are a nice option, however you cannot have a situation where you use same classes to get user with posts and same classes to get post with its user. Either you have to "ignore" user when serializing post or "ignore" post when serializing user. Otherwise you always get the infinite loop. Solution for that is using DTOs (Data Transfer Object)
Another important thing is that when using bidirectional mappings with JPA, you have to yourself set the "reference to the owner" of the collection (in this case, when adding a post to a user, make sure you also set the user reference on the post object to that user). In this example you see how in the class Post in line 22 you set the reference on the currently being added PostComment object for the post attribute to this
Let's make it work step by step.
Since you're using Spring Boot, I suspect these entities are returned directly from the REST controller(s). So when you try to return a user and call getUserById() it does the following:
Hibernate fetches the user by id and sets the lazy collection of posts
Spring is trying to create a JSON of this user using Jackson, which is calling all the getters
since posts are not loaded yet hibernate will either
a. load all posts in an additional SQL SELECT if the session is still open
b. throw LazyInitializationExcepiton is the session is closed
So your homework task #1 is to make sure the session is still open (if you really need this). By default in the Spring Boot app Hibernate session boundaries are the same as Spring #Transactional boundaries. (HOWEVER, in case you are using Spring Boot 2+, find property spring.jpa.open-in-view. It's true by default and it registers OpenEntityManagerInViewInterceptor that gives you open Hibernate session during the whole lifetime of a web request.)
When the session is open and the lazy loading will work, the following will happen:
one user is loaded into the session by id
all posts of that user are lazy loaded when Jackson calls the getter
since Jackson recursively goes and calls all the getters, each post.getTags() will be called
now each tag.getPosts() will be called
and again each post.getUser() and post.getTags() will be called
...
as you can see, you will load all of your DB to the application + you'll get StackOverflowException :(
So you homework task #2 is to put back #JsonMappedReference, #JsonBackReference (for instance if you load all tags for posts then you should not load all posts for tags).
I have to mention that this is not the right way to do it. It is much better to load everything you need first (for instance using join fetch) and then start building a JSON. But since you've asked... :)
Regarding the write operation, it is a bit trickier and it depends on HOW you actually do it. But at least make sure that session and transaction are open when you're trying to write data to the DB.
I know only basics of DB and JPA/Hibernate. I have to manage a User table, where a user can have many roles. The roles are contained in a catalog table which in my User formulary i do not pretend to manage/modify, i just need the catalog values as a reference to add or delete to my user.
I think the best approach would be to create a relationship table between User and Role to hold the users and their roles 'User_Roles' (unless there is a more efficient approach).
I am not allowed to modify the Role entity since it is used for different purposes in a lot of other areas of my app that are independent of the User table.
I've seen a lot of examples but I still do not know which one exactly aplies to my specific needs. How can I map my User and its roles in a sigle Entity with JPA and Hibernate?
Maybe the next image describes better what I want:
Thank you very much in advance for your answers.
In your case you have to use #ManyToMany to associate both tables.
That should look at this:
#Entity
#Table(name = "User")
public class User {
...
#ManyToMany
#JoinTable(name = "User_Roles", joinColumn = "id_person")
private Set<Role> roles = new HashSet<>;
}
#Entity
#Table(name = "Role")
public class Role {
...
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>;
}
What you're describing is a one-to-many relationship but it's between User and the joining table - User_Roles. Since there is not much you can do to avoid the joining table, the best thing would be to use #ManyToMany with #JoinTable annotations to map the relationship. Remember to use Set instead of List. You don't need an entity for the joinint table then.
You can find a discussion about this topic in this blog post.
As per your above screen, what I understood user can be assigned more than 1 role.
i.e. 1 user can be mapped to multiple role and 1 role can be mapped to multiple users.
Hence relationship between user and role is many to many.
many to many relationship can be achieved using third table which is called mapping table.
so , we have following tables in your example :-
user
user_roles
role
#Entity
#Table(name = "user")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
#Id
#SequenceGenerator(name = "USER_ID_GENERATOR", sequenceName = "USER_SEQ",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_ID_GENERATOR")
#Column(name = "user_id")
private Long userId;
#OneToOne
#JoinColumn(name = "persion_id")
private person person;`
enter code
here`
#Basic
#Column(name = "date")
private Date date;
#Basic
#Column(name = "observations")
private String observations;
#Basic
#Column(name = "text")
private String text;
#JsonIgnore
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<UserRoles> users = new ArrayList<>();
}
#Entity
#Table(name = "role")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Role {
#Id
#SequenceGenerator(name = "ROLE_ID_GENERATOR", sequenceName = "ROLE_SEQ",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ROLE_ID_GENERATOR")
#Column(name = "role_id")
private Long roleId;
#Basic
#Column(name = "id1")
private Long idOne;
#Basic
#Column(name = "id1")
private Long idTwo;
#Basic
#Column(name = "id1")
private Long idThree;
#Basic
#Column(name = "text")
private String text;
#JsonIgnore
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL)
private List<UserRoles> users = new ArrayList<>();
}
#Entity
#Getter
#Setter
#Table(name = "user_roles")
#JsonInclude(JsonInclude.Include.NON_NULL)
#Audited
public class UserRoles {
private static final long serialVersionUID = 1L;
#EmbeddedId
UserRolesKey userRoleId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("role_id")
#JoinColumn(name = "role_id")
Roles role;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(user_id)
User user;
#PrePersist
private void prePersist() {
super.onPrePersist();
if (this.getId() == null) {
UserRolesKey mapKey = new UserRolesKey();
mapKey.setRoleId(this.getRole().getRoleId());
mapKey.setUserRoleId(this.getUser().getUserId());
this.setId(mapKey);
}
}
}
While saving you just need to populate user entity with all the uaerRoles mapping entity and persist it. jpa will save all the details.
while updating role assign to user you need to fetch the user entity and update the mapping by adding new userRoles entity and nullifying the while is going to be removed.
I have a Entity Like below
#Entity
#Table(name = "table1")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class TableOneEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "created_by")
private String createdBy;
#Column(name = "title_in_unicode")
private String titleInUnicode;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "table1_id")
#LazyCollection(LazyCollectionOption.FALSE)
private Set<ErrorMessages> errorMessages = new HashSet<ErrorMessages>();
The Error Message Entity is like
#Entity
#Table(name = "error_messages")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ErrorMessages {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long errorId;
#Column(name = "table1_id")
private Long table1Id;
#Column(name = "field_key")
private String fieldKey;
#Column(name = "field_value")
private String fieldValue;
I am using hibernate criteria to fetch all the records from the DB;
#SuppressWarnings("unchecked")
#Override
public List listTableOneEntites() {
Criteria criteria = getSession().createCriteria(TableOneEntity.class);
return (List) criteria.list();
}
when I printing the size of the List its giving me 417 but on doing the count * on DB on the same table the entries are 410 .
But my colleague who is running the same code in his local machine with the same dump is getting both the entries as 410.
We both are using mysql DB.
Has anyone any idea about this mismatch in count.
You are eagerly fetching the ErrorMessage entity which is a one to many so my guess is that some of the TableOneEntity's have more than one ErrorMessage's joined to them.
Try adding criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY). If this works then you may wish to research it further - I can't remember if it forces a switch to multiple selects rather than a join but my gut feeling is that since you only have one join it should still be able to do it efficiently.
Why your colleague would get different results is beyond me however - do you both definitely have the exact same data? Are you executing the joined query when you run the count *? If not then try that and see if yours jumps to 417.