Связь JPA / Hibernate для унаследованного класса возвращает null

#java #hibernate #jpa

#java #переход в спящий режим #jpa

Вопрос:

У меня есть иерархическая система классов для сущностей, и все они в порядке, пока я не введу отношения. Вот несколько примеров кода:

Базовый класс:

 @MappedSuperclass
public class BaseClass
{

    // Some fields
    // Ids are MySQL autoincrementing unsigned BIGINT
    protected Long id;
    // ... other fields

    @JsonCreator
    public BaseClass (
        // ...fields
    )
    {

        // assign fields
    }

    public BaseClass () { /** Default copy constructor */ }

    public BaseClass update (BaseClass value)
    {
        // repeat bellow line for all fields
        this.field1 = (value.field1 != null) ? value.field1 : this.field1;
        // finally, for chaining
        return this;
    }

    // Getters and Setters for all fields

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", updatable = false)
    @JsonProperty("id")
    public Long getId ()
    {
        return this.id;
    }

    public BaseClass setId (Long value)
    {
        this.id = value != null ? value : this.id;
        return this;
    }

    @Column(name = "field1")
    @JsonProperty("field1")
    public String getField1 ()
    {
        return this.field1;
    }

    public BaseClass setField1 (Field1Type value)
    {
        this.field1 = value;
        return this;
    }

}
 

Унаследованный класс:

 @Entity
@Table(name = "`table_name`")
@DynamicInsert
@DynamicUpdate
public class InheritedClass extends BaseClass implements Serializable
{
    /**
     * the value is for demonstration only, it's randomly generated per serializable entity
     */
    private static final long serialVersionUID = 1L;

    private List<RelatedClass1> relatedClass1Field;
    private RelatedClass2 relatedClass2Field;

    public InheritedClass () { /** Default copy constructor */ }

    public InheritedClass (BaseClass value)
    {
        super.update(value);
    }

    // inheritedClassField is the field in the many-to-one end of the relationship in RelatedClass1
    @Transient // javax.persistence, not bean
    @JsonProperty("related_class1_field")
    @OneToMany(
        targetEntity=RelatedClass1.class
        , fetch=FetchType.EAGER
        , cascade = { CascadeType.ALL }
        , mappedBy = "inheritedClassField"
    )
    public List<RelatedClass1> getRelatedClass1Field ()
    {
        return this.relatedClass1Field;
    }

    public InheritedClass setRelatedClass1Field (List<RelatedClass1> value)
    {
        this.relatedClass1Field = value;
        return this;
    }

    // inheritedClassField is the field in the many-to-one end of the relationship in RelatedClass1
    @Transient // javax.persistence, not bean
    @JsonProperty("related_class2_field")
    @ManyToOne(
        targetEntity=RelatedClass2.class
        , fetch=FetchType.EAGER
        , cascade = { CascadeType.ALL }
    )
    @JoinColumn(
        name = "related_id"
        , referencedColumnName = "id"
    )
    public RelatedClass2 getRelatedClass2Field ()
    {
        return this.relatedClass2Field;
    }

    public InheritedClass setRelatedClass2Field (RelatedClass2 value)
    {
        this.relatedClass2Field = value;
        return this;
    }

}
 

Когда я пытаюсь получить доступ к экземпляру InheritedClass , relatedClass1Field и relatedClass2Field являются null , однако они заполняются в базе данных.

НО

Если я определяю связь с помощью стратегии доступа к полю, они возвращают правильное значение:

     @Access(AccessType.FIELD)
    @OneToMany(
        targetEntity=RelatedClass1.class
        , fetch=FetchType.EAGER
        , cascade = { CascadeType.ALL }
        , mappedBy = "inheritedClassField"
    )
    private List<RelatedClass1> relatedClass1Field;

    @ManyToOne(
        targetEntity=RelatedClass2.class
        , fetch=FetchType.EAGER
        , cascade = { CascadeType.ALL }
    )
    @JoinColumn(
        name = "related_id"
        , referencedColumnName = "id"
    )
    private RelatedClass2 relatedClass2Field;

    
    @JsonProperty("related_class1_field")
    public List<RelatedClass1> getRelatedClass1Field ()
    {
        return this.relatedClass1Field;
    }

    public InheritedClass setRelatedClass1Field (List<RelatedClass1> value)
    {
        this.relatedClass1Field = value;
        return this;
    }


    @JsonProperty("related_class2_field")
    public RelatedClass2 getRelatedClass2Field ()
    {
        return this.relatedClass2Field;
    }

    public InheritedClass setRelatedClass2Field (RelatedClass2 value)
    {
        this.relatedClass2Field = value;
        return this;
    }
 

Комментарии:

1. Большинство людей все равно помещают эти аннотации в поле, так есть ли проблема?

2. @K.Nicholas Поскольку я использую наследование сущностей, я использую стратегию доступа к свойствам, чтобы я мог легко переопределить реализацию способа преобразования данных при доступе к данным верхнего уровня, сохраняя при этом доступность полей при доступе к данным более низкого уровня.

3. Я думаю, что если вы попытаетесь добавить аннотации к свойству дочернего класса, которые должны быть применены к родительскому классу, у вас возникнут проблемы с JPA.

4. @Хитрый, пожалуйста, покажите место / места, где вы помещаете @Id аннотацию?

5. @Хитрый, почему вы используете @Transient для получения InheritedClass ? Вы должны использовать его только в том случае, если хотите исключить поле из постоянного состояния объекта.

Ответ №1:

Вы не должны использовать @Transient аннотации для этих методов:

 @Transient
@OneToMany(
   targetEntity=RelatedClass1.class
   , fetch=FetchType.EAGER
   , cascade = { CascadeType.ALL }
   , mappedBy = "inheritedClassField"
)
public List<RelatedClass1> getRelatedClass1Field ()
// ...

@Transient
@ManyToOne(
   targetEntity=RelatedClass2.class
   , fetch=FetchType.EAGER
   , cascade = { CascadeType.ALL }
)
@JoinColumn(
   name = "related_id"
   , referencedColumnName = "id"
)
public RelatedClass2 getRelatedClass2Field ()
// ...
 

Согласно аннотации документации @Transient указывает, что свойство или поле не являются постоянными.

И простой пример. Представьте, что у вас есть следующая сущность:

 @Entity
@Table(name = "TST_PATIENT")
public class Person {

    private Long id;
    // ...
    private Date dateOfBirth;

    @Id
    @Column(name = "P_ID")
    public Long getId() {
       return id;
    }
    
    @Column(name = "P_DOB")
    public Date getDateOfBirth() {
       return dateOfBirth;
    }
    
    @Transient
    public long getAge() {
       return ChronoUnit.YEARS.between(
          LocalDateTime.ofInstant( Instant.ofEpochMilli( dateOfBirth.getTime()), ZoneOffset.UTC),
          LocalDateTime.now()
       );
    }

    // setters omitted for brevity
}
 

Здесь мы используем @Transient аннотацию, потому что на самом деле у нас нет age столбца в TST_PATIENT таблице (он просто вычисляется на основе другого сохраняемого поля), и мы хотим дать указание hibernate исключить это свойство из постоянного состояния сущности.