如何用JPA防止保存子对象?

时间:2017-03-24 14:29:20

标签: java jpa

我在学校和学生实体之间建立了OneToMany关系。我想要做的是当我保存学校对象时不保存或更新学生对象。(当然不要删除它们)

当我尝试保存下面的学校对象时,它也会更新我的学生对象,但我不希望它们更新但只能加入。有什么办法吗?

我删除了Cascade,但它仍无效。

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

我的实体;

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

编辑:

  • 当我尝试保存/更新/删除School对象时,请勿保存/更新/删除Student对象。认为我在Student表上没有任何插入/更新/删除权限。

5 个答案:

答案 0 :(得分:8)

  

当我尝试保存下面的学校对象时,它也会更新我的学生对象,但我不希望它们更新但只能加入。有什么办法吗?

我认为您的意思是您希望管理协会本身 - 即哪些学生与学校相关 - 而不是学生的详细信息。出于概念和实际原因,我担心,这没有多大意义。

从概念上讲,一对多关系的“拥有”方面总是“很多”。当您通过连接表管理关联时,这有点任意(但仍然是真的),但是当通过实体属性直接管理关联时,这很重要。更新关联需要管理拥有方的实体,在这种情况下是Student s。

实际上,从School方面管理协会只会带来一些困难。例如,如果将 Student添加到School,该怎么办?在关联可以持久化之前,该学生需要持久化,但是您希望避免这种情况发生。将StudentSchool移动到另一个School时,会出现类似但较小的问题。

现在,您确实可以避免将持久性操作从Student级联到School,但我希望这样做的结果是无法管理它们之间的关联以及管理cascade秒。如果这是您想要做的,那么您将省略@OneToMany注释中的任何Student属性,或者显式指定级联类型的空列表。

但是,请注意,当您提交对持久性上下文的更改时,将保存对所有持久性实体的更改。如果要修改School实体而不将这些更改保存到数据库中,那么最好的替代方法可能是分离这些实体,或者制作它们的分离副本。

<强>更新

正如评论中所阐明的,当应用程序无权更新Student的基表时,基本问题是如何修改StudentSchool实体之间的关系,但是对表示关系的连接表有足够的权限。你不能自动对School的持续更改自动执行此操作,因为Student不是 - 也不可能 - 是关系的拥有方。

澄清一下:JPA在您将School实体移动到其他Student时尝试保存它们,因为它的目的是School与其特定{{1}之间的关联}}是Student状态的一部分。如果Student实体附加到持久性上下文并且是脏的(例如,因为它被分配给不同的School),则在提交对PC的更改时将更新它。这与级联或School的状态没有任何关系,除了您通过将Student s的状态移动到不同的School s students来间接修改EntityManager的状态。 1}}列表。

由于您使用的是联接表,因此您可以修改对象模型,以便将每个学生/学校协会表示为一个实体。如果这些协会具有自己的属性,例如注册日期,那将是有意义的,否则我不会推荐它。

另一种方法是编写本机查询(通过JPA);你可以在网上找到很多关于它的信息,例如https://blogs.oracle.com/JPQL01/entry/native_query_in_java_persistence。然而,这确实引入了一些问题。特别是,它使Student的缓存无效,如果不处理,可能会产生严重的问题。如果您正常执行更新,它会产生实体更改而不会触发将触发的实体生命周期方法,这对您来说也很重要。

对于缓存,解决方案是分离受影响的SchoolEntityManager.detach()(旧的和新的)实体。您可以选择性地使用EntityManager.clear(),或 en masse {{1}}。之后,您需要重新查询或合并您想要继续使用的实体,这可能会产生一些混乱的问题,具体取决于它们的使用范围以及其他代码对它们的假设。

至于生命周期方法,如果你需要确保它们开火,那么你可以自己调用它们。

This article提供了有关您可能需要执行的操作的更多详细信息。

答案 1 :(得分:4)

也许你可以试试@JoinColumn:

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

答案 2 :(得分:2)

您向我们展示的结构不是您列出的一对多关系。这是多对多的。

@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;

真的那么简单。

答案 3 :(得分:1)

删除这些注释

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

因此,hibernate实体管理器可能不会将此对象与自动更新相关联

因此,您必须使用set方法向学生编写手动查询,或者将学生列表作为代码逻辑中的参数来设置学生

private List<Student> students;

然后它将按预期工作

答案 4 :(得分:1)

我建议删除school.setStudents(studentList)因为你告诉hibernate编辑这些学生的many2one字段。尝试session.clear()如果它不起作用,除了删除声明,并确保你从学生方面这样做。 我的意思是为学生定义一个不相反的学校。

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();}

    }

}