2014-12-23 3 views
0

Это всегда было проблемой обработки каскадов в JPA или Hibernate, но я действительно не получил его сейчас.JPA CascadeType.All не удаляет родительские и дочерние строки

Я хочу удалить марку родительской строки и дочерние элементы, ссылающиеся на нее. Я использовал CascadeType.ALL, но не повезло.

Вот мои модели (Transact < - TransactProduct -> Продукция -> Brand -> Клиент), я показываю только отношения для ясности:

@Entity 
@Table(name = "customer") 
public class Customer { 
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "customer", cascade = CascadeType.ALL) 
    private Collection<Brand> brands; 
} 

@Entity 
@Table(name = "brand") 
public class Brand implements Serializable { 
    @ManyToOne 
    @JoinColumn(name = "customer_id") 
    private Customer customer; 

    @OneToMany(mappedBy = "brand", cascade = CascadeType.ALL) 
    private Collection<Product> products; 
} 

@Entity 
@Table(name = "product") 
public class Product { 

    @ManyToOne 
    @JoinColumn(name = "brand_id") 
    private Brand brand; 

    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL) 
    private Collection<TransactProduct> transactProducts; 
} 

@Entity 
@Table(name = "transact") 
public class Transact { 

    @OneToMany(mappedBy = "transact", fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
    private Collection<TransactProduct> transactProducts; 

    @ManyToOne 
    @JoinColumn(name = "user_id") 
    private User user; 
} 

@Entity 
@Table(name = "transact_product") 
public class TransactProduct { 

    @ManyToOne 
    @JoinColumn(name = "product_id") 
    private Product product; 

    @ManyToOne 
    @JoinColumn(name = "transact_id"); 
    private Transact transact; 
} 

Марка Repository:

@Repository 
public interface BrandRepository extends JpaRepository<Brand, Long> {} 

В моем контроллере я хочу удалить такой бренд:

Brand brand = brandRepository.findOne(id); 
brandRepository.delete(brand); 

После findOne it wr ИТЭС те утешать:

select * from brand brand0_ left outer join customer customer1_ on brand0_.customer_id=customer1_.id where brand0_.id=? 
select * from brand brands0_ where brands0_.customer_id=? 

И после того, как удалить это:

select * from brand brand0_ left outer join product products1_ on brand0_.id=products1_.brand_id where brand0_.id=? 
select * from customer customer0_ left outer join brand brands1_ on customer0_.id=brands1_.customer_id where customer0_.id=? 

Он даже не запустить запрос на удаление. Что делать, чтобы удалять бренды и каскадировать эти процессы удаления на продукты и транзакт продуктов, ссылающихся на него? Я использую CascadeType.All, но он не работает.

Спасибо.

EDIT: Я добавил модель моего покупателя к вопросу. А также хочу отметить, что, когда я удалить объект клиента с помощью:

customerRepository.delete(id); 

это делает,

delete from brand where id=? 
delete from customer where id=? 

Я добавил orphanRemoval к ​​Брэнд сущности, как Энди Dufresne упоминается, но это не сработало. Может быть, это потому, что у бренда также есть родительский объект? Потому что, когда я использую тот же метод для удаления клиентов, он просто работает. И объект клиента не имеет родителя.

+0

Я рад, что вы решили свою проблему. Он должен работать из коробки с помощью 'orphanRemoval = true'. Если вы хотите ответить, у вас есть '@ Transactional' на вашем контроллере и работает правильно? Поскольку удаление сироты будет обработано в entityManager.flush(). Я просто немного странно не работает, как с удалением сироты. –

+0

Я добавил и протестировал, но ничего не изменилось. Как я понял, проблема заключается не только в том, что сиротская сеть удаляется. Родитель (бренд) также не был удален. Как упоминалось в моем ответе, я думаю, что это связано с тем, что у бренда также есть родитель (клиент). Возможно, jpa ожидает, что мы сначала удалим его родителя. – cuneytyvz

+0

Как я пишу комментарий, у меня появилась идея. Я попытался удалить FetchType.EAGER из объекта Customer, после чего он сработал! Но мне нужно было добавить @Transactional, как вы сказали. Фактически, удаление EAGER несколько напоминает удаление клиента из бренда (родителя от ребенка). Причина, по которой он не удаляется, состоит в том, что у него есть родитель. И это требует Transactional. Но это не имеет смысла для меня, потому что мы должны иметь возможность удалять детей независимо от того, имеет ли он родителя или нет. – cuneytyvz

ответ

3

Указание orphanRemoval=true в JPA 2.0 (Hibernate CascadeType.DELETE_ORPHAN) сообщает JPA удалить дочерние записи при удалении родителя.

Можете ли вы обновить сопоставления @OneToMany, чтобы использовать этот атрибут и попробовать? Напр. для бренда это будет выглядеть

@Entity 
@Table(name = "brand") 
public class Brand implements Serializable { 
    @ManyToOne 
    @JoinColumn(name = "customer_id") 
    private Customer customer; 

    @OneToMany(mappedBy = "brand", cascade = CascadeType.ALL, orphanRemoval=true) 
    private Collection<Product> products; 
} 
+0

Я пробовал, но не повезло: /. Я редактировал свой вопрос с новой информацией. – cuneytyvz

1

После отладки и ввода исходного кода jpa и hibernate я понял это.

Я хочу удалить бренд и его дочерние элементы, но у него также есть родитель (Клиент). При вызове функции JPARepository.delete после некоторых делегаций речь идет о AbstractEntityManagerImpl класса и этот код работает:

@Override 
public void remove(Object entity) { 
    checkOpen(); 
    try { 
     internalGetSession().delete(entity); 
    } 
    catch (MappingException e) { 
     throw convert(new IllegalArgumentException(e.getMessage(), e)); 
    } 
    catch (RuntimeException e) { 
     //including HibernateException 
     throw convert(e); 
    } 
} 

Здесь internalGetSession() функция на самом деле реализуется в классе EntityManagerImpl как:

@Override 
protected Session internalGetSession() { 
    if (session == null) { 
     SessionBuilderImplementor sessionBuilder = internalGetEntityManagerFactory().getSessionFactory().withOptions(); 
     sessionBuilder.owner(this); 
     if (sessionInterceptorClass != null) { 
      try { 
       Interceptor interceptor = (Interceptor) sessionInterceptorClass.newInstance(); 
       sessionBuilder.interceptor(interceptor); 
      } 
      catch (InstantiationException e) { 
       throw new PersistenceException("Unable to instantiate session interceptor: " + sessionInterceptorClass, e); 
      } 
      catch (IllegalAccessException e) { 
       throw new PersistenceException("Unable to instantiate session interceptor: " + sessionInterceptorClass, e); 
      } 
      catch (ClassCastException e) { 
       throw new PersistenceException("Session interceptor does not implement Interceptor: " + sessionInterceptorClass, e); 
      } 
     } 
     sessionBuilder.autoJoinTransactions(getTransactionType() != PersistenceUnitTransactionType.JTA); 
     session = sessionBuilder.openSession(); 
    } 
    return session; 
} 

Здесь мы можем наблюдать объект сеанса.Без удаления брендов родительского клиента, объект сеанса содержит PersistenceContext как

entityKeys=[ 
    EntityKey[com....model.Customer#101], 
    EntityKey[com....model.Brand#102], 
    EntityKey[com....model.Product#104]] 
] 

И с этим контекстом он просто не снимает Марка лица. Чтобы убрать бренд, мы должны сначала установить его родительский null и сохранить db, а затем удалить бренд. После установки родителя (клиента) нулевой точки PersistentContext становится:

entityKeys=[ 
    EntityKey[com.....model.Brand#102], 
    EntityKey[com.....model.Product#104] 
] 

Итак, после этого он удаляет бренд.

А также orphanRemovel = true, чтобы иметь возможность удалять дочерние объекты бренда, как указано в другом ответе.

Так после того, как все, что я изменил в своем коде, как показано ниже:

Я добавил orphanRemoval = истинный к моему Марка Entity

@Entity 
@Table(name = "brand") 
public class Brand implements Serializable { 
    @ManyToOne 
    @JoinColumn(name = "customer_id") 
    private Customer customer; 

    @OneToMany(mappedBy = "brand", cascade = CascadeType.ALL, orphanRemoval=true) 
    private Collection<Product> products; 
} 

И я изменил мою удалить логику, как показано ниже:

Brand brand = brandRepository.findOne(id); 
brand.setCustomer(null); // set customer null 

brandRepository.save(brand); // persist 

brandRepository.delete(brand); // then delete 

А также, я не знаю, почему в таком случае удаление объекта не работает. Это должно быть что-то с тем, как hibernate и jpa работают внутри.

0

Как сказал Энди Дуфресне, чтобы удалить Brand.products, когда вы удаляете Brand, вам нужны CascadeType.REMOVE и orphanRemoval = true. Кроме того, если вы хотите удалить Brand.customer, когда вы удаляете Brand, вам также необходимо выполнить каскадное удаление события в объекте Customer. Не забывайте, что вам нужна транзакция, так как вы используете Spring, имеете аннотацию @Transactional в своем методе или классе Controller.

@Entity 
@Table(name = "brand") 
public class Brand implements Serializable { 
    @ManyToOne(cascade = CascadeType.REMOVE) 
    @JoinColumn(name = "customer_id") 
    private Customer customer; 

    @OneToMany(mappedBy = "brand", cascade = CascadeType.ALL, orphanRemoval=true) 
    private Collection<Product> products; 
} 
Смежные вопросы