Hibernate #OneToMany #ManyToOne mapping - java

everyone i have a question with using annotation #OneToMany/#ManyToOne; is it possible to create one user model with two sets of subjects in this model (conducted for the teacher and attending the student) instead of creating separate student and teacher models? I wrote such a code but when I want to get data about item and user, hibernate crashes the "Stack overflow" error.I will add that I use H2 Database.
User Entity:
#Entity
public class User{
#OneToMany(
mappedBy = "student",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
private Set<Item> items = new HashSet<>();
#OneToMany(mappedBy = "teacher",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
private Set<Item> carriedItems= new HashSet<>();
}
//id and other data
Item entity:
#Entity
public class Item{
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "student_id", nullable = false)
private User student;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "teacher_id", nullable = false)
private User teacher;
}
//id and other data
Thanks for help #Leviand

Based on your comments, looks like your code and logic needs some rework.
First, use instead of Item pojo two dedicated classes:
#Entity
#Table(name = Studend) // I'm guessing a table name
public class Student{
#JoinColumn(name = "student_id", nullable = false)
private User user;
}
#Entity
#Table(name = Teacher) // I'm guessing a table name
public class Teacher{
#JoinColumn(name = "teacher_id", nullable = false)
private User user;
}
Then since an User can only be connected to a single Teacher or Student, refact that in something like:
#Entity
#Table(name = User) // I'm guessing a table name
public class User{
#OneToOne(
mappedBy = "student",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
private Student student;
#OneToMany(mappedBy = "teacher",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
private Teacher teacher;
}
Hope this helps :)

Related

JPA #JoinTable with composite (2 column) primary keys

In a spring-boot app, I've got the following entity definition:
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#JoinTable(name = "userrole",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Role> roles;`
I'm using Spring-data-jpa,Hibernate with H2 as the database.
The trouble is that spring-data-jpa, hibernate always generate/creates the join table (DDL) 'userrole' with a single column primary key. e.g. 'username'.
Hence, if records such as {'username', 'user_role'} and {'username', 'admin_role'} is inserted in the join table ('userrole'), the next insert fails with an error due to the 'duplicate' primary key.
I've tried using both columns in the above definition, as well as the following variation:
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name = "username"),
#JoinColumn(name = "role") })
private List<Role> roles;`
But that they resulted in the same or worse problems, e.g. and in the latter, even table creation fails because only a single column is used as primary key for the jointable. Role is simply another table with 2 columns 'role' and 'description', basically a role catalog.
How do we specify to JPA that the #JoinTable should use both 'username' and 'role' columns as composite primary keys?
edit:
I tried using an independent table/entity as suggested, thanks #Kamil Bęben
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#OneToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
mappedBy = "username",
orphanRemoval = true
)
#ElementCollection
private List<UserRole> roles;
UserRole is defined as such
#Data
#NoArgsConstructor
#Entity
#Table(name = "userrole")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userrole_seq")
Long id;
#Column(nullable = false, name = "username", length = 100)
private String username;
#Column(nullable = false, name = "role", length = 50)
private String role;
the repository for that user-roles join table is defined as
#Repository
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
UserRole findByUsernameAndRole(String username, String role);
List<UserRole> findByUsername(String username);
List<UserRole> findByRole(String role);
}
Admittedly, ugly, but that it works. And that somehow, it seemed to use the correct findByUsername() method to retrieve the roles as is relevant to the user, probably related to the 'mappedBy' clause. 'black magic'! There's lots more that I'd still need to find my way around JPA, Spring, Spring-data
edit2:
further update:
the original #JoinTable works as well.
But that the relations need to be specified as #ManyToMany
#ManyToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.MERGE
)
#JoinTable(name = "usersroles",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
private List<Role> roles = new ArrayList<Role>();
This creates 2 column primary keys as expected for the 'users-roles' table
Thanks to #Roman
If Role only has two columns, eg user_id and role, the way to map this in jpa would be as following
#ElementCollection
#CollectionTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "role")
List<String> roles = new ArrayList<>();
Otherwise, jpa really requires each entity's identifier and join columns to be separate columns, so Role entity would have to have columns like id, user_id and role_name. Could look like this .:
class Role {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id");
User user;
String roleName;
// Other fields
}
And in the User entity
#OneToMany(mappedBy = "user") // user is Field's name, not a column
List<Role> roles = new ArrayList<>();
Further reading

JPA Biderecional relation

I am trying to create a relational 1-to-1 between User Table and Code Table.
1 User can have 1 code but it's not mandatory.
Code class
#Entity
#Table(name = "codes")
public class Code {
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", nullable = false)
private User user;
...
}
User class
#Entity
#Table(name = "users")
public class User {
...
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Code code;
}
Flows:
When I create a Code, I provide a user and it will add automatically the User ID into Code table BUT the User table keeps the code column empty.
Problem:
I am doing something wrong here? I was expecting that when I create a code, the User table also updates with PIN code.
The owner is User. The User controls the relationship.
#Entity
#Table(name = "codes")
public class Code {
...
#OneToOne(mappedBy = "code", fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
...
}
#Entity
#Table(name = "users")
public class User {
...
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Code code;
}

Spring data JPA clone entity with LAZY relationships

I have Entity:
#Data
#Entity
#Table(name = "USERS")
public class User{
#Id
#Column(name = "GUID", nullable = false)
private String guid;
#OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = LAZY)
private List<Role> roles;
#OneToMany(mappedBy = "user", fetch = LAZY, cascade = {CascadeType.ALL})
private List<Person> persons;
And I need clone this entity. I do it like this:
usersRepository.detach(user);
But I can not get roles and persons becaus it LAZY. I use hack:
user.getRoles().size();
user.getPersons().size();
usersRepository.detach(user);
But I do not like it. Can I make it easier?

Hibernate identity generator issue

I am having a particular issue when trying to save a collection of objects with hibernate. It seems that when I have more than one object of the same type, hibernate fails to generate the identifier, so I get a org.hibernate.NonUniqueObjectException .
Example:
App1 --> urls
{strApplicationId:1;URLTypeEntity{strCode:1,strDescription:Reply},strURL:www.address1.com},
{strApplicationId:1;URLTypeEntity{strCode:1,strDescription:Reply},strURL:www.address2.com},
{strApplicationId:1;URLTypeEntity{strCode:2,strDescription:Home},strURL:www.address3.com}
If I do not have two URLs with the same URLTypeEntity on the collection, the error is not triggered
#Entity
#Table(name = "tbl_urls")
public class URLEntity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="intCode")
private Integer intCode;
private String strApplicationID;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "intType", referencedColumnName = "intCode")
private URLTypeEntity objURLType;
private String strURL;
}
#Entity
#Table(name = "tbl_applications")
public class ApplicationEntity
{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "strApplicationID")
private List<URLEntity> colURLs;
}
ApplicationEntity must also have an id.
The solution was changing the CascadeType from ALL To Merge
#OneToMany(cascade = CascadeType.ALL, mappedBy = "strApplicationID")
Changed To
#OneToMany(cascade = CascadeType.MERGE, mappedBy = "strApplicationID")

#OneToMany unknown target entity property, with several #ManyToOne in children Entity

I have a error with Hibernate:
org.hibernate.AnnotationException: mappedBy reference
an unknown target entity property: ch.zkb.documenz.backend.model.Template.user
in ch.zkb.documenz.backend.model.User.templates
I have two Tables: User and Template, but in Template I need to use the id of the user in: createdBy, lockBy or lastUpdateBy, I think I have to use the #onetomany like in my example, but something it's incorrect, What is the best practice to do this then?
public class User implements Serializable {
#Id
#Column(name = "user_id", unique = true, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Set<Template> templates;
public class Template implements Serializable {
#Id
#Column(name = "template_id", unique = true, nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "createdBy")
private User createdBy;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lastUpdateBy")
private User lastUpdateBy;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lockBy")
private User lockBy;
EDIT, I have now a Problem with the Bidirectional LAZY load, I want to get the user wo created the template but I can't.. always is NULL, but in the DB is stored properly
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "createdBy")
private Set<Template> createdTemplates;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "createdBy", referencedColumnName = "user_id", nullable = false)
private User createdBy;
You schould map to your private keys seperately, then in an transient method you can merge them.
private Set<Template> templates = new HasSet<Template>();
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "createdBy")
private Set<Template> createdByTemplates;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "lastUpdateBy")
private Set<Template> lastUpdateByTemplates;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "lockBy")
private Set<Template> lockByTemplates;
#Transient
public Set<Template> getTemplates(){
if(getCreatedByTemplates() != null){
templates.addAll(getCreatedByTemplates());
}
if(getLockByTemplates() != null){
templates.addAll(getLockByTemplates());
}
if(getLastUpdateByTemplates() != null){
templates.addAll(getLastUpdateByTemplates());
}
return templates;
}
The exception message is clear about what doesn't work, the mappedBy must refer to the attribute name that you do have in the other entity,
in your Template entity there's no attribute named "user", try to remove the mappedBy.
I believe mapped by is looking for bean name "user" in Template.
public class User implements Serializable {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Set<Template> templates;
public class Template implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
Change is variable name createdby to user in Template Class would help

Categories