I have this POJO class:
package com.weather.weather.entity;
import jakarta.persistence.*;import lombok.Data;
import lombok.NoArgsConstructor;import org.hibernate.annotations.Proxy;import org.springframework.transaction.annotation.Transactional;
import java.util.List;import java.util.Set;
#Entity#Table(name = "users")#Data#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "collaborator",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles;
}
Also Role class:
package com.weather.weather.entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Proxy;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
#Entity
#Table(name = "roles")
#Data
#NoArgsConstructor
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// #OneToMany(mappedBy = "role")
// private List<User> users;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
}
But always getRoles() are null.
I have tried changing data to #OneToOne, #ManyToOne, #OneToMany.But its always null.At some point, when i was doing #ManyToOne way, i have got this exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.weather.weather.entity.Role.users: could not initialize proxy - no Session
And i found to ways to fix that. I`ve tried putting fetch property, #Proxy(lazy=false), and many many others. Nothing helped
Your annotation #ManyToMany imported from jakarta.persistence.
You should import from javax.persistence.* and you can remove proxy annotation and Lazy fetchType
I have an application in which there are Projects associated with multiple Text entities and Text entities are associated with multiple projects (Many-To-Many).
Now when I try to fetch Text entities in hibernate I get projects=null
Here is the Project Side of the relationship:
import lombok.Data;
import javax.persistence.*;
import java.util.List;
#Data
#Entity
#Table(name = "project_data")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name="project_id")
private int projectId;
#Column(name="project_name")
private String projectName;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="project_text", joinColumns = {#JoinColumn(name = "project_id")}, inverseJoinColumns = {#JoinColumn(name="text_id")})
private List<Text> texts;
public Project(String projectName){
this.projectName = projectName;
}
}
Here is the Text Side of the Relationship:
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.List;
#Data
#Entity
#Table(name="text_data")
public class Text {
#Id
#GeneratedValue(generator = "text_hash")
#GenericGenerator(name = "text_hash",strategy = "TextHashGenerator")
#Column(name="text_id")
private String text_id;
#Column(name="text_value")
private String text;
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "texts", fetch = FetchType.LAZY)
private List<Project> projects;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="text_keyword", joinColumns = {#JoinColumn(name = "text_id")}, inverseJoinColumns = {#JoinColumn(name = "keyword_id")})
private List<Keyword> keywords;
public Text(String text){
this.text = text;
}
}
Here is the custom TextHashGenerator I am using for generating ID for Text Entities
import org.apache.commons.codec.digest.DigestUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import java.io.Serializable;
public class TextHashGenerator implements IdentifierGenerator{
#Override
public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException {
String textHash = "";
if(o instanceof Text){
Text text = (Text) o;
textHash = DigestUtils.sha256Hex(text.getText());
}
return textHash;
}
}
this problem is with all the relationships as they are unable to fetch Owner side of the relationship.
Output
Text(text_id=21e57b707ffe2bd2d89b2b6f6c999597dc6e9dd90eaee809fbd06c222cf54de8, text=Text 1, projects=null, keywords=[Keyword(keywordId=2, keyword=Text 1 Keyword 1, texts=null, meanings=[Meaning(meaningId=3, meaning=Text 1 Keyword 1 meaning 1, keyword=null), Meaning(meaningId=4, meaning=Text 1 Keyword 1 meaning 2, keyword=null), Meaning(meaningId=5, meaning=Text 1 Keyword 2 meaning 1, keyword=null)]), Keyword(keywordId=6, keyword=Text 1 Keyword 2, texts=null, meanings=null), Keyword(keywordId=7, keyword=Text 2 Keyword 1, texts=null, meanings=null), Keyword(keywordId=8, keyword=Text 2 Keyword 2, texts=null, meanings=null)])
I am using following query to fetch Text
Query query = session.createQuery("from Text");
You have to join the relations if you set fetch = FetchType.LAZY (which you should, don't change that). Do it like this:
Query query = session.createQuery("SELECT t from Text t JOIN t.projects")
I've got an entity class MyEntity that can be associated with other MyEntitys. I want to define the relationship between the MyEntitys. So I've ended up with a class like
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import javax.persistence.*;
import java.util.HashMap;
import java.util.Map;
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#Accessors(chain = true)
#Entity
#Table
public class MyEntity {
#Id
#GeneratedValue
private Long id;
#NonNull
private String name;
private String email;
private String phone;
#NonNull
#OneToOne
private MyEntityType myEntityType;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private Map<String, Address> addresses = new HashMap<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable()
private Map<Relationship, MyEntity> relationships = new HashMap<>();
public MyEntity addAddress(String key, Address address) {
addresses.put(key, address);
return this;
}
public MyEntity addRelationship(Relationship relationship, MyEntity myEntity) {
relationships.put(relationship, myEntity);
return this;
}
}
Where the relationship class looks like
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import javax.persistence.*;
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#Entity
#Table
#Accessors(chain = true)
public class Relationship {
#Id
#GeneratedValue
private Long id;
#NonNull
private String name;
#NonNull
private String antonym;
}
The field in question is the relationships field in MyEntity.
Things seem to work until I delete a MyEntity. If the deleted MyEntity has no relationships everything is ok. If the deleted MyEntity has relationships then the MyEntitys it is related to are also deleted.
If I modify the #ManyToMany annotation to
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
Then the related MyEntitys are no longer deleted. Does this look like the correct way to do this?
In order to correctly define the Map<Entity, Entity> mapping you need to combine #ManyToMany (or #OneToMany), #JoinTable and the #MapJoinColumn annotations:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(...)
#MapKeyJoinColumn(name = "relationship_id")
private Map<Relationship, MyEntity> relationships = new HashMap<>();
See here for more examples.
I'm trying to do a many-to-many mapping between 2 tables users and products. I wrote their entities and repositories but still the application is giving error. Please help me out if you can, thanks in advance.
Error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: com.poc.joins.entities.User.users in com.poc.joins.entities.Product.users
The code snippets are
User
package com.poc.joins.entities;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Entity
#Table(name = "users")
public class User {
#Id
private String username;
private String password;
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "users_products",
joinColumns = {#JoinColumn(name = "username")},
inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Product> products = new HashSet<>();
}
// Getter, setters, constructors are not shown here
Product
package com.poc.joins.entities;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Entity
#Table(name = "products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String p_name;
private Integer quantity;
private Float price;
private Float total;
#ManyToMany(mappedBy = "users")
private Set<User> users = new HashSet< >();
}
// Getter, setters, constructors are not shown here
In the owned entity (Product), you pass in the field that owns the relationship (in the User entity):
#ManyToMany(mappedBy = "products")
private Set<User> users = new HashSet< >();
Originally you told the Persistence provider to look for the field called users in the User entity which would hold all the information about the relationship (#JoinTable etc.)
I want to persist my entity with ManyToMany relation. But i have some problem during persisting process.
My entities :
#Entity
#Table(name = "USER")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long userId;
#Column(name = "NAME", unique = true, nullable = false)
String userName;
#Column(name = "FORNAME")
String userForname;
#Column(name = "EMAIL")
String userEmail;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "USER_USER_ROLES", joinColumns = #JoinColumn(name = "ID_USER"), inverseJoinColumns = #JoinColumn(name = "ID_ROLE"))
List<UserRoles> userRoles = new ArrayList<UserRoles>();
// getter et setter
}
and
#Entity
#Table(name = "USER_ROLES")
public class UserRoles implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long userRolesId;
#Column(unique = true, nullable = false, name = "ROLE_NAME")
String roleName;
// getter et setter
}
Service code :
User user = new User();
UserRoles role;
try {
role = userRolesServices.getUserRoleByName("ROLE_USER"); // find jpql - transaction
} catch (RuntimeException e) {
LOGGER.debug("No Roles found");
role = new UserRoles("ROLE_USER"); // create new
}
user.addUserRole(role);
user.setUserName(urlId);
user.setUserForname(fullName);
user.setUserEmail(email);
userServices.createUser(user); // em.persist(user) - transaction
First time, when I try to persist a User with UserRoles "ROLE_USER", no problem. User and UserRoles and join tables are inserted.
My problem is when I try to persist a second User with the same UserRoles.
I check if the UserRoles exists by finding it (userRolesServices.getUserRoleByName(...)).
If exists -> add this UserRoles to User list (id + role name) else i create a new one (only role name).
By when I try to persist the second User, i obtain the following exception :
"detached entity to persist : .....UserRoles" (maybe because getUserRoleByName is performed in another transaction)
If I do not use getUserRoleByName (only *new UserRoles("ROLE_USER");*), i obtain the following exception :
"...ConstraintViolation : Duplicated entry for 'ROLE_NAME' ..."
So, how to properly persist an entity with #ManyToMany relation ?
For above problem I would say your entity relationship cascade is wrong. Consider this: A user can have multiple roles but there can be fixed number of roles that can exist in the system. So CASCADE ALL from User entity does not make any sense, since life cycle of UserRoles should not depend on User entity life cycle. E.g. when we remove User, UserRoles should not get removed.
detached entity to persist exception will only occur when you are passing object which has primary key already set to persist.
Remove cascade and your problem will be solved now only thing you will need to decide is how you are going to insert User roles. According to me there should be separate functionality to do so.
Also do not use ArrayList, use HashSet. ArrayList allows duplicates.
I will provide my answer if anyone get same type of problem to me and the author.
Basically what I was facing was a situation when I had one table which was some kind of CONSTANT values. And the other would change, but it should map (many to many) to those CONSTANTS.
The exact problem is USERS and it's ROLES.
Roles would be known and added on system startup, thus they should never get removed. Even if no user would have some Role it should still be in the system.
The class implementation, using JPA:
User:
#Entity
#Table(name = "USERS")
public class User{
#Id
private String login;
private String name;
private String password;
#ManyToMany(cascade = {CascadeType.MERGE})
private Set<Role> roles = new HashSet<>();
Role:
#Entity
#Table(name = "ROLE")
public class Role {
#Id
#Enumerated(value = EnumType.STRING)
private RoleEnum name;
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();
Usage
This setup will easily add/remove Role to User. Simply by passing an array, f.e.: user.getRoles().add(new Role("ADMIN")); and merge the user. Removing works with passing an empty list.
If you forget to add the Role before adding it to the user most likely you will get an error like:
javax.persistence.RollbackException: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: com.storage.entities.Role#246de37e.
What and why
mappedBy attribute is added to the child Entity as described in the JPA Docs
If you choose to map the relationship in both directions, then one
direction must be defined as the owner and the other must use the
mappedBy attribute to define its mapping (...)
cascade = {CascadeType.MERGE} is added for proper cascades JPA Docs
Cascaded the EntityManager.merge() operation. If merge() is called on
the parent, then the child will also be merged. This should normally
be used for dependent relationships. Note that this only affects the
cascading of the merge, the relationship reference itself will always
be merged.
(maybe because getUserRoleByName is performed in another transaction)
That would seem to the the issue, do the query in the same transaction/entity manager. Otherwise re-find it in the current transaction using find().
Duplication Reason: Id is autogenerated, so every time a new role is being created.
Use in this way :
User
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_Id;
#Column(name = "email")
private String email;
#Column(name = "firstname")
private String firstname;
#Column(name = "lastname")
private String lastname;
#Column(name = "password")
private String password;
#Column(name = "active")
private int active;
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name="user_role",
joinColumns=#JoinColumn(name="user_Id"),
inverseJoinColumns=#JoinColumn(name="role_Id"))
private Set<Role> roles = new HashSet<>();
//Getter and Setter
Role
#Entity
#Table(name="roles")
public class Role {
#Id
#Column(name="role_Id")
private int role_Id;
#Column(name="role_name")
private String role_name;
#ManyToMany(mappedBy = "roles")
private Set<User> users= new HashSet<>();
Controller (Should have added it to Service)
#PutMapping("/addEmp")
public String addEmp(#RequestBody User user) {
String pass=passencoder.encode(user.getPassword());
user.setPassword(pass);
List<Role> roles =rolerepo.findAll();
for(Role role: roles)
System.out.println("Roles"+ role.getRole_name());
//user.setRoles(new HashSet < > (rolerepo.findAll()));
userrepo.save(user);
return "User Created";
}
Output
Roles
User_Role
If you liked the answer please subscribeYoutube Channel Atquil
I have the same issue, but couldn't get through it yet.
My RelationShip is Hotel to DeliveryPartners.
Following are the classes:
#Entity Class
package com.hotelapp.models;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.Set;
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Hotel {
#Id
#GeneratedValue(generator = "hotel_id", strategy = GenerationType.AUTO)
#SequenceGenerator(name = "hotel_id", sequenceName = "hotel_id")
private Integer hotelId;
private String hotelName;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "address_id")
private Address address;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "hotel_id")
private Set<Menu> menuList;
#ManyToMany(cascade = {CascadeType.MERGE} ,fetch = FetchType.EAGER)
#JoinTable(name ="hotel_delivery", joinColumns = #JoinColumn(name ="hotel_id"), inverseJoinColumns = #JoinColumn(name="delivery_id"))
private Set<Delivery> delivery;
public Hotel(String hotelName, Address address, Set<Menu> menu, Set<Delivery> delivery) {
this.hotelName = hotelName;
this.address = address;
this.menuList = menu;
this.delivery = delivery;
}
#Override
public String toString() {
return "Hotel{" +
"hotelName='" + hotelName + '\'' +
", address=" + address +
", menu=" + menuList +
", delivery=" + delivery +
'}';
}
}
#Delivery Class
package com.hotelapp.models;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Delivery {
#Id
#GeneratedValue(generator = "del_id", strategy = GenerationType.AUTO)
#SequenceGenerator(name = "del_id", sequenceName = "delivery_id")
private Integer deliveryId;
private String partnersName;
private Double charges;
#ManyToMany(mappedBy = "delivery", cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JsonIgnore
private Set<Hotel> hotelList = new HashSet<>();
public Delivery(String partnersName, Double charges) {
this.partnersName = partnersName;
this.charges = charges;
}
#Override
public String toString() {
return "Delivery{" +
"partnersName='" + partnersName + '\'' +
", charges='" + charges + '\'' +
'}';
}
}
#Controller Class
#PostMapping("/hotels")
public ResponseEntity<Hotel> addHotel(#RequestBody Hotel hotel){
Hotel hotel1 =hotelService.addHotel(hotel);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("desc", "oneHotelAdded");
return ResponseEntity.ok().headers(httpHeaders).body(hotel1);
}
When I use merge cascade type, getting following exception:
Hibernate: insert into address (city, state, street_name, zip_code, address_id) values (?, ?, ?, ?, ?)
Hibernate: insert into hotel (address_id, hotel_name, hotel_id) values (?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into hotel_delivery (hotel_id, delivery_id) values (?, ?)
2020-07-12 00:13:37.973 INFO 50692 --- [nio-9098-exec-1] o.h.e.j.b.internal.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
2020-07-12 00:13:38.026 ERROR 50692 --- [nio-9098-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - **save the transient instance before flushing: com.hotelapp.models.Delivery; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hotelapp.models.Delivery] with root cause
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hotelapp.models.Delivery**
at org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:347) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:495) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.type.EntityType.nullSafeSet(EntityType.java:280) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.writeElement(AbstractCollectionPersister.java:930) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1352) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:52) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_221]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
I realise from the queries part there is no insert query for delivery table so that delivery can be used in hotel_delivery (MTM Table).
Don't know how to proceed now.
I was getting the same error, but after adding the cascade = CascadeType.ALL for both sides of relationship the issue is resolved.
earlier I was cascade = CascadeType.ALL only on the parent side of the relation, after adding child also the code is working fine now.
here is my code.
Reader(parent entity):
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(name="READER_SUBSCRIPTIONS", joinColumns=
{#JoinColumn(referencedColumnName="ID")}
, inverseJoinColumns={#JoinColumn(referencedColumnName="ID")})
private List<Subscription> subscriptions;
Subscription (child entity):
#ManyToMany(mappedBy="subscriptions", cascade = CascadeType.ALL)
private Set<Reader> readers;
Persistence code:
List<Subscription> list = new ArrayList<Subscription>();
list.add(sub1);
list.add(sub2);
Set<Reader> readerSet = new HashSet<Reader>();
readerSet.add(reader1);
readerSet.add(reader2);
reader1.setSubscriptions(list);
reader1.setSubscriptions(list);
sub1.setReaders(readerSet);
sub2.setReaders(readerSet);
reader2.setSubscriptions(list);
reader2.setSubscriptions(list);
readSubscriberRepository.save(reader1);
readSubscriberRepository.save(reader2);