I have the following three tables:
player (id, name)
status (id, status_text)
player_status (player_id, status_id)
The player_status table combines the first two tables with a n-to-n relationship
Table "player":
id
player
agzua76t34gusad
"Anna"
sdahb433tbjsdbv
"Julia"
Table "status":
id
status_text
jjbsdnv8677v6df
"operational"
bulsiu783fdszjh
"violated"
Table "player_status"
record_id
record_status_id
agzua76t34gusad
jjbsdnv8677v6df
sdahb433tbjsdbv
bulsiu783fdszjh
The player can have a status assigned or not.
Now when a player has a status, how can I remove this status, so that the player but also the status stays in the tables but only the relation in the player_status table will be removed.
These are the classes for Player and Status
#Entity
#Table(name = "player")
public class Player {
#Column(nullable = false)
private String id;
#Column(nullable = false)
private String name;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "player_status",
joinColumns = {#JoinColumn(name = "player_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "status_id", referencedColumnName = "id")})
private Set<Status> statusList = new HashSet<>();
}
#Entity
#Table(name = "status")
public class Status {
#Column(nullable = false)
private String id;
#Column(name = "status_text", nullable = false)
private String statusText;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "statusList")
#JsonIgnore
private Set<Player> players = new HashSet<>();
}
This is how the relation table is created in *.sql:
create table player_status
(
player_id varchar references player (id) on update cascade on delete cascade,
status_id varchar references status (id) on update cascade
);
How can I delete only an entry from the player_status table? I tried to retrieve a player from the db, changed/removed his status, but this did not update the player.
You just remove the associated Status from the statusList collection, and the row of the association table will be automatically deleted when the EntityManager is flushed.
Related
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
how do i create entity for area_item_table which have foreign key as primary key
the Item entity:
#Entity
#Table(name = Item.ITEM)
public class Item {
#Id
#Column(name = ITEM_ID)
private long itemId;
#OneToOne
#JoinTable(name = "area_item_table",
joinColumns = {#JoinColumn(name = ITEM_ID, referencedColumnName = ITEM_ID)},
inverseJoinColumns = {
#JoinColumn(name = AREA_ID, referencedColumnName = ID)})
private Area area;
[removed get set and unrelated fields from entity]
}
the Area entity:
#Entity
#Table(name = Area.AREA)
public class Area {
#Id
#Column(name = ID, nullable = false)
private Long id;
[removed get set and unrelated fields from entity]
the table area_item_table :
item_id
area_id
1
121
is there a way to create an Entity for this table without creating new primary key field
Im learning Hibernate and cant solve problem.
Why Hibernate does delete in table "writer_post" after update entity Writer?
Table writer_post have writer_id and post_id entity(Writer, Post) and have annotation #MoreToOne.
What did I do wrong?
Update:
I updating existing Writer. Same in table existing row for Writer.
Example:
Writer: id: 1, first_name: Dmitry, last_name: Polischuk.
Writer_post: writer_id: 1, post_id: 1.
I does update parameter only last_name.
Create tables
CREATE TABLE IF NOT EXISTS Label(
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS Writer(
id SERIAL PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL
);
CREATE TABLE IF NOT EXISTS Post(
id SERIAL PRIMARY KEY,
content VARCHAR(100) NOT NULL,
create_date TIMESTAMP DEFAULT NULL,
updated_date TIMESTAMP DEFAULT NULL,
status VARCHAR(30) NOT NULL
);
CREATE TABLE IF NOT EXISTS Post_Label(
label_id INT NOT NULL,
post_id INT NOT NULL,
FOREIGN KEY (post_id) REFERENCES Post(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (label_id) REFERENCES Label(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS Writer_Post(
post_id INT NOT NULL,
writer_id INT NOT NULL,
FOREIGN KEY (post_id) REFERENCES Post(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (writer_id) REFERENCES Writer(id) ON DELETE CASCADE ON UPDATE CASCADE
);
Entity Writer
#Getter
#Setter
#RequiredArgsConstructor
#Entity
#Table(name = "Writer")
public class Writer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#ManyToOne(targetEntity = Post.class, cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name = "writer_post",
joinColumns = #JoinColumn(name = "writer_id"),
inverseJoinColumns = #JoinColumn(name = "post_id"))
private List<Post> posts;
Entity Post
#Getter
#Setter
#RequiredArgsConstructor
#Entity
#Table(name = "Post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "Content")
private String content;
#Column(name = "create_date")
private Date created;
#Column(name = "updated_date")
private Date updated;
#Enumerated(EnumType.STRING)
#Column(name = "status")
private PostStatus status;
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name = "post_label",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "label_id"))
private List<Label> labels;
Code for update Writer
#Override
public Writer update(Writer writer) {
try(Session session = HibernateUtil.getSessionFactory().openSession()){
session.beginTransaction();
session.saveOrUpdate(writer);
session.getTransaction().commit();
return writer;
}
}
Result stacktrace
Hibernate: update Writer set first_name=?, last_name=? where id=?
Hibernate: delete from writer_post where writer_id=?
Your mapping looks like one post can be mapped to many writers. I guess you meant vice-versa. So, it's worth trying to change this:
#ManyToOne(targetEntity = Post.class, cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name = "writer_post",
joinColumns = #JoinColumn(name = "writer_id"),
inverseJoinColumns = #JoinColumn(name = "post_id"))
private List<Post> posts;
to this:
#OneToMany(targetEntity = Post.class, cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name = "writer_post",
joinColumns = #JoinColumn(name = "writer_id"),
inverseJoinColumns = #JoinColumn(name = "post_id"))
private List<Post> posts;
I have three entity classes.
Product, Category, and SubCategory.
A Category has a OneToMany relation with SubCategory
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "category_id")
private Long categoryId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "category_id")
private List<SubCategory> subCategories;
}
The product is assocciated with a Category and one of its SubCategories
#Entity
#Table(name = "PRODUCTS")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = ("id"))
private Long id;
#ManyToOne
#JoinColumn(name = "category", unique = false, nullable = true, insertable = true, updatable = true)
private Category category;
#ManyToOne
#JoinColumn(name = "sub_category", unique = false, nullable = true, insertable = true, updatable = true)
private SubCategory subCategory;
}
now if I delete a Category, all its SubCatogries are deleted, but I also want the associations in Product to be updated to null. I thought of manually fetching all the products with the associated deleted Category and updating them manually, but is there a way to handle this with JPA annotations?
This update from JPA will be very inefficient in performance perspective.
Your table PRODUCTS has columns category abd sub_category which linked with correspond tables by foreign keys. Add to end of definition of each of these columns string 'ON DELETE SET NULL' and what you want will be done by database automatically.
The Product entity has a ManyToOne relationship with SubCategory that means that the SubCategory entity has a OneToMany relationship with Product. So, in the SubCategory class, where you have defined the OneToMany relationship, you need to mention the cascadetype = remove
#manytoone(cascade = cascadetype.remove)
Hope it solves your problem
I have 3 tables in my database: Students, Courses and Students_Courses
Students can have multiple courses and courses can have multiple students. There is a many-to-many relationship between Students and Courses.
I have 3 cases for my project and courses added to my Courses table.
(a) When I add a user, it gets saved fine,
(b) When I add courses for the student, it creates new rows in User_Courses - again, expected behaviour.
(c) When I am trying to delete the student, it is deleting the appropriate records in Students and Students_Courses, but it is also deleting Courses records which is not required. Even if I don't have any user in a course, I want the course to be there.
Below is my code for tables and annotate classes.
CREATE TABLE `Students` (
`StudentID` INT(11) NOT NULL AUTO_INCREMENT,
`StudentName` VARCHAR(50) NOT NULL
PRIMARY KEY (`StudentID`)
)
CREATE TABLE `Courses` (
`CourseID` INT(11) NOT NULL AUTO_INCREMENT,
`CourseName` VARCHAR(50) NOT NULL
PRIMARY KEY (`CourseID`)
)
CREATE TABLE `Student_Courses` (
`StudentId` INT(10) NOT NULL DEFAULT '0',
`CourseID` INT(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`StudentId`, `CourseID`),
INDEX `FK__courses` (`CourseID`),
INDEX `StudentId` (`StudentId`),
CONSTRAINT `FK__courses` FOREIGN KEY (`CourseID`) REFERENCES `courses` (`CourseID`) ON DELETE NO ACTION,
CONSTRAINT `FK_students` FOREIGN KEY (`StudentId`) REFERENCES `students` (`StudentId`)
)
This is the Java code generated by Hibernate:
#Entity
#Table(name = "Students")
public class Students implements java.io.Serializable {
private Integer StudentID;
private String Students;
private Set<Courses> Courseses = new HashSet<Courses>(0);
public Students() {
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "StudentID", unique = true, nullable = false)
public Integer getStudentID() {
return this.StudentID;
}
public void setStudentID(Integer StudentID) {
this.StudentID = StudentID;
}
#Column(name = "Students", nullable = false, length = 50)
public String getCampaign() {
return this.Students;
}
public void setCampaign(String Students) {
this.Students = Students;
}
#ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
#JoinTable(name = "Student_Courses", joinColumns = {
#JoinColumn(name = "StudentId", nullable = false, updatable = false)}, inverseJoinColumns = {
#JoinColumn(name = "CourseID", nullable = false, updatable = false)})
public Set<Courses> getCourseses() {
return this.Courseses;
}
public void setCourseses(Set<Courses> Courseses) {
this.Courseses = Courseses;
}
}
#Entity
#Table(name = "Courses")
public class Courses implements java.io.Serializable {
private Integer CourseID;
private String CourseName;
private Set<Students> Studentses = new HashSet<Students>(0);
public Courses() {
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CourseID", unique = true, nullable = false)
public Integer getCourseID() {
return this.CourseID;
}
public void setCourseID(Integer CourseID) {
this.CourseID = CourseID;
}
#Column(name = "CourseName", nullable = false, length = 100)
public String getCourseName() {
return this.CourseName;
}
public void setCourseName(String CourseName) {
this.CourseName = CourseName;
}
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "Courseses")
public Set<Students> getStudentses() {
return this.Studentses;
}
public void setStudentses(Set<Students> Studentses) {
this.Studentses = Studentses;
}
}
How can I achieve what I have described? I could not find any reasonable documentation on the web.
I found the correct mapping (and tested that with JUnit with an extensive case) in a similar scenario. I don't think I am going to post testing code because it would take long time to adapt to this example. Anyway the key is to:
Not use mappedBy attribute for the annotations, use join columns
List the possible CascadeTypes excluding REMOVE
In OP's example
#ManyToMany(fetch = FetchType.LAZY,
cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
},
targetEntity = Course.class)
#JoinTable(name = "XTB_STUDENTS_COURSES",
inverseJoinColumns = #JoinColumn(name = "COURSE_ID",
nullable = false,
updatable = false),
joinColumns = #JoinColumn(name = "STUDENT_ID",
nullable = false,
updatable = false),
foreignKey = #ForeignKey(ConstraintMode.CONSTRAINT),
inverseForeignKey = #ForeignKey(ConstraintMode.CONSTRAINT))
private final Set<Course> courses = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY,
cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
},
targetEntity = Student.class)
#JoinTable(name = "XTB_STUDENTS_COURSES",
joinColumns = #JoinColumn(name = "COURSE_ID",
nullable = false,
updatable = false),
inverseJoinColumns = #JoinColumn(name = "STUDENT_ID",
nullable = false,
updatable = false),
foreignKey = #ForeignKey(ConstraintMode.CONSTRAINT),
inverseForeignKey = #ForeignKey(ConstraintMode.CONSTRAINT))
private final Set<Student> students = new HashSet<>();
Extensive JUnit testing verified that:
I can add courses to students and vice versa flawlessly
If I remove a course from a student, the course is not deleted
Vice versa
If I remove a student, all courses are detached but they are still persisted (to other students) in database
Vice versa
Based on what you've told me you don't want cascade=CascadeType.ALL on the getCourseses method in Student. Keep in mind that Hibernate cascades are not the same as database cascades. Even if you don't have any cascades then Hibernate will delete the Students_Courses record.
The best way to think of Hibernate cascades is that if you call an operation on an entity and that operation is listed in the cascade list then that operation will be called on all of the child entities.
For example, when you call delete on Student, since delete is in the cascade list for Courses, Hibernate will call delete on each of the Course entities referenced by that student. That is why you are seeing the Course records disappearing.
Don't worry about database cascades, Hibernate will take care of those on its own.
You just need to Remove cascade = CascadeType.ALL in Student class only no change is required in Courses class
and add the below code
cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.DETACH}..
It means while deleting owner class record it will not delete a non-owner record.
After this, On Delete it will delete only from Student table and student_course.
course table data remains the same.