【问题标题】:Save child objects automatically using JPA Hibernate使用 JPA Hibernate 自动保存子对象
【发布时间】:2011-04-25 00:49:32
【问题描述】:

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

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

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

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

session.save(parent);

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

提前致谢。

【问题讨论】:

标签: java database hibernate


【解决方案1】:

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

嗯,这里有两件事。

首先,您需要级联保存操作(但我的理解是您正在这样做,否则在“子”表中插入期间您不会违反 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);
参考

【讨论】:

  • 是的,我的问题是我实际上想要一个单向映射,而是有一个类似双向映射的东西。问题是我在 Child 表中映射了外键。所以我将它从 Child 表中删除并且它起作用了。我在父表中使用了@OneToMany 和@JoinColumn。谢谢。
  • @pascal 很好的解释!谢谢帕斯卡。我了解在从父母方面进行设置时,您需要执行上述操作。但是如果我们从子端设置,下面这个是否足够 child.setParent(parent) ?
  • 是的,我称之为“互惠”关系......在我的代码中。处理它的另一种方法是......就在 .save (在 jpa 层)之前......通过最后的沟渠检查“确保互惠”。
  • 私人收藏 ensureReciprocal(Collection inputItems) { Collection returnItems = null; if (null != inputItems) { returnItems = inputItems;
  • returnItems.parallelStream().forEach((par) -> { if (null != par.getMyChilds()) { for (MyChildJpaEntity chd : par.getMyChilds()) { if (null = = detail.getParentMyParentJpaEntity()) { /* jpa 不喜欢空的“互惠”关系。缺少互惠有时会显示为子实体上没有代理键持久 */
【解决方案2】:

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

如果你使用注解,你需要做这样的事情,

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

【讨论】:

  • 没错,默认的级联行为只是 NOTHING。
  • 我觉得应该是@OneToMany(cascade = CascadeType.ALL),插入不存在!
  • newnoise:这些天我没有接触 Hibernate。但那个时候,它是可用的选项之一。
  • CascadeType.INSERTCascadeType.PERSIST 替换。
  • 我尝试使用CascadeType.PERSIST。它没有用。用CascadeType.ALL 替换它可以解决问题。
【解决方案3】:

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

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

在父方:

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

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

并在child侧放如下注解:

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

 }

【讨论】:

  • 请详细说明您的答案,以便提问者知道您的答案如何以及为什么有效。
  • 它将插入 1 个客户端,然后插入 3 个 SMS 号码,然后更新 3 个 SMS 号码的 fk。这对性能不利。在 Birectional 中使用 LinkManagement
【解决方案4】:

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

child.parent = this;

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

【讨论】:

    【解决方案5】:

    在双向关系的子对象中分配父对象的方法有哪些?

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

    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 拦截器在执行任何 DB 操作之前提供 api 来完成您自己的工作。同样的对象的 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;
        }
    
    }
    

    您可以将拦截器注册为配置

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

    【讨论】:

    • 能否分享一下保存在配置中的代码
    【解决方案6】:

    在 JPA @*To* 关系中,父实体和子实体必须在(父)保存之前交叉分配。

    【讨论】:

      【解决方案7】:

      使用org.hibernate.annotationsCascade,如果hibernateJPA 一起使用,它会以某种方式抱怨保存子对象。

      【讨论】:

        【解决方案8】:

        简而言之,将级联类型设置为 all ,就可以了; 对于您的模型中的示例。 像这样添加代码。 @OneToMany(mappedBy = "receipt", cascade=CascadeType.ALL) 私有列表 saleSet;

        【讨论】:

          【解决方案9】:

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

          注意:如果您遇到 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;
                   
                  
              }
          

          【讨论】:

            猜你喜欢
            • 2015-07-08
            • 1970-01-01
            • 1970-01-01
            • 2015-03-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-10-03
            相关资源
            最近更新 更多