使用JPA Hibernate自动保存子对象

时间:2010-10-13 19:04:40

标签: java database hibernate

我在Parent和Child表之间有一对多的关系。在父对象中我有一个

List<Child> setChildren(List<Child> childs)

我在Child表中也有一个外键。此外键是引用数据库中的父行的ID。所以在我的数据库配置中,这个外键不能为NULL。 此外键也是Parent表中的主键。

所以我的问题是如何通过这样的方式自动保存子对象:

session.save(parent);

我尝试了上面但是我收到一个数据库错误,抱怨Child表中的外键字段不能为NULL。有没有办法告诉JPA自动将这个外键设置到Child对象中,以便它可以自动保存子对象?

提前致谢。

8 个答案:

答案 0 :(得分:61)

  

我尝试了上面但是我收到一个数据库错误,抱怨Child表中的外键字段不能为NULL。有没有办法告诉JPA自动将这个外键设置到Child对象中,以便它可以自动保存子对象?

嗯,这里有两件事。

首先,您需要级联保存操作(但我的理解是您正在执行此操作,或者在“子”表中插入期间不会出现FK约束违规)

其次,你可能有一个双向关联,我认为你没有正确设置“链接的两面”。你应该做这样的事情:

Parent parent = new Parent();
...
Child c1 = new Child();
...
c1.setParent(parent);

List<Child> children = new ArrayList<Child>();
children.add(c1);
parent.setChildren(children);

session.save(parent);

常见的模式是使用链接管理方法:

@Entity
public class Parent {
    @Id private Long id;

    @OneToMany(mappedBy="parent")
    private List<Child> children = new ArrayList<Child>();

    ...

    protected void setChildren(List<Child> children) {
        this.children = children;
    }

    public void addToChildren(Child child) {
        child.setParent(this);
        this.children.add(child);
    }
}

代码变为:

Parent parent = new Parent();
...
Child c1 = new Child();
...

parent.addToChild(c1);

session.save(parent);

参考

答案 1 :(得分:23)

我相信您需要通过xml / annotation在映射中设置级联选项。请参阅Hibernate reference example here

如果您使用的是注释,则需要执行以下操作

@OneToMany(cascade = CascadeType.PERSIST) // Other options are CascadeType.ALL, CascadeType.UPDATE etc..

答案 2 :(得分:2)

以下是在双向关系的子对象中分配父对象的方法吗?

假设您有一个关系说一对多,那么对于每个父对象,都存在一组子对象。 在双向关系中,每个子对象都将引用其父对象。

eg : Each Department will have list of Employees and each Employee is part of some department.This is called Bi directional relations.

要实现这一点,单向是在持久化父对象的同时在子对象中指定父对象

Parent parent = new Parent();
...
Child c1 = new Child();
...
c1.setParent(parent);

List<Child> children = new ArrayList<Child>();
children.add(c1);
parent.setChilds(children);

session.save(parent);

其他方式是,您可以使用hibernate Intercepter,这种方式可以帮助您不要为所有模型编写上述代码。

Hibernate拦截器在执行任何数据库操作之前提供apis来完成你自己的工作。在onSave of object上,我们可以使用反射在子对象中指定父对象。

public class CustomEntityInterceptor extends EmptyInterceptor {

    @Override
    public boolean onSave(
            final Object entity, final Serializable id, final Object[] state, final String[] propertyNames,
            final Type[] types) {
        if (types != null) {
            for (int i = 0; i < types.length; i++) {
                if (types[i].isCollectionType()) {
                    String propertyName = propertyNames[i];
                    propertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
                    try {
                        Method method = entity.getClass().getMethod("get" + propertyName);
                        List<Object> objectList = (List<Object>) method.invoke(entity);

                        if (objectList != null) {
                            for (Object object : objectList) {
                                String entityName = entity.getClass().getSimpleName();
                                Method eachMethod = object.getClass().getMethod("set" + entityName, entity.getClass());
                                eachMethod.invoke(object, entity);
                            }
                        }

                    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return true;
    }

}

您可以将Intercepter注册为配置为

new Configuration().setInterceptor( new CustomEntityInterceptor() );

答案 3 :(得分:1)

使用org.hibernate.annotations执行Cascade,如果同时使用hibernateJPA,则会以某种方式抱怨保存子对象。

答案 4 :(得分:0)

在你的setChilds中,您可能想尝试通过列表循环并执行类似

的操作
child.parent = this;

您还应该将父级上的级联设置为适当的值。

答案 5 :(得分:0)

以下程序描述了hibernate中双向关系的工作原理。          当父将保存其子对象列表时将自动保存。

    class dataLoadTest extends FunSuite with Matchers with BeforeAndAfterEach {

  private val master = "local[*]"
  private val appName = "data_load_testing"

  var spark: SparkSession = _

  override def beforeEach() {
    spark = new SparkSession.Builder().appName(appName).master(master).getOrCreate()
  }


  test("Creating dataframe should produce data from of correct size") {
    val sQLContext = spark.sqlContext
    import sQLContext.implicits._

    val df = spark.sparkContext
    .textFile("/Applications/spark-2.2.0-bin-hadoop2.7/examples/src/main/resources/people.txt")
    .map(_.split(","))
    .map(attributes => Person(attributes(0), attributes(1).trim.toInt))
    .toDF()

    assert(df.count() == 3)
    assert(df.take(1)(0)(0).equals("Michael"))
  }

  override def afterEach() {
    spark.stop()
  }

}
case class Person(name: String, age: Int)

答案 6 :(得分:0)

总之,设置级联类型为全,将做好工作; 有关模型中的示例。 像这样添加代码。  @OneToMany(mappedBy =&#34;收据&#34;,cascade = CascadeType.ALL)  私人清单saleSet;

答案 7 :(得分:0)

如果您没有双向关系并且只想保存/更新子表中的单列,那么您可以使用子实体创建JPA存储库并调用save/saveAll或update方法。

注意:如果您遇到 FK 违规,则意味着您的邮递员请求具有主键和外键 ID 与子表中生成的 ID 不匹配,检查请求中的 ID 并您将要更新的子表(它们应该匹配/如果它们不匹配则意味着您违反了 FK) 在事务之前保存父和子时生成的任何 id,这些 id 应该在您的第二次调用中匹配您尝试更新子表中的单列。

家长:

@Entity
@Table(name="Customer")
public class Customer implements Serializable  {

     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)  
     private UUID customerId ;
     
     @OneToMany(cascade = CascadeType.ALL) 
     @JoinColumn(name ="child_columnName", referencedColumnName= 
                "parent_columnName")
     List<Accounts> accountList;
}

孩子:

 @Entity
    @Table(name="Account")
    public class Account implements Serializable  {

         @Id
         @GeneratedValue(strategy = GenerationType.IDENTITY)  
         private UUID accountid;
         
        
    }