7

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.
hellzone
  • 5,393
  • 25
  • 82
  • 148
  • 1
    Do you mean some other way than via the `cascade` attribute of your `@OneToMany` annotation? – John Bollinger Mar 24 '17 at 14:31
  • @JohnBollinger I don't get what you mean. I can use Cascade but I don't know how to do it with cascade. – hellzone Mar 24 '17 at 14:33
  • Do you mean you're using a third table to define which students go to which schools? That would be need a `@ManyToMany` annotation, not `@OneToMany`. Without having `cascade`, it defaults to no action. – coladict Mar 24 '17 at 14:34
  • Not necessarily, @coladict. A join table is most frequently used (indeed is required) for many-to-many relationships, but can be used to map other kinds of relationships, too. JPA explicitly documents support for that. – John Bollinger Mar 24 '17 at 14:38
  • 1
    I have written an answer, but in the course of doing so I came to suspect that this is an X/Y problem. Can you explain a bit more fully what you are really trying to accomplish here? – John Bollinger Mar 24 '17 at 15:06
  • Can you update your post to include both sides of the relationship? – nickrak Mar 28 '17 at 04:00
  • Since you've posted a bounty, I presume that you find my answer unsatisfactory. That's your prerogative, but I doubt you'll get a better answer -- from me or anyone -- without explaining why mine does not answer the question to your satisfaction. – John Bollinger Mar 31 '17 at 17:37
  • @hellzone What is your JPA implementation? – Tair Apr 01 '17 at 19:06

5 Answers5

9

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • The difficulties you mentioned are irrelevant. I already don't have any cascade annotation but it still updates my child objects. Whats your solution? – hellzone Mar 25 '17 at 08:52
  • @hellzone, the difficulties I mention are ***key***. They explain why what you are asking to do, if indeed I have characterized it correctly, *is not supported by JPA*. You cannot manage a relationship without managing the entities on its owning side. That you seem to want to do so is a substantial reason for my conclusion, expressed as a comment on the question, that you are presenting an X-Y problem: you are asking for X as a means to solve a different problem, Y, and you would be better off asking directly about Y. – John Bollinger Mar 27 '17 at 13:27
  • Its like I am asking what is the output of the System.out.println("test" == "test")? and you are saying don't use == symbol when you compare Strings. Man I don't care == or String equality or some other things. Just tell me the output of the program. "For example, what if a new Student is added to a School? " I don't have any insert/update/delete authority on Students table and application doesn't have any interface to add a new student object. – hellzone Mar 27 '17 at 13:52
  • @hellzone, so then yes, it *is* an X-Y question. The Y is "How can I modify the relationship between `Student`s and `School`s when I do not have permission to modify `Student`?" I will shortly update my answer to discuss that. – John Bollinger Mar 27 '17 at 14:04
  • 1
    Just in case 51.1K reputation is not enough to give credibility - This answer IS the right one and denote a perfect understanding of JPA principles. However I'd say that mapping the association table as a distinct entity is the solution to the OP problem whether or not the association holds attributes – Gab Mar 27 '17 at 15:19
9

Perhaps you can try @JoinColumn:

@OneToMany
@JoinColumn(name = "school_id", updatable = false, insertable = false)
private List<Student> students;
Rock
  • 329
  • 2
  • 6
2

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.

coladict
  • 4,799
  • 1
  • 16
  • 27
  • A many-to-many relationship requires a join table, but use of a join table does not necessarily make the relationship many to many. Mapping it that way could work around the problem, provided that the relationship is unidirectional, but it changes the semantics of the relationship, and very well could cause other problems. – John Bollinger Mar 31 '17 at 17:33
1

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

harsha kumar Reddy
  • 1,251
  • 1
  • 20
  • 32
1

i suggest to remove the school.setStudents(studentList) because you telling hibernate to edit the many2one field of this students. try to session.clear() if it did not work than remove the satement and make sure you do this from the student side. i mean define a scool for student not the opposite.

package main;

import org.hibernate.Session;

import hibernate.ConnectionFactory;
import hibernate.models.Car;
import hibernate.models.Client;

public class MainClass {

    public static void main(String[] args) {
        try {
            /*
             * if you remove cascading configuration from both side 
             * hibernate will not save unsaved transient instance
             */
            if(ConnectionFactory.buildSessionFactory()){
                Session session = ConnectionFactory.getSessionFactory().openSession();
                session.beginTransaction();
                // save a client instance 
                Client client = new Client();
                client.setName("client");
                session.save(client);
                session.getTransaction().commit();
                session.close();


                session = ConnectionFactory.getSessionFactory().openSession();
                session.beginTransaction();
                Car car = new Car();
                car.setName("Audi");
                client = (Client) session.get(Client.class, 1);
                client.setName("client1");
                car.setClient_id(client);
                /* inset statement
                 * to prevent hibernate from updating client instance just clear
                 * the session and save only the wanted instance 
                 */
                session.clear();
                session.save(car);
                session.getTransaction().commit();
                session.close();

                session = ConnectionFactory.getSessionFactory().openSession();
                session.beginTransaction();
                car = (Car) session.get(Car.class, 1);
                car.setName("Audi1");
                client = (Client) session.get(Client.class, 1);
                client.setName("Charif1");
                car.setClient_id(client);
                /* update statement
                 * to prevent hibernate from saving other instance just clear
                 * the session and update only the wanted instance 
                 */
                session.clear();
                session.saveOrUpdate(car);
                session.getTransaction().commit();
                session.close();

                /**
                 * but you you are doing from the wrong side
                 * you are trying to tell that this client have
                 * this cars but Hibernate will try to link this 
                 * car by editing the many2one field ??!! 
                 * so remove the statement 
                 * school.setStudents(studentList);
                 * 
                 * 
                 */
            }
        } catch (Exception e) {
            // TODO: handle exception
        }



        try {
            ConnectionFactory.getSessionFactory().close();
        } catch (Exception e) {e.printStackTrace();}

    }

}
Charif DZ
  • 14,415
  • 3
  • 21
  • 40