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")
Related
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'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 am working on a Spring-MVC application in which I am trying to add many-to-many mapping for an already existing one-to-many mapping between two entities. Two entities which we have in the project are GroupSection and GroupNotes.
For one of the task in project, I had to introduce a many-to-many mapping between GroupSection and GroupNotes, but I am getting a lazy initialization exception.
Error :
org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: failed to lazily initialize a collection of role: com.tooltank.spring.model.GroupSection.groupSections, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.tooltank.spring.model.GroupSection["groupSections"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.tooltank.spring.model.GroupSection.groupSections, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.tooltank.spring.model.GroupSection["groupSections"])
Here is the controller method which was called:
#RequestMapping(value = "/sections/get/{canvasid}")
public #ResponseBody List<GroupSection> getAllSectionsForCanvas(#PathVariable("canvasid") int canvasid) {
boolean value = this.personService.getCanvasValuesForCanvas(canvasid);
return this.groupSectionService.listGroupSectionByCanvasid(canvasid, value);
}
DAO method(Service method just calls the DAO method) :
#Override
#Transactional(readOnly = true)
public List<GroupSection> listGroupSectionByCanvasid(int mcanvasid) {
try {
Session session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("From GroupSection as msection where " +
"msection.currentcanvas.mcanvasid=:mcanvasid and msection.sectionDisabled=false and msection.sectionInActive=false");
query.setParameter("mcanvasid", mcanvasid);
return query.list();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
GroupSection model :
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import javax.persistence.*;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
#Entity
#Table(name = "membersection")
public class GroupSection {
// Below is self-mapping, required for one of the task, works.
#JsonIgnore
#ManyToOne
#JoinColumn(name = "owned_section_id", nullable = true)
private GroupSection primarySection;
#OneToMany(mappedBy = "primarySection", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Set<GroupSection> groupSections = new HashSet<>();
// Many to many mapping, I had lazy loading before, but tried Eager to see if error goes, it doesn't. :
#ManyToMany(fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
#JoinTable(name = "sectionjunction",joinColumns = {#JoinColumn(name = "msectionid")},
inverseJoinColumns = {#JoinColumn(name = "mnoteid")})
private Set<GroupNotes> groupNotesSet = new HashSet<>();
// One to many mapping below :
#OneToMany(mappedBy = "ownednotes", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupNotes> sectionsnotes = new HashSet<>();
}
GroupNotes :
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import javax.persistence.*;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
#Entity
#Table(name = "groupnotes")
public class GroupNotes implements Serializable {
#JsonIgnore
#ManyToMany(mappedBy = "groupNotesSet",fetch = FetchType.EAGER)
private Set<GroupSection> groupSectionSet = new HashSet<>();
#ManyToOne
#JoinColumn(name = "msectionid",nullable = true)
#JsonIgnore
private GroupSection ownednotes;
// Self mappings :
#JsonIgnore
#ManyToOne
#JoinColumn(name = "owned_note_id", nullable = true)
private GroupNotes primaryNote;
#OneToMany(mappedBy = "primaryNote", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Set<GroupNotes> groupNotesSet = new HashSet<>();
}
What am I doing wrong? Is it possible to simultaneously have one-to-many and many-to-many mapping between 2 classes. If yes, then what's the deal with that error. Kindly let me know. THank you. :-)
When calling listGroupSectionByCanvasid you open a transaction and a session. When the call return the session is closed. And when spring is returning the value it tries to read you object and because of lazy loading of your manytomany relation groupNotesSet and groupSections are hibernate collections that need the session. And in your case session doesn't exsit anymore.
One Group has many Users:
Group
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import javax.persistence.*;
import java.util.Collection;
import java.util.List;
#Entity
#Table(name = "GROUPS")
public class Group {
#Id
#Column(name = "ID")
private Long ID;
#Column(name = "NAME")
private String NAME;
//#JsonManagedReference
#OneToMany(mappedBy = "group"
//, fetch = FetchType.EAGER
//, cascade = CascadeType.ALL
)
private List<Users> itsUser;
//getters and setters are omitted for clarity
}
Users
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.persistence.*;
import static javax.persistence.GenerationType.SEQUENCE;
#Entity
#Table(name = "USERS")
#SequenceGenerator(name = "SEQUENCE_USER_ID", //my own name in java (unique)
sequenceName = "GENERATOR_SEQUENCE_USERS", //in database
initialValue = 1,
allocationSize = 1)
public class Users {
#JsonProperty(value = "id") //these play a role when both reading or writing
#Id
#Column(name = "ID")
#GeneratedValue(strategy=SEQUENCE, generator="SEQUENCE_USER_ID")
private Long ID;
#JsonProperty(value = "name")
#Column(name="NAME")
private String NAME;
#JsonProperty(value = "username")
#Column(name="USERNAME")
private String USERNAME;
#JsonProperty(value = "password")
#Column(name="PASSWORD")
private String PASSWORD;
#JsonProperty(value = "email")
#Column(name="EMAIL")
private String EMAIL;
#JsonProperty(value = "picture") //Now it works with both mypic and picture as json keys
#Column(name="PICTURE")
private String PICTURE;
//#Column(name="GROUP_ID") //we already have a ManyToOne for this, we cannot repeat it
#JsonProperty(value = "groups_id")
//to ignore it in jpa (http://stackoverflow.com/questions/1281952/jpa-fastest-way-to-ignore-a-field-during-persistence)
private Long itsGroupId;
#Transient
public Long getItsGroupId() {
if(itsGroupId == null) {
this.itsGroupId = group.getID();
} else {
//nop
}
return itsGroupId;
}
public void setItsGroupId(Long itsGroupId) {
this.itsGroupId = itsGroupId;
}
//#JsonIgnore
//#JsonProperty(value = "groups_id")
//#JsonBackReference
#ManyToOne(optional = false, targetEntity = Group.class)
#JoinColumn(
name = "GROUP_ID", //column name
referencedColumnName = "ID" //reference name
)
private Group group;
//getters and setters are omitted for clarity
}
We are using Spring with Spring-data and Jackson to do things automagically but we cannot configure the magic:
We are trying to stick on the following constraints at the same time:
1) Keep the ability to have a reference to the groupId and the ManyToOne relationship group.
This is easy to be achieved by putting #Transient annotation at the groupId because #Column is not allowed since we have already declared the #ManyToOne annotation. You also have to implement the getGroupId method accordingly.
2) Return a json of Users class that contains the groups_id.
This can be implemented by setting the #JsonProperty annotation.
3) Create a user class, and also save it in the database, by a json. The json contains groups_id which has as a value an integer for the foreign key.
This does not work because by setting it #Transient above, then the system refuses to save in the database something that is transient or at least this is how we interpret this exception:
HTTP Status 500 - Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: not-null
property references a null or transient value: com.pligor.mvctest.models.Users.group;
nested exception is org.hibernate.PropertyValueException:
not-null property references a null or transient value: com.pligor.mvctest.models.Users.group
On the backend do something like this:
Group group = groupRepository.findById(userResource.getGroupId());
if (group != null) {
User user = new User(userResource);
user.setGroup(group);
userRepository.save();
}
The idea behind this is that you need to fetch the group from the DB, to be able to link it with the newly created User
To be specific, I have these codes in Java (Class A and class B):
//Class A
package models;
import java.util.*;
import javax.persistence.*;
import play.db.ebean.Model;
#Entity
#Table(name="A")
public class A extends Model {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="id_a_seq")
#SequenceGenerator(name="id_a_seq", sequenceName="id_a_seq")
#Column(name="ID_A", insertable=true, updatable=true, unique=true, nullable=false)
public Integer id;
#Column(name="NOME")
public String nome;
#Column(unique = true, name="CPF")
public String cpf;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="A_B",
joinColumns={#JoinColumn(name="ID_A")},
inverseJoinColumns={#JoinColumn(name="ID_B")})
public List<B> lista = new ArrayList<B>();
}
//Class B
package models;
import java.util.*;
import javax.persistence.*;
import play.db.ebean.Model;
#Entity
#Table(name="B")
public class B extends Model {
public B() {}
public B(Integer id, String nome) {
this.id = id;
this.nome = nome;
}
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="id_b_seq")
#SequenceGenerator(name="id_b_seq", sequenceName="id_b_seq")
//#AttributeOverride(name = "id", column = #Column(name = "ID_B"))
#Column(name="ID_B", insertable=true, updatable=true, unique=true, nullable=false)
public Integer id;
#Column(name="NOME")
public String nome;
#ManyToMany(mappedBy="lista")
public List<A> lista = new ArrayList<A>();
}
When I run localhost:9000 in the browser, I get this error message:
"PersistenceException: Error with the Join on [models.B.lista]. Could not find the local match for [ID_B] Perhaps an error in a #JoinColumn".
I could solve this problem renaming ID_A and ID_B to ID (for both), but I can't have the name ID for the id columns (in table A and in table B). I need to figure out how to make Play to accept the ID_A and ID_B names for their id column name.
Change the mapping on your lista relationship to:
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "A_B",
joinColumns = {#JoinColumn(name = "ID_A", referencedColumnName = "ID_A")},
inverseJoinColumns = {#JoinColumn(name = "ID_B", referencedColumnName = "ID_B")})
public List<B> lista = new ArrayList<B>();
Note the addition of referencedColumnName to #JoinColumn.
referencedColumnName reference:
When used inside a JoinTable annotation, the referenced key column is in the entity table of the owning entity, or inverse entity if the join is part of the inverse join definition.