0

I have simple example of my problem. There ara two students and three subjects. At the beginning student1 has all subjects, student2 has no subjects. Relation is one student and many subjects.

I'm trying to swap subject1 from student1 to student2 with jpa hibernate. After transaction commit change appears in database: foreign key student_id of subject table is changed, but in java student1 object is still have subject1 even if I load student1 from database.

@Entity
@Table(name = "STUDENTS")
public class Students {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ST_ID")
    private int id;


    @OneToMany(mappedBy = "student", cascade = CascadeType.ALL)
    List<Subject> subjects = new ArrayList<>();
    //getters and setters....
}



@Entity
@Table(name = "SUBJECT")
public class Subject {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "SB_ID")
    private int id;

    @Column(name = "MARK")
    private int mark;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "ST_ID")
    private Students student;
    //getters and setter....
}

Main code:

    public static void main(String[] args) throws Exception {
    
        EntityManager em = Persistence.createEntityManagerFactory("Test").createEntityManager();
//        em.getTransaction().begin(); //does not work
        Students student1 = getStudentById(1, em); //load student1 from database by id
        Students student2 = getStudentById(2, em); //load student2 from database by id
        
        Subject subject1 = getSubjectById(1, em); //load subject1 from database by id
        Subject subject2 = getSubjectById(2, em); //load subject2 from database by id
        Subject subject3 = getSubjectById(3, em); //load subject3 from database by id
        System.out.println("student1 subjects");
        for(Subject sb : student1.getSubjects()){
            System.out.println(sb.getId()); //prints subjects: 1,2,3
        }
        System.out.println("\nstudent2 subjects");
        for(Subject sb : student2.getSubjects()){
            System.out.println(sb.getId()); //prints no subjects
        }    
        //swap subject1 between student1 and student2
        subject1.setStudent(student2);
        student2.getSubjects().add(subject1);
        
        //save changes
        em.getTransaction().begin();
        em.persist(student2);
        em.persist(student1);
        em.persist(subject2);
        em.persist(subject1);
        //em.merge does't work
        //em.flush(); //doesn't work
        em.getTransaction().commit();
        
        student1 = getStudentById(1, em); //load student1 from database by id
        System.out.println("\nstudent1 subjects");
        for(Subject sb : student1.getSubjects()){
            System.out.println(sb.getId()); //and still student1 has subject1
        }
    
        student2 = getStudentById(2, em); //load student2 from databse by id
        System.out.println("\nstudent2 subjects");
        for(Subject sb : student2.getSubjects()){
            System.out.println(sb.getId()); //now student2 also has subject1
        }
    
        
    }
    
    public static Students getStudentById(int id, EntityManager em){
        Query query = em.createQuery("from entity.Students where ST_ID="+id);
        return (Students)query.getSingleResult();
    }
    public static Subject getSubjectById(int id, EntityManager em){
        Query query = em.createQuery("from entity.Subject where SB_ID="+id);
        return (Subject) query.getSingleResult();
    }

After commit subject table looks like this. It shows that subject with id 1 now has student with id 2.

enter image description here

Entity object student1 is only updated if I make em.refresh after commit, but I don't understand why I need to use refresh if I load student1 from database? And also I'm not sure, is refresh entity is good practice?

1 Answers1

0

You have to understand that Hibernate returns objects from the first level cache or persistence context if possible to retain object identity. So even though you use a query to load the student from the database, it will just return the object you previously persisted since it's associated with the persistence context.

When you want to reload state from the database you have to use refresh or detach and then do the query.

Christian Beikov
  • 15,141
  • 2
  • 32
  • 58
  • Yes, I already understood this, but great thank you for the answer because I wasn't comletely sure. Now I'm thinking how to apply this knowledge to spring Data for refreshing entities objects. If you know some solutions, I will be greateful for help – Alexander Tukanov Nov 25 '20 at 09:01
  • I guess you are using the open session in view anti-pattern as it is the default in Spring Data? In that case, I'd suggest you clear the persistence context with `entityManager.clear` before wanting to reload previously committed state. – Christian Beikov Nov 25 '20 at 09:04
  • 1
    I'm not sure about open session. I just use CrudRepos from Spring data. Do you mean something like that? https://stackoverflow.com/questions/45491551/refresh-and-fetch-an-entity-after-save-jpa-spring-data-hibernate – Alexander Tukanov Nov 25 '20 at 09:06