I have the following entities
Student
#Entity
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//getter and setter for id
}
Teacher
#Entity
public class Teacher implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//getter and setter for id
}
Task
#Entity
public class Task implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinTable(name = "student_task", inverseJoinColumns = { #JoinColumn(name = "student_id") })
private Student author;
#ManyToOne(optional = false)
#JoinTable(name = "student_task", inverseJoinColumns = { #JoinColumn(name = "teacher_id") })
private Teacher curator;
//getters and setters
}
Consider that author and curator are already stored in DB and both are in the attached state. I'm trying to persist my Task:
Task task = new Task();
task.setAuthor(author);
task.setCurator(curator);
entityManager.persist(task);
Hibernate executes the following SQL:
insert
into
student_task
(teacher_id, id)
values
(?, ?)
which, of course, leads to null value in column "student_id" violates not-null constraint
Can anyone explain this issue and possible ways to resolve it?
UPDATE
See my own solution below.
I've resolved my issue with the help of #SecondaryTable and switched from #JoinTable to #JoinColumn:
Task
#Entity
#SecondaryTable(name="student_task")
public class Task implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinColumn(table = "student_task", name = "student_id")
private Student author;
#ManyToOne(optional = false)
#JoinColumn(table = "student_task", name = "teacher_id")
private Teacher curator;
//getters and setters
}
Now, generated SQL looks like:
insert
into
student_task
(student_id, teacher_id, id)
values
(?, ?, ?)
and everything works just fine :)
I think you are missing the JoinColumns tag...
joinColumns = { #JoinColumn(name = "student_id", referencedColumnName = "id") }
joinColumns = { #JoinColumn(name = "teacher_id", referencedColumnName = "id") }
in author and curator respectively
Also remember, that the inversjoincolumn is the column in the owned table.. so it must be something like:
inverseJoinColumns = {#JoinColumn(name="id")})
Related
So I'm following a Spring Boot tutorial online, and I have two Java classes, which are entities: Order and Address:
Order.java:
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// more properties
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "shipping_address_id", referencedColumnName = "id")
private Address shippingAddress;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "billing_address_id", referencedColumnName = "id")
private Address billingAddress;
}
Address.java:
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// more properties
#OneToOne
#PrimaryKeyJoinColumn
private Order order;
}
Is this a bidirectional relationship (even though neither entity has a #JoinColumn with a mappedBy attribute) or is this two unidirectional relationships, Order->Address and Address->Order?
In the following example, there are 3 entities which have relations e.g. #ManyToMany, #OneToMany and #ManyToOne:
Student:
#Entity
#Data
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private String name;
#JsonIgnore
#ManyToMany(mappedBy = "students")
private Set<Subject> subjects = new HashSet<>();
}
Subject:
#Entity
#Data
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private String name;
#ManyToMany
#JoinTable(
name = "subject_student",
joinColumns = #JoinColumn(name = "subject_id"),
inverseJoinColumns = #JoinColumn(name = "student_id")
)
Set<Student> students = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "teacher_id", referencedColumnName = "id")
private Teacher teacher;
}
Teacher:
#Entity
#Data
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#JsonIgnore
#OneToMany(mappedBy = "teacher")
private Set<Subject> subjects;
}
1. In the subject entity, I tried to remove #JoinColumn and the related entities are connected as the example above:
#ManyToMany
#JoinTable(name="subject_student")
public Set<Student> students = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
private Teacher teacher;
So, if we want to use subject_id - student_id pair in subject_student table and use teacher_id in subject table as it is created in the example, can I use my simplified notation by removing #JoinColumn? Because, if there is not a special case, I think it is redundant to verbose notation of relations.
2. When I use the second approach, the columns are created as plural e.g. subjects_id - students_id in subject_student. So, can I prevent this and create them as in the previous example by using my approach?
I'm trying to make simple one to many relationship but hibernate is throwing error, no idea what to do.
Class Product:
public class Products {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne()
#JoinColumn(name = "user_id", foreignKey = #ForeignKey(name = "fk_user"))
private Users users;
}
and Class Users:
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long Id;
#OneToMany()
#JoinColumn(name = "product_id", foreignKey = #ForeignKey(name = "fk_product_id"))
private List<Products> productsList = new ArrayList<>();
}
I got error: Error executing DDL "alter table products drop constraint fk_user" via JDBC Statement
Here is a working example of such relationship :
Drawer class :
#OneToMany (mappedBy="drawer", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true)
private Set<Pocket> pockets;
Pocket class :
#ManyToOne (fetch=FetchType.EAGER)
#JoinColumn(name = "id_drawer", nullable = false)
private Drawer drawer;
Since the foreign key is on the child side (Products class), you can drop it on the parent side and reference to child as being the owning side:
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long Id;
#OneToMany(mappedBy="users")
private List<Products> productsList = new ArrayList<>();
}
I have two classes Student and Subject:
Student
#Entity
#Table(name = "students")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "full_name")
private String fullName;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "students")
private Set<Subject> subject;
//Getters & setters
}
Subject
#Entity
#Table(name = "subjects")
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "subject_name")
private String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Student> students;
//Getters & setters
}
When I am trying to save a student registered with more than one subject, I am not getting any records in the JoinTable.
My code for saving the entiers:
Subject subject = new Subject();
subject.setName("JAVA");
Subject subject2 = new Subject();
subject2.setName("C");
Subject subject3 = new Subject();
subject3.setName("C++");
Student student = new Student();
student.setFullName("dibya");
Set<Subject> subjects = new HashSet<>();
subjects.add(subject2);
subjects.add(subject);
subjects.add(subject3);
student.setSubject(subjects);
session.saveOrUpdate(student);
Please tell me where am I doing wrong.
Found the solution to my problem.
It was not with the configuration. With just mappedBy attribute ManyToMany relationship can be accomplished.
In my program I have added session.saveOrUpdate(subject); after saving saving the students.
This solved my problem. :)
A many-to-many relationship is modeled as a join table. This is a table with 2 columns, both are foreign keys to the tables of the repationship (Student and Subject in your case).
In Hibernate if you use #ManyToMany you need to either #JoinTable or #JoinColumn to define the relationship. Once it's done your update will work.
See
http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch07.html
and
http://www.dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
EDIT:
Applied to your code it should look like (UNTESTED):
In Student:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "student_id")
private int id;
....
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "students_subject", joinColumns = { #JoinColumn(name = "student_id") }, inverseJoinColumns = { #JoinColumn(name = "subject_id") })
private Set<Subject> subject;
In Subject
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "subject_id")
private int id;
...
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "students_subject", joinColumns = { #JoinColumn(name = "subject_id") }, inverseJoinColumns = { #JoinColumn(name = "student_id") })
private Set<Student> students;
Your case might be falling into mixing JPA and Hibernate cascading. Try annotate your #ManyToMany with additional
#Cascade({CascadeType.SAVE_UPDATE})
you can also consult this two links to check if this is really your case:
Confusion between JPA and Hibernate cascading
http://www.mkyong.com/hibernate/cascade-jpa-hibernate-annotation-common-mistake/
Is it possible to expose a manytomany relationship that uses a join entity (that contains extra data columns), below is my entities;
I'm trying to get 'purchases' to show in REST, I've put in 'products' as an example of a working REST mapping;
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = Purchase.class, orphanRemoval = true)
#JoinColumn(name = "user_id", updatable = false)
private List<Purchase> purchases = new ArrayList<>();
#ManyToMany
#JoinColumn(name = "user_id", updatable = false)
private List<Product> products = new ArrayList<>();
}
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
#Entity
public class Purchase implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
#ManyToOne(targetEntity = Prodect.class)
#JoinColumn(name = "product_id", referencedColumnName = "id")
private Product product;
#Column(name = "purchase_date")
private Date purchaseDate;
}
So if i send the REST call;
[GET http://localhost:8080/webapp/users/1]
It returns links for [http://localhost:8080/webapp/users/1/products] but not for [http://localhost:8080/webapp/users/1/purchases]
worked out what the issue was; I need to create a JpaRepository for the Purchase entity. Soon as I added that, the REST links for purchases are available.