Автоматично зберігайте дочірні об’єкти за допомогою JPA Hibernate


80

Я маю відношення "один до багатьох" між таблицею "Батьки та діти". У батьківському об'єкті я маю файл

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

У мене також є зовнішній ключ у таблиці Child. Цей зовнішній ключ - це ідентифікатор, який посилається на батьківський рядок у базі даних. Отже, у моїй конфігурації бази даних цей зовнішній ключ не може мати значення NULL. Також цей зовнішній ключ є первинним ключем у батьківській таблиці.

Отже, моє питання полягає в тому, як я можу автоматично зберігати об’єкти дітей, роблячи щось подібне:

session.save(parent);

Я спробував вищезазначене, але я отримую помилку бази даних, яка скаржиться, що поле зовнішнього ключа в дочірній таблиці не може мати значення NULL. Чи є спосіб сказати JPA автоматично встановлювати цей зовнішній ключ у дочірній об’єкт, щоб він міг автоматично зберігати дочірні об’єкти?

Заздалегідь спасибі.


Покажіть нам відображення, код та оригінальну помилку.
Adeel Ansari

Відповіді:


90

Я спробував вищезазначене, але я отримую помилку бази даних, яка скаржиться на те, що поле зовнішнього ключа в таблиці 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);
Список літератури

1
Так, моя проблема полягала в тому, що я насправді хотів односпрямоване відображення, але замість цього мав щось на зразок двонаправленого відображення. Проблема полягала в тому, що у мене в таблиці Child був відображений зовнішній ключ. Тож я видалив його зі столу Child, і він спрацював. Я використовував @OneToMany та @JoinColumn у батьківській таблиці. Дякую.
Marquinio,

@pascal Чудове пояснення! Дякую Паскалю. Я розумію, що під час налаштування з батьківської сторони потрібно зробити вищезазначене. Але якщо ми встановлюємо з дочірньої сторони, чи достатньо наступного child.setParent (батьків)?
HopeKing

34

Я вважаю, що вам потрібно встановити параметр каскаду у своєму відображенні за допомогою xml / annotation. Зверніться до довідкового прикладу Hibernate тут .

Якщо ви використовуєте анотацію, вам потрібно зробити щось подібне,

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

Правда, поведінка каскаду за замовчуванням - це просто НІЩО.
masterziv

10
Я думаю, що це повинно бути @OneToMany(cascade = CascadeType.ALL), вставка не існує!
tommueller

newnoise: У наш час я не зв’язуюся з Hibernate. Але того часу це був один із доступних варіантів.
Adeel Ansari

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, а після цього оновить fk з 3 номерів SMS. Це не добре для виступу. Використовуйте LinkManagement у двосторонньому режимі
Tarunjeet Singh Salh

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 Intercepter, цей спосіб допомагає вам не писати код вище для всіх моделей.

Гібернаційний перехоплювач надає apis для самостійної роботи перед виконанням будь-якої операції з БД. Так само 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() );

Ви можете поділитися кодом збереження його в конфігурації
Юсуф,

0

Використовуйте org.hibernate.annotationsдля виконання Cascade, якщо hibernateі JPAвикористовуються разом, це якось скаржиться на збереження дочірніх об'єктів.


0

Якщо коротко встановити тип каскаду для всіх, зробить свою роботу; Для прикладу у вашій моделі. Додайте такий код. @OneToMany (mappedBy = "квитанція", cascade = CascadeType.ALL) приватний список saleSet;

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.