I have two tables, student and teacher, with a relationship ManyToOne. The table structure is as follows
student(
id long,
student_id string,
....
teacher_id string,
active boolean
)
teacher(
id long,
teacher_id string,
....
active boolean
)
I'm using Spring boot and Hibernate. Here when updating an entity, the active column of the existing row in the table will be set to false and a new row will be added with a new id(long) and active as true. That is why there are two id values in each table. The problem here is I have specified the student-teacher relation as many to one in my entity with the foreign key as teacher_id.
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "student_id")
private String studentId;
#ManyToOne
#JoinColumn(name = "teacher_id", referencedColumnName = "teacher_id")
private Teacher teacher;
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
#Entity
#Table(name = "teacher")
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "teacher_id")
private String teacherId;
#OneToMany(mappedBy = "teacher")
private Set<Student> students = new HashSet<>();
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
But since multiple teachers can occur with the same teacher_id, this fails. Is there any way to give a condition to the relationship to fetch the teacher with active as true? In table, there will be only one teacher with the given id and active as true.
I just came across your post. I had the same requirement few months ago and this is what i did..
public class Student {
#Column(name = "teacher_id ")
private String teacherId;
#ManyToOne
#JoinFormula(value = "(Select t.id from teacher t where t.teacher_id= teacher_id and t.active=1)"
)
private Teacher teacher;
}
#Entity
#Table(name = "teacher")
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
This approach works for my case. If there is a better one you can share it.
Thanks
There should be no teacher_id foreign key field in the Teacher entity. Rather, just use the primary key id column instead. Consider this version of your entities:
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "student_id")
private String studentId;
#ManyToOne
#JoinColumn(name = "teacher_id", referencedColumnName = "id")
private Teacher teacher;
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
#Entity
#Table(name = "teacher")
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "teacher")
private Set<Student> students = new HashSet<>();
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
This design should enforce that a given student can be associated with only one teacher (though a given teacher can have multiple students).
Related
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 have one table called image and another table called duplicate. There are two OneToMany relations are associated.
I am not quite sure whether below implementation is the right approach for that. Moreover whether we require that second private List<DuplicateEntity> duplicateEntities2; ?
ImageEntity:
#Entity
#Table(name = "image")
public class ImageEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "filename")
private String fileName;
#OneToMany(mappedBy = "imageEntity1")
private List<DuplicateEntity> duplicateEntities1;
#OneToMany(mappedBy = "imageEntity2")
private List<DuplicateEntity> duplicateEntities2;
}
DuplicateEntity:
#Entity
#Table(name = "duplicate")
public class DuplicateEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "image_a_id")
private ImageEntity imageEntity1;
#ManyToOne
#JoinColumn(name = "image_b_id")
private ImageEntity imageEntity2;
}
I have 3 tables which are Person Login and Account.
Person and Login is OneToOne relation and Login has one FK which is connected Person's id column called PERSON_ID.
Person(one) and Account(many) is OneToMany relation and Account has one FK which is connected Person's id column called PERSON_ID as well .
what i want to do is when i delete one data from Account , nothing happen to Person and Login.
if i delete one data from Person which id=1, Login's PERSON_ID=1 data will be deleted , and all of the data PERSON_ID=1 from Account will be deleted as well.
if i delete one data from Login which PERSON_ID=1, Person 's id=1 data will be deleted , and all of the data PERSON_ID=1 from Account will be deleted as well.
how should i set the cascade ?
i've tried dozens of times and still can't find the logic in there, thanks!!
here's my code of all 3 tables without setting cascade:
`
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "ID", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "SEX")
private String sex;
#OneToMany(mappedBy = "person",fetch = FetchType.LAZY)
private List<Account> account;
#OneToOne(mappedBy = "person")
private Login login;
#get..
#set..
}
`
#Entity
#Table(name = "ACCOUNT")
public class Account {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "ACCOUNT")
private String account;
#Column(name = "AMOUNT")
private String amount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID",referencedColumnName = "ID")
public Person person;
#get..
#set..
}
`
#Entity
#Table(name = "LOGIN")
public class Login {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "USERNAME")
private String userName;
#Column(name = "PASSWORD")
private String password;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", referencedColumnName = "ID")
private Person person;
#get..
#set..
}
It's been a while, but if I'm not mistaken you need to use the cascade=REMOVE option on the OneToMany and OneToOne relationships. In the OneToOne I think you need to specify cascade=REMOVE on the side that does NOT own the relationship, that is, the side that also contains the "mappedBy" property.
Finally, I believe JPA will NOT automatically load lazy relationships and then cascade them. I'm thinking you may need to fetch the relationship before you delete the parent entity (otherwise JPA will not know what to delete).
I am struggling with how to setup my JPA entity classes and which annotations should go where
I have the following tables:
Table Customer {
id: primary key,
name
}
Table CustomerDimension {
id: primary key, foreign key(Customer.id),
detail
}
Currently I have the following entity classes:
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#OneToOne
private CustomerDimension customerDimension;
}
public class CustomerDimension {
// ? what is meant to go here?
private long id;
#Column(name = "detail")
private String detail;
}
What annotation is meant to go on CustomerDimension.id to allow me to insert a new Customer that has a new CustomerDimension?
Should CustomerDimension also have a reference back to Customer?
Table Customer {
id: primary key,
name
}
Table CustomerDimension {
id: primary key,
foreign key(Customer.id),
detail
}
CustomerDimension is the owning side. so, the #OneToOne mapping should be like
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
}
public class CustomerDimension {
#Id
private long id;
#Column(name = "detail")
private String detail;
#OneToOne
private Customer customer;
}
You have the following problems :
Customer and CustomerDimension need the annotation #Entity.
In your DDL, the table CustomerDimension has a foreign key on Customer. Hence, the #OneToOne relationship should be declared on CustomerDimension's side.
Still in the DDL, your foreign key does not have an explicit name. I will assume it is customer_id and use it to declare the #JoinColumn (see below)
#Column annotations are required only if you need the column to have a name which is different from the attribute's name (but you can keep them for clarity).
Here is how I would map it.
#Entity
#Table(name = "Customer") //Optional
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "name") //Optional
private String name;
}
And for CustomerDimension :
#Entity
#Table(name = "CustomerDimension") //Optional
public class CustomerDimension {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "detail") //Optional
private String detail;
#OneToOne
#JoinColumn(name = "customer_id") //NOT optional
private Customer customer
}
EDIT (answer to your comment) :
If you really want your FK to be the primary key, you can do it like this :
#Entity
#Table(name = "CustomerDimension") //Optional
public class CustomerDimension {
#Column(name = "detail") //Optional
private String detail;
#Id
#OneToOne
#JoinColumn(name = "id") //NOT optional
private Customer customer
}
I still wonder why you do not put all information in the same table. It would save you a SQL join.
What you have here is a OneToMany biidirectional relationship with a foreign key instead of a join table. A join table seems to be preferred by vendors, but it's OK.
So, you have a list (or set) of CustomerDimensions in Customer, but with the mappedBy value set.
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy="customer")
List<CustomerDimensions> dimensions;
}
and
public class CustomerDimension {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "detail")
private String detail;
#ManyToOne
Customer customer;
}
It's natural that Customers have a set of dimensions. By having a bidirectional mapping, if you have a dimension, then you can look up the customer easy (just reference the customer field)
EDIT: Since the CustomerDimension table has a Customer id reference, you can select many CustomerDimensions for one Customer, hence a OneToMany relationship. In order to set the CustomerDimension.customer_id field, simply put a CustomerDimension in the Customers list of dimensions.
I am new to JPA and stuggles with defining the relations between my classes. I have a class called Player and a class called Game. A game holds references to two Player instances. The question is, how should this be modelled?
This is my current code:
#Entity
#Table(name = "t_player")
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "uuid")
private final String uuid = UUID.randomUUID().toString();
I think this is ok, but my problem is in the Game class:
#Entity
#Table(name = "t_game")
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class Game {
public Game() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Basic
#Column(name = "uuid")
private final String uuid = UUID.randomUUID().toString();
#OneToOne
#PrimaryKeyJoinColumn
#JoinColumn(name = "id")
private Player player_1;
#OneToOne
#PrimaryKeyJoinColumn
#JoinColumn(name = "player_2")
private Player player_2;
public Game(Player player_1, Player player_2) {
this.player_1 = player_1;
this.player_2 = player_2;
}
}
This is not working, my table t_game only has two field; id and uuid. Where is my problem?
Remove the PrimaryKeyJoinColumn annotation, as I don't think it is what you meant to use, as it conflicts with the joincolumn definition. Use the joincolumn annotation instead to define the foreign key field name and the field it references if necessary.