Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent saving child object with JPA?

Tags:

java

jpa

I got OneToMany relationship between school and student entities. What I want to do is when I save a school object don't save or update students object.(and of course don't delete them)

When I try to save school object like below it also updates my student objects but I don't want them to be updated but only jointable. Is there any way?

I removed Cascade but its still not working.

School school = new School();
school.setStudents(studentList);
repository.save(school);

My Entity;

@OneToMany(fetch = FetchType.EAGER)
@JoinTable(name = "school_student", joinColumns = {@JoinColumn(name = "school_id")},
        inverseJoinColumns = {@JoinColumn(name = "student_id")})
private List<Student> students;

EDIT:

  • When I try to save/update/remove a School object DON'T save/update/remove Student object. Think that I don't have any insert/update/delete authority on Student table.
like image 638
hellzone Avatar asked Mar 24 '17 14:03

hellzone


4 Answers

When I try to save school object like below it also updates my student objects but I don't want them to be updated but only jointable. Is there any way?

I think you mean that you want the association itself to be managed -- i.e. which students are associated with the school -- but not the student details. This does not make much sense, I'm afraid, for both conceptual and practical reasons.

Conceptually, the "owning" side of a one-to-many relationship is always the "many". This is a bit arbitrary (but nevertheless still true) when you manage the association via a join table, but it is important when the association is managed directly via entity attributes. Updating the association requires managing the entities on the owning side, which in this case is your Students.

As a practical matter, managing the association from the School side only would raise some difficulties. For example, what if a new Student is added to a School? That student needs to be persisted before the association can be persisted, but you want to avoid that happening. There is a similar, but lesser, problem when you move a Student from one School to a different one.

Now, you can indeed avoid cascading persistence operations from Schools to their Students, but I would expect the result of doing so to be failure to manage the association between them in conjunction with managing Schools. If that's what you want to do, then you would omit any cascade attribute from the @OneToMany annotation, or else explicitly specify an empty list of cascade types.

Note also, however, that changes to all persistent entities are saved when you commit changes to your persistence context. If you want to modify Student entities without having those changes be saved to the database, then your best alternative is probably to detach those entities, or else to make detached copies of them.

Update:

As has been clarified in comments, the essential problem is how to modify the relationship between School and Student entities when the application does not have permission to update the base table of Student, but does have sufficient permissions on the join table by which the relationship is represented. You cannot do this automatically in conjunction with persisting changes to Schools, because School is not -- and cannot be -- the owning side of the relationship.

To clarify: JPA is attempting to save your Student entities when you move them to different Schools because for its purposes, the association between a Student and his particular School is part of the state of the Student. If the Student entity is attached to the persistence context and is dirty (e.g. because it was assigned to a different School), then it will be updated when changes to the PC are committed. This has nothing in particular to do with cascading or with the state of the School, except that you modify the states of your Students indirectly by moving them to different Schools' students lists.

Since you are using a join table, you could modify your object model to represent each student / school association as an entity in its own right. That would make sense if those associations had attributes of their own, such as enrollment dates, but otherwise I wouldn't recommend it.

The alternative is to write native queries (via JPA); you'll find lots of information about that around the net, such as https://blogs.oracle.com/JPQL01/entry/native_query_in_java_persistence. This does introduce some problems, however. In particular, it invalidates the EntityManager's cache, which can produce serious problems if not dealt with. It may also be significant to you that it produces entity changes without triggering the entity lifecycle methods that would fire if you performed the update normally.

With respect to the cache, the solution is to detach the affected Student and School (both old and new) entities. You can do that selectively with EntityManager.detach(), or en masse with EntityManager.clear(). Afterward, you'll need to re-query or merge the entities you want to continue using, and that could make for some messy problems, depending on how pervasively they are used and on what assumptions other code makes about them.

As for the lifecycle methods, if you need to ensure they fire, then you have the alternative of invoking them yourself.

This article provides a few more details on what you might need to do.

like image 143
John Bollinger Avatar answered Nov 19 '22 14:11

John Bollinger


Perhaps you can try @JoinColumn:

@OneToMany
@JoinColumn(name = "school_id", updatable = false, insertable = false)
private List<Student> students;
like image 9
Rock Avatar answered Nov 19 '22 14:11

Rock


The structure you're showing us is not a One-To-Many relation as you've listed. It's Many-To-Many.

@ManyToMany(cascade = {}, targetEntity = Student.class)
@JoinTable(name = "school_student", joinColumns = {
// references column "id" of "school"
    @JoinColumn(name = "school_id", referencedColumnName = "id")
}, inverseJoinColumns = {
// references column "id" of "student"
    @JoinColumn(name = "student_id", referencedColumnName = "id")
})
private List<Student> students;

It's really that simple.

like image 2
coladict Avatar answered Nov 19 '22 13:11

coladict


remove these annotations

@OneToMany(fetch = FetchType.EAGER)
@JoinTable(name = "school_student", joinColumns = {@JoinColumn(name = "school_id")},
    inverseJoinColumns = {@JoinColumn(name = "student_id")})

So hibernate entity manager may not associate this object to update automatically

So you have to write manual query to students in set method or pass list of students as argument in your code logic to set students

private List<Student> students;

then it will work as you expected

like image 1
harsha kumar Reddy Avatar answered Nov 19 '22 12:11

harsha kumar Reddy