使用JPA Hibernate自动保存子对象


78

我在父子表之间有一对多关系。在父对象中,我有一个

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

我在子表中也有一个外键。此外键是引用数据库中父行的ID。因此,在我的数据库配置中,此外键不能为NULL。同样,此外键也是父表中的主键。

所以我的问题是如何通过执行以下操作来自动保存子对象:

session.save(parent);

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

提前致谢。


向我们展示映射,代码和原始错误。
Adeel Ansari

Answers:


88

我尝试了上面的方法,但是我收到一个数据库错误,抱怨Child表中的外键字段不能为NULL。有没有办法告诉JPA自动将此外键设置为Child对象,以便它可以自动保存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.addToChildren(c1);

session.save(parent);
参考文献

1
是的,我的问题是我实际上想要一个单向映射,但是却有一个双向映射之类的东西。问题是我在Child表中映射了外键。所以我从儿童表中删除了它,并且可以正常工作。我在父表中使用了@OneToMany和@JoinColumn。谢谢。
Marquinio

@pascal很棒的解释!谢谢Pascal。我了解从家长一方进行设置时,您需要执行上述操作。但是,如果我们从子方开始设置,则下面的child.setParent(parent)是否足够?
HopeKing

32

我相信您需要通过xml / annotation在映射中设置层叠选项。请在此处参考Hibernate参考示例

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

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

没错,默认的级联行为只是什么。
masterziv 2012年

10
我认为应该是@OneToMany(cascade = CascadeType.ALL),插入不存在!
tommueller

newnoise:这些天我没有和Hibernate保持联系。但是那时候,它是可用的选项之一。
Adeel Ansari 2013年

2
CascadeType.INSERT被替换CascadeType.PERSIST
Adeel Ansari

我尝试使用CascadeType.PERSIST。没用 替换为已CascadeType.ALL解决的问题。
Jelle den Burger

2

以下程序描述了双向关系如何在休眠状态下工作。

当父对象将保存其子对象列表时,将自动保存。

在家长方面:

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

         @Id
         @GeneratedValue(strategy = GenerationType.IDENTITY)     
         @OneToMany(mappedBy="clients", cascade=CascadeType.ALL)
          List<SmsNumbers> smsNumbers;
    }

并将以下注释放在子级:

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

     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     int id;
     String number;
     String status;
     Date reg_date;
     @ManyToOne
     @JoinColumn(name = "client_id")
     private Clients clients;

    // and getter setter.

 }

主班:

 public static void main(String arr[])
 {
    Session session = HibernateUtil.openSession();
      //getting transaction object from session object
    session.beginTransaction();

    Clients cl=new Clients("Murali", "1010101010");
    SmsNumbers sms1=new SmsNumbers("99999", "Active", cl);
    SmsNumbers sms2=new SmsNumbers("88888", "InActive", cl);
    SmsNumbers sms3=new SmsNumbers("77777", "Active", cl);
    List<SmsNumbers> lstSmsNumbers=new ArrayList<SmsNumbers>();
    lstSmsNumbers.add(sms1);
    lstSmsNumbers.add(sms2);
    lstSmsNumbers.add(sms3);
    cl.setSmsNumbers(lstSmsNumbers);
    session.saveOrUpdate(cl);
    session.getTransaction().commit(); 
    session.close();    

 }

2
请详细说明您的答案,以便询问者知道您的答案的工作方式和原因。
UmarZaii

它将先插入1个客户端,然后再插入3个SMS号码,然后将更新3个SMS号码的fk。这对表演不利。在双向
区域

1

在您的setChilds中,您可能想尝试遍历列表并执行类似的操作

child.parent = this;

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


1

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

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

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拦截器提供api在执行任何DB操作之前做自己的工作。就像对象的onSave一样,我们可以使用反射在子对象中分配父对象。

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

u能请分享配置保存它的代码
优素福

0

如果将和一起使用org.hibernate.annotations,则这样做会以某种方式抱怨保存子对象。CascadehibernateJPA


0

简而言之,都是级联型;例如,在您的模型中。像这样添加代码。@OneToMany(mappedBy =“ receipt”,级联= CascadeType.ALL)私有列表saleSet;

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.