2016-11-27 5 views
0

У меня проблема с сохраняющимися объектами в Spring JPA/hibernate. Я также использую Lombok Plugin, чтобы уменьшить коды шаблонов. В принципе у меня есть объект под названием продукт, который имеет oneToMany отношения с подпроизведением:Как сохранить объект JPA, который имеет два уровня отношений oneToMany?

@Entity 
    @Table(name="product") 
    @EqualsAndHashCode(exclude = {"productId"}, callSuper=false) 
    @ToString(includeFieldNames=true, callSuper=true) 
    @Data 
    public class Product implements Serializable { 

     @Id 
     @GeneratedValue(strategy=GenerationType.IDENTITY) 
     @Column(name="product_id") 
     private Integer productId; 


     @OneToMany(fetch = FetchType.EAGER, mappedBy="product" 
      , cascade ={CascadeType.ALL}, orphanRemoval=true) 
     private Set<SubProduct> subProducts; 
    } 

и это мое подпроизведение, который имеет другой oneToMany отношение с joinTable называется ProductVariantMap:

@Entity 
@Table(name="sub_product") 
@EqualsAndHashCode(exclude = {"subId","product"}) 
@ToString(includeFieldNames=true, exclude = {"product"}) 
@Data 
public class SubProduct implements Serializable { 

    @Id 
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    @Column(name="sub_id") 
    private Integer subId; 

    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name = "product_id") 
    private Product product; 

    @OneToMany(fetch = FetchType.EAGER, 
     mappedBy="productVariantMapId.subProduct" 
     , cascade = {CascadeType.ALL}) 
    private List<ProductVariantMap> productVariantMaps; 

} 

Это мой ProductVariantMap с встроен идентификатор, который содержит PRODUCTID, SubID и variantId

@Entity 
@Table(name="product_variant_map") 
@EqualsAndHashCode 
@ToString(includeFieldNames=true) 
@Data 
public class ProductVariantMap implements Serializable { 

    @EmbeddedId 
    private ProductVariantMapId productVariantMapId; 

    @Column(name="variant_value", nullable=false) 
    private String variantValue; 
} 

productVariantMapId:

@Embeddable 
@EqualsAndHashCode(exclude = {"subProduct","product"}) 
@ToString(includeFieldNames=true,exclude = {"subProduct","product"}) 
@Data 
public class ProductVariantMapId implements Serializable { 

    @ManyToOne(fetch = FetchType.EAGER) 
    @JoinColumn(name = "sub_id") 
    private SubProduct subProduct; 

    @ManyToOne(fetch = FetchType.EAGER) 
    @JoinColumn(name = "product_id") 
    private Product product; 

    @ManyToOne(fetch = FetchType.EAGER) 
    @JoinColumn(name = "variant_id") 
    private Variant variant; 


} 

и последнее, но не в последнюю очередь это мой вариант:

@Entity 
@Table(name="variant") 
@EqualsAndHashCode(exclude = {"variantId"}) 
@ToString(includeFieldNames=true) 
@Data 
public class Variant implements Serializable { 

    @Id 
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    @Column(name="variant_id") 
    private Integer variantId; 

    @Column(name="name", nullable=false) 
    private String name; 
} 

Моя цель состоит в том, чтобы быть в состоянии сохраняются все отношения на одном дыхании, когда я productRepository.saveAndFlush (продукт). Обратите внимание, что все первичные ключи генерируются генератором последовательности базы данных и, следовательно, его значение null перед saveAndFlush. У меня ниже исключения, когда я пытался сделать выше:

java.lang.NullPointerException 
    at org.hibernate.type.descriptor.java.AbstractTypeDescriptor.extractHashCode(AbstractTypeDescriptor.java:65) 
    at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:201) 
    at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:206) 
    at org.hibernate.type.EntityType.getHashCode(EntityType.java:355) 
    at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:241) 
    at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:61) 
    at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:54) 
    at org.hibernate.internal.AbstractSharedSessionContract.generateEntityKey(AbstractSharedSessionContract.java:459) 
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:162) 
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125) 
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67) 
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189) 
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132) 
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:788) 
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753) 
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80) 
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:391) 
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:316) 

Я проследил выполнение и казалось, что исключение было вызвано классом Варианта, имеющими нулевую Hashcode, который не должен был быть случаем, так как я заселен вариантом с различным имя для спящего режима для различения (хотя идентификатор еще не известен) для каждого субпроизведения (я пытался сохранить 2 подпрограммы под одним продуктом). Затем каждый подпродукт будет иметь два productVariantMap.

Я ожидаю, что останется 1 строка продукта, 2 подпрограммы, 4 строки карты варианта продукта и 2 варианта строк. Можно ли сохранить все отношения за один раз в JPA? Большое спасибо

ответ

1

В хэше JPA и равном методе должны быть основаны на первичном ключе. Скорее не использовать фреймворк для генерации hash() equals() и реализовать его.

public boolean equals(Object o) { 
if (this == o) return true; 
if (o == null || !(o instanceof Person)) 
    return false; 

Person other = (Person)o; 

if (id == other.getId()) return true; 
if (id == null) return false; 

// equivalence by id 
return id.equals(other.getId()); 
} 

public int hashCode() { 
    if (id != null) { 
     return id.hashCode(); 
    } else { 
     return super.hashCode(); 
    } 
} 

Другое замечание: @Embedable класс является частью объекта в той же таблице БД. Никакой связи между встроенным и его родителем не требуется. Удалить @ManyToOne

+0

Причина, по которой мой метод хэша и равенства основан на других полях рядом с идентификатором, заключается в том, что я полагаюсь на генератор идентификаторов базы данных. Если я использую ID как свой уникальный идентификатор, Java будет обрабатывать новые объекты в наборе как единое целое, поскольку все они имеют нулевой идентификатор, см. Это http://www.onjava.com/pub/a/onjava/2006/09/13 /dont-let-hibernate-steal-your-identity.html – user3391672

+0

См. эту строку в методе equals: if (id == null) return false; Новые объекты без Id никогда не будут такими же. –

+0

Если я удалю класс manyToOne в классе Embeddable, как hibernate знает, какой sub_id или product_id имеет один конкретный продуктVariantMap ?. Помните, что все объекты Id имеют значение null до saveAndFlush – user3391672