JPA / Hibernate - встраивание атрибута - PullRequest
8 голосов
/ 13 июля 2009

У меня проблема с отображением встроенного атрибута класса. Я создал несколько классов, которые похожи на то, что я пытаюсь сделать, чтобы проиллюстрировать. По сути, у меня есть иерархия классов @Embeddable, которая использует Inheritance. Класс верхнего уровня «Номер детали» имеет только один атрибут, а расширяющиеся классы не добавляют атрибуты в класс «Номер детали», они только добавляют некоторую проверку / логику.

Вот что я имею в виду:

PART

@Entity
@Table(name="PART")
public class Part {
    private Integer id;
    private String name;
    private PartNumber partNumber;

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name="PART_NAME")
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Embedded
    public PartNumber getPartNumber() {
        return partNumber;
    }
    public void setPartNumber(PartNumber partNumber) {
        this.partNumber = partNumber;
    }

}

PARTNUMBER

@Embeddable
public abstract class PartNumber {

    protected String partNumber;
    private String generalPartNumber;
    private String specificPartNumber;

    private PartNumber() {

    }

    public PartNumber(String partNumber) {
        this.partNumber = partNumber;

    }

    @Column(name = "PART_NUMBER")
    public String getPartNumber() {
        return partNumber;
    }

    public void setPartNumber(String partNumber) {
        this.partNumber = partNumber;
    }

    /**
     * @param partNumber
     * @return
     */
    public boolean validate(String partNumber) {
        // do some validation
        return true;
    }

    /**
     * Returns the first half of the Part Number
     * 
     * @return generalPartNumber
     */
    @Transient
    public String getGeneralPartNumber() {
        return generalPartNumber;

    }

    /**
     * Returns the last half of the Part Number 
     * which is specific to each Car Brand
     * 
     * @return specificPartNumber
     */
    @Transient
    public String getSpecificPartNumber() {
        return specificPartNumber;

    }

}

FORD PARTNUMBER

public class FordPartNumber extends PartNumber {

    /**
     * Ford Part Number is formatted as 1234-#1234
     * 
     * @param partNumber
     */
    public FordPartNumber(String partNumber) {
        super(partNumber);
        validate(partNumber);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.test.PartNumber#validate(java.lang.String)
     */
    @Override
    public boolean validate(String partNumber) {
        // do some validation
        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.test.PartNumber#getGeneralPartNumber()
     */
    @Override
    public String getGeneralPartNumber() {
        return partNumber;

    }

    /*
     * (non-Javadoc)
     * 
     * @see com.test.PartNumber#getSpecificPartNumber()
     */
    @Override
    public String getSpecificPartNumber() {
        return partNumber;

    }

}

CHEVY PARTNUMBER

public class ChevyPartNumber extends PartNumber {

    /**
     * Chevy Part Number is formatted as 1234-$1234
     * 
     * @param partNumber
     */
    public ChevyPartNumber(String partNumber) {
        super(partNumber);
        validate(partNumber);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.test.PartNumber#validate(java.lang.String)
     */
    @Override
    public boolean validate(String partNumber) {
        // do some validation
        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.test.PartNumber#getGeneralPartNumber()
     */
    @Override
    public String getGeneralPartNumber() {
        return partNumber;

    }

    /*
     * (non-Javadoc)
     * 
     * @see com.test.PartNumber#getSpecificPartNumber()
     */
    @Override
    public String getSpecificPartNumber() {
        return partNumber;

    }
}

Конечно, это не работает, потому что Hibernate игнорирует иерархию наследования и ему не нравится тот факт, что PartNumber является абстрактным. Есть ли способ сделать это с помощью JPA или аннотаций Hibernate? Я пытался использовать аннотацию @Inheritance JPA.

Я не могу реорганизовать часть «PartNumber» иерархии, потому что первоначальный Разработчик хочет иметь возможность расширять PartNumber на N многих классов XXXXPartNumber.

Кто-нибудь знает, будет ли что-то подобное частью JPA 2.0 или новой версией Hibernate?

Ответы [ 5 ]

15 голосов
/ 17 июля 2009

Компонентное (например, @Embeddable) наследование не поддерживается и, скорее всего, никогда не будет. Для этого есть веская причина - идентификатор сущности играет решающую роль во всех стратегиях наследования, поддерживаемых Hibernate, а компоненты не имеют (сопоставленных) идентификаторов.

У вас есть три варианта:

A) Отобразить PartNumber (и все его потомки) как объекты. PartNumber может оставаться абстрактным:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="part_type", discriminatorType=DiscriminatorType.STRING)
public abstract class PartNumber {
...
}

@Entity
@DiscriminatorValue("Ford")
public class FordPartNumber extends PartNumber {
...
}

B) Исходя из вашего примера, кажется, что все потомки PartNumber отличаются только поведением (они не вводят никаких новых свойств для хранения). Если это действительно так, вы можете отобразить свойства PartNumber плюс ваше собственное значение дискриминатора (чтобы вы знали, какой класс создать) как частное свойство @Embedded и иметь методы доступа get / setPartNumber () в классе Part marshall / unmarshall соответствующих подклассов. Вы даже можете написать свой собственный пользовательский тип Hibernate, чтобы сделать это для вас (это довольно просто).

C) Если потомки PartNumber ДОЛЖНЫ различаться по свойствам, которые должны быть сохранены, и отображение их как объектов недопустимо по любой причине, вы можете использовать marshall / unmarshall их для цепочки (как XML или что-либо еще, что соответствует требованиям) и хранить тот. Я использую XStream для этой конкретной цели, и я написал простой тип Hibernate, чтобы пойти с ним. Ваше отображение частей будет выглядеть примерно так:

@Type(type="xmlBean")
public PartNumber getPartNumber() {
    return partNumber;
}
public void setPartNumber(PartNumber partNumber) {
    this.partNumber = partNumber;
}

и потомки PartNumber вообще не должны отображаться. Недостатком, конечно, является то, что работа с XML в базе данных - это немного больше хлопот, так что, возможно, это не идеальный подход к тому, о чем вам, возможно, понадобится сообщить. OTOH, я использую это для хранения настроек плагина, и это спасло меня много проблем с отображениями / обслуживанием БД.

2 голосов
/ 14 июля 2009

У меня похожая проблема в моей собственной схеме, поэтому то, к чему я сейчас прибегаю, выглядит так:

Родительский класс:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@SequenceGenerator(name="SEQ", sequenceName="part_id_seq", initialValue=1, allocationSize=1)
public abstract class BasePart {
    @Id
    @Column(name="part_id")
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ")
    protected Long partId;

    @YourBusinessKeyAnnotation
    @Column(name="part_number")
    protected String partNumber
    ...
}

Детские классы:

@Entity
public class FordPart extends BasePart {
    ...
}

@Entity
public class ChevyPart extends BasePart {
    ...
}

Теперь я мог затем манипулировать ключом biz так, как мне было нужно, и это сработало хорошо, потому что каждый из различных типов деталей получил свою собственную таблицу (что полезно для нас).

Вы также можете использовать @Embedded с @AttributeOverrides Я думаю, чтобы указать имена столбцов по-разному, как вам нужно ... Есть пример из документации аннотаций .

@Entity
public class Person implements Serializable {

    // Persistent component using defaults
    Address homeAddress;

    @Embedded
    @AttributeOverrides( {
            @AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
            @AttributeOverride(name="name", column = @Column(name="bornCountryName") )
    } )
    Country bornIn;
    ...
}

...

@Entity
public class Person implements Serializable {

    // Persistent component using defaults
    Address homeAddress;

    @Embedded
    @AttributeOverrides( {
            @AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
            @AttributeOverride(name="name", column = @Column(name="bornCountryName") )
    } )
    Country bornIn;
    ...
}

...

@Embedded
@AttributeOverrides( {
        @AttributeOverride(name="city", column = @Column(name="fld_city") ),
        @AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ),
        @AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") )
        //nationality columns in homeAddress are overridden
} )
Address homeAddress;

Возможно, вы сможете злоупотребить этим настолько, что вам будет все равно ...

1 голос
/ 14 июля 2009

Похоже, что вы пытаетесь сделать, это использовать "Embedable Inheritance", который, я не верю, пока поддерживает hibernate.

@Embeddable
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="partType", discriminatorType=DiscriminatorType.STRING)
public PartNumber getPartNumber() 
{
  return partNumber;
}

Так как это выглядит как разногласия между Ford и Chevy, это похоже на формат. Я бы, вероятно, скрыл отображение и добавил бы другую функцию, чтобы вернуть определенный тип, который вы хотите.

@Embeddable
public PartNumber getPartNumber() 
{
  return partNumber;
}

@Transient
public SpecificPartNumber getSpecificPartNumber()
{
  return PartNumberFactory.create( getPartNumber() );
}

Смотрите ссылки в комментариях ...

1 голос
/ 13 июля 2009

Хм, не уверен. Вы пытались поставить @MappedSuperclasson PartNumber и @Embeddable на подклассах. Смотрите также - https://forum.hibernate.org/viewtopic.php?t=966129

В качестве побочного узла - рассматривали ли вы возможность использования Hibernate Validator вместо собственного метода проверки?

0 голосов
/ 07 декабря 2017

Как уже упоминалось в других ответах, похоже, что Hibernate не поддерживает наследование встраиваемых объектов (по крайней мере, мне не удалось заставить его работать). Я нашел несколько обходных путей (спасибо @ ChssPly76 за вдохновение):

1) Заменить @Embaddable на @Entity и сохранить иерархию PartNumber в выделенной таблице. Конечно, для составного класса требуется отношение @OneToOne (Part).

2) Ввести промежуточный класс сущностей, который полностью отражает структуру таблицы базы данных. А затем сопоставьте сущность с классами модели вашего домена (и обратно) вручную. Такой подход обеспечивает исключительную гибкость за счет ручной работы по написанию и тестированию кода отображения.

3) Хранить PartNumber потомков в сериализованной форме и полагаться на сериализатор, который должен иметь возможность создавать экземпляр правого класса во время десериализации.

Для своей задачи я выбрал третий подход. Я использую Hibernate Types для сериализации объектов в JSON и сохраняю результат в столбце jsonb PostgreSQL. Hibernate Types использует Jackson под капотом, поэтому все его аннотации (включая те, которые управляют полиморфным поведением) находятся в вашем распоряжении. В вашем случае код будет выглядеть так:

@Entity
public class Part {
    // ...

    @Type(type = "com.vladmihalcea.hibernate.type.json.JsonBinaryType")
    private PartNumber partNumber;

}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(value = [
    JsonSubTypes.Type(value = FordPartNumber::class, name = "FordPartNumber"),
    JsonSubTypes.Type(value = ChevyPartNumber::class, name = "ChevyPartNumber")
])
public abstract class PartNumber { /* ... */ }

public class FordPartNumber extends PartNumber { /* ... */ }

public class ChevyPartNumber extends PartNumber { /* ... */ }

Полученный JSON в базе данных будет выглядеть как

{
    "type": "FordPartNumber",
    // ... other serialized properties of the FordPartNumber class
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...