Teaching myself Hibernate, I have the following table structure/relationship
Which is represented by the following classes...
Users
#Entity
#Table(name = "users")
public class User implements IUser<Role>, Serializable {
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
#SequenceGenerator(name = "user_key_seq")
#Column(name = "key", insertable = false, updatable = false)
private Long key;
#Column(name = "name")
private String name;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "userroles",
joinColumns = {
#JoinColumn(name = "userkey")},
inverseJoinColumns = {
#JoinColumn(name = "rolekey")})
private Set<Role> roles = new HashSet<>(5);
#Override
public Set<Role> getRoles() {
return roles;
}
// Other setters and getters
}
Roles
#Entity
#Table(name = "roles")
public class Role implements IRole, Serializable {
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
#SequenceGenerator(name = "roles_key_seq")
#Column(name = "key", insertable = false, updatable = false)
private Long key;
#Column(name="name")
private String name;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="parentroles",
joinColumns = {#JoinColumn(name="childkey")},
inverseJoinColumns = {#JoinColumn(name="parentkey")})
private Set<Role> roles = new HashSet<>(5);
// Other setters and getters
}
The problem I'm getting is this...
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.kaizen.chishiki.core.data.User.roles, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:575)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:214)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:554)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:142)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
at og.kaizen.chishiki.core.testdatacore.Main.<init>(Main.java:37)
at og.kaizen.chishiki.core.testdatacore.Main.main(Main.java:25)
Now, I know I could set the fetch type to FetchType.EAGER, but I'd prefer not to do that, apart from performance concerns, I'm trying to learn my way around these problems ;)
I was wondering if there was a way to write a Hibernate query to satisfy this relationship and load the rows manually (it would also allow me to return the interface of Role instead of the implementation as I'm having to do now (would allow me to maintain that contract)).
From what I've read, it would seem that I would need to construct an Entity for UserRoles. Not that I'm against this solution, but I was curious if there was a way to get around it.
roles appears in a number of other relationships, so there is no way it can form a direct relationship with the parent table (that is, I can't put the key of the parent table in the role table directly, besides, users can have multiple roles)
The other solution I had in mind was to write a stored procedure to do it for me. While this is viable solution that I'm not above doing, it does make migrating the application to another database more difficult, not a show stopper, but I'm trying to keep these ideas in mind.
If you want to load users with their roles, but still keep the association lazy, use a HQL query containing a left join fetch:
select u from User u left join fetch u.roles where ...
If you have a User and want to make sure its roles collection is initialized before closing the session, call Hibernate.initialize(user.getRoles()).
If you are using Hibernate 4.1.6+ then one can handle those lazy association problems by using hibernate.enable_lazy_load_no_trans property in hibernate.properties.
For More refer : https://stackoverflow.com/a/11913404/286588
I solved this problem by annotating the method in the controller as transactional.As lazy-load basically returns a list of future objects ,the method needs to be transactional.
Related
Is there a way to join tables in Spring Boot without using plain SQL strings which are not type safe? Currently using JBDC .
These methods here are not string safe:
Joining two table entities in Spring Data JPA
In .NET, Entity Framework has a method in C#,
from u in db.Users
join ad in db.Address on u.Addressid equals ad.AddressId
select ..
Java Example:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
//...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
// ... getters and setters
}
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
//...
#OneToOne(mappedBy = "address")
private User user;
//... getters and setters
}
It is "type-safe" - because they all are strings.
Your problem is: If someone use the "wrong" referenced field, then it will not checked at compile-time.
This is true and cannot be changed, but you can check it at runtime.
Or even better: Spring/Hibernate can do that for you.
Just enable the hibernate validation by adding spring.jpa.hibernate.ddl-auto=validate to your application.yml or application.properties.
Now on the startup of spring you will get this exception (if you have a typo for address/adress):
Caused by: org.hibernate.AnnotationException: Unknown mappedBy in: Address.user, referenced property unknown: User.adress
You can also use the other modes. Have a look to the documentation.
All modes (expect none) will validating the schema.
That's the best solution, since there are no compile-time-checks.
You can try using JOOQ also.
I've made some research but without any specific answer.
I know how #JsonView... #JsonIgnore works... but my point here is about back end, the point of view from there. I'm working on spring boot and by default OSIV is enabled, so as far as I know, if I'm not wrong, if I make a call in database on an #Entity that has #ManyToMany association it will eagerly fetch everything.
Till there I have no issues, the problem is that the associated Collection also has Collections... And some services need to fetch them and others don't... Then I keep getting LazyInitializationException.
#Entity
#EntityListeners(AuditingEntityListener.class)
#Inheritance(strategy = InheritanceType.JOINED)
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private int id;
private String categoryTitle;
private String categoryDescription;
#ManyToMany
#JoinTable(
name = "Category_Parent",
joinColumns = #JoinColumn(name = "id_category", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "id_category_parent")
)
private Set<Category> parentCategory;
#ManyToMany
#JoinTable(
name = "Category_Parent",
joinColumns = #JoinColumn(name = "id_category_parent", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "id_category")
)
private Set<Category> subCategory;
Then to prevent that error I used #Query like this
#Repository
public interface CategoryRepository extends JpaRepository<Category, Integer> {
#Query("from Category c left join fetch c.subCategory left join fetch c.parentCategory")
List<Category> getAllCategories();
}
Now I'm able to fetch it lazly... I used that #Query way because it is the only one I know to fetch the associated Collections... I heared about EntityGraph and Hibernate.initialize() but have no knowledge on how to proceed (would appreciate some link).
So, then I have Json exception because the json response is infinite. How can I avoid this new issue? Using DTO?
I appreciate.
------ EDIT ------
I've used #JsonView on the properties that I want to send as response, however if I use #JsonView over subCategory only, it works, but if I use on parentCategory I got the infinite loop once again... Can't solve it.
You can add fetch = FetchType.LAZY to your #ManyToMany or #OneToMany annotation like this: #ManyToMany(fetch = FetchType.LAZY). More instruction is at https://www.baeldung.com/hibernate-lazy-eager-loading
I am currently developing an application with the API in JavaSpringBoot and I need to add relationships between users (friends). For this I made a many-to-many relation which contains two fields:
CREATE TABLE friend_relation
(
fk_id_friend integer REFERENCES users (id) NOT NULL,
fk_id_user integer REFERENCES users (id) NOT NULL,
PRIMARY KEY (fk_id_friend, fk_id_user)
);
When I connect with a user and add relationships : everything is fine, but if I connect to one of the accounts added as a friend by the other user, there is a StackOverflowError.
I know it's because of the almost identical entries in the database, but I have no idea how to get my entity to be correct.
Currently each user must add the other individually, I imagine that I have to make a friend request system but again I am blocked.
Do I have to make an "effective" field in my friend_relation table. If so, how do I use it? Should I create a specific entity for this table or leave it in the User entity?
Currently, this is what my user entity looks like:
#Entity
#Table(name = "users")
#Data
#Accessors(chain = true)
public class UserEntity {
#Id
#SequenceGenerator(name = "users_generator", sequenceName = "users_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_generator")
#Column(name = "id")
private Integer id;
[...]
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "friend_relation",
joinColumns = #JoinColumn(name = "fk_id_user", referencedColumnName = "id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "fk_id_friend", referencedColumnName = "id", nullable = false)
)
private List<UserEntity> friends = new ArrayList<>();
}
When trying to modify my entity to avoid the error:
#ManyToMany
#JoinTable(name="friend_relation",
joinColumns=#JoinColumn(name="fk_id_user"),
inverseJoinColumns=#JoinColumn(name="fk_id_friend")
)
private List<UserEntity> friends;
#ManyToMany
#JoinTable(name="friend_relation",
joinColumns=#JoinColumn(name="fk_id_friend"),
inverseJoinColumns=#JoinColumn(name="fk_id_user")
)
private List<UserEntity> friendOf;
I looked for resources on the internet but I did not find it, I may have searched poorly and I apologize in advance if the answer has already been given.
If you can help me it is with great pleasure, thank you in advance!
(And sorry for the Google Translate I preferred not to use my rough English)✌
Ok sorry update, i don't post the stake trace : https://pastebin.com/Ls2qRpU4
It happens when I get my user on the front side. I am trying to connect but I cannot because this error occurs.
First of, I've noticed an #Data on your entity, which I suppose is from Project Lombok? Just be careful, Lombok generated methods can trigger lazy loading and there are problems with equals/hashCode for auto-generated IDs. (see here and here)
Now to your problem:
It's indeed a JSON serialization issue. You have circular references and Jackson runs in circles bc. it doesn't know where to stop.
You can either use a DTO projection where you resolve this circle yourself, like:
public class UserDto {
public Integer id;
public List<Integer> friends; // List of IDs
}
You can also use Jackson annotations to achieve almost the same thing, like #JsonIdentityInfo. Here's an article about it.
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table(name = "users")
#Data
#Accessors(chain = true)
public class UserEntity {
#Id
#SequenceGenerator(name = "users_generator", sequenceName = "users_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_generator")
#Column(name = "id")
private Integer id;
[...]
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "friend_relation",
joinColumns = #JoinColumn(name = "fk_id_user", referencedColumnName = "id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "fk_id_friend", referencedColumnName = "id", nullable = false)
)
private List<UserEntity> friends = new ArrayList<>();
}
When you have complex Entities with circular references or very large object trees, you often have to think hard about how to serialize them - especially when taking LazyLoading into account. I have spent a lot of time on this in complex professional projects. Simple automatically generated DTOs and serialization sometimes don't cut it.
I'm not an expert on this, so I'm sure others will give more accurate answers. However, the problem seems to occur when mapping the entity to JSON. You can't map to JSON a user with a collection of friend users that also have their friends in a fully mapped collection since that causes infinite recursion. I would try to use Jackson annotations to make the serialization of the friends list produce a list of ids instead of a list of complete users.
I have following classes in bidirectional many to many relationship.
#Table(name = "message")
#Entity
public class Message {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "message_id", unique = true, nullable = false)
private int id;
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.MERGE)
#JoinTable(name = "tags_messages",
joinColumns = #JoinColumn(name = "message_id", referencedColumnName = "message_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "tag_id"))
private Set<Tag> tags=new HashSet<>();
and
#Table
#Entity(name = "tag")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tag_id", unique = true, nullable = false)
private int id;
#Column(name = "name", unique = false, nullable = false)
private String name;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.MERGE)
private Set<Message> messages;
When trying to save new Message, I got exception saying: "detached entity to persist...Tag". I got it to work by setting CascadeType.MERGE, but I don't understand why it is working. It would be great if someone can explain me why :)
Steps I did which lead to exception:
In db I already had two Tags objects and no Messages. I also had connecting empty table messages_tags
On frontend (Android) I create new Message object (without id), add one Tag (entire object, pulled from db, with id) to Message.
Send new Message to backend (using Retrofit). Hit my controller function, then service function in which I tried to save new Message with accompanying child Tags. At first, my cascading type annotation on both side, was like this:
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
I thought, since I have one part of relationship covered, that I need to cover other one as well. So I did this:
newMessage.getTags().forEach(t -> t.getMessages().add(newMessage));
messageRepository.save(newMessage) //Bum! exception
I commented out that line for setting other part of relationship, set MERGE as cascading type and save simply worked. WHY? Are there any other consequences I may experience while doing other CRUD operations on any of these entities?
When you add a new Tag to the Message on the frontend, you have a different persistent context from the one used on backend. That's why the tag entity is seen as detached (it has a PK but it is not in the backend's persistent context). Since you did not specify a cascade type for JPA to know that to do with Tag instance, the persist of the Message instance fails.
Once you specify the cascade type MERGE, the Tag instance is merged into the backend's persistent context and the save succeeds.
You can avoid the using of MERGE cascade by saving first the Tag instance.
Short version
I have a basic setup where a user table is linked to a role table and a role table is linked to a right. These are both Many-to-Many relations. The roles are a dynamic entity and not of interest for the application (only for visual aspects). When I fetch a user I want to return the data in the user table including a list of the names of all rights.
To clarify, this is what I want the solution to do:
I managed to get the rights in my user object and return them, but it's inefficient due to the extra query calls hibernate makes after the original query was called.
Detailed version
Let me first give you some information on how to entities are linked and what the code looks like. My (simplified) database table structure looks like this:
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "user_id", columnDefinition = "user_id")
private Long userId;
#Transient
private List<String> rights
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"))
#JsonIgnore
private List<Role> roles;
//Getters and setters
}
Role.java
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "role_id", columnDefinition = "role_id")
private Long roleId;
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"))
#JsonIgnore
private List<Employee> employees;
#ManyToMany
#JoinTable(
name = "role_right",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"),
inverseJoinColumns = #JoinColumn(name = "right_id", referencedColumnName = "right_id"))
#JsonIgnore
private List<Right> rights;
//Getters and setters
}
Right.java
#Entity
#Table(name = "right")
public class Right {
#Id
#Column(name = "right_id", columnDefinition = "right_id")
private Long rightId;
#Column(name = "name", columnDefinition = "name")
private String name;
#ManyToMany
#JoinTable(
name = "role_right",
joinColumns = #JoinColumn(name = "right_id", referencedColumnName = "right_id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"))
#JsonIgnore
private List<Role> roles;
//Getters and setters
}
It's important to know that I use the Java Specifications API to join the tables:
return (root, query, cb) -> {
query.distinct(true);
Join rolesJoin = root.join("roles", JoinType.LEFT);
Join rightsJoin = rolesJoin.join("rights", JoinType.LEFT);
return cb.conjunction();
};
This creates the correct query:
select <columns go here>
from employee user0_
left outer join user_role roles1_ on user0_.user_id=roles1_.user_id
left outer join role role2_ on roles1_.role_id=role2_.role_id
left outer join role_right rights3_ on role2_.role_id=rights3_.role_id
left outer join right right4_ on rights3_.right_id=right4_.right_id
Everything looked to good to me till now. But when I tried to fetch the names of all roles, there where more than two queries (count for page and the original one) being executed
//The original code uses lambda for this
for(Role role : user.getRoles()){
for(Right right: role.getRights()){
user.addRight(right.getName());
}
}
The extra query looks like:
select <column stuff>
from role_right rights0_
inner join right right1_ on rights0_.right_id=right1_.right_id
where rights0_.role_id=?
This makes the call very inefficient to me. In this case it's a single user, but with multiple users it adds up.
Is there a way to have a single query put the names of all rights in the user entity, without adding extra query executions?
Things I tried so far:
Using #SecondaryTable to directly define column from the Right table in my User entity. I could not get to first link the Role to the User and then use fields from the Role table to link the Right table. So in the end I would have to #SecondaryTable annotation on top of my User object and define columns of the Right object below.
Using #Formula in the User entity to insert a native call into the query. This did also not work as the annotation did not understand how to map everything into a list of rights.
There might be other options here, or I did something horribly wrong with implementing the ones above. But for now I don't which way to go in finding a solution for my problem. If someone could tell me, that would be great.
Thanks in advance,
Robin
You are using Root.join which does just the joining of tables for the purposes of the query; lazy associations in the loaded entities will still not be initialized.
As I see, your intention is to initialize the lazy collections as well. For that you have to use Root.fetch (defined in the interface method inherited from the FetchParent):
Create a fetch join to the specified collection-valued attribute using
the given join type.
However, your intention is not a good practice; do not join multiple collections in one query, otherwise the query result set will explode with full Cartesian product between the joined collections. Your result set contains <num of users> * <num of roles per user> * <num of rights per role> rows. So, each user data is repeated <num of roles per user> * <num of rights per role> times in the generated result set.
The approach I find to be the best and most straightforward is to specify batch size on lazy associations.