Гибернация пользовательского типа с отношением ManyToOne

#java #hibernate

#java #гибернация

Вопрос:

Что я хотел бы иметь: адрес класса, который представляет адрес по всему миру, также у меня есть объект Country, который я хотел бы использовать здесь в качестве отношения, чтобы снизить риск наличия адреса в стране, которую я бы указал (стоит ли смягчать?)

Мой общий вопрос, возможно ли это вообще?

Кстати, я использую Hibernate 5.4.6 на Java 11

Давайте создадим простой класс:

 public class Address {

    private Country country;
    private String city;
    private String street;
    private String number;
    private String zipCode;

    public Address(
            final Country country,
            final String city,
            final String street,
            final String number,
            final String zipCode
    ) {
        this.country = country;
        this.street = street;
        this.number = number;
        this.city = city;
        this.zipCode = zipCode;
    }

    protected Address() {
    }

    public String getFormatted() {
        return String.format("%s %s, %s %s, %s", street, number, city, zipCode, country);
    }
}
  

,
Страна также прямолинейна:

 @Entity
@Table(name = "countries")
public class Country {

    @Column
    @Id
    @NotNull
    private String nameIso;

    @Column
    @NotNull
    private String name;

    @Column
    @NotNull
    private boolean isAllowed = false;

    protected Country() {
    }

    public Country(final String nameIso, final String name) {
        this.nameIso = nameIso;
        this.name = name;
    }

    public String getNameIso() {
         return nameIso;
    }
}
  

И пример объекта, который использует этот класс:

 @Entity
@Table(name = "delivery_address")
public class DeliveryAddress {

    @Id
    @Column
    @NotNull
    @Type(type = UUID)
    private java.util.UUID id;

    @ManyToOne(fetch = LAZY)
    private User user;

    @Column
    @NotNull
    private Address address;

    protected DeliveryAddress() {
    }

    public DeliveryAddress(User user, Address address) {
        this.id = java.util.UUID.randomUUID();
        this.user = user;
        this.address = address;
    }

    public Country getCountry() {
        return this.address.getCountry();
    }
}
  

Мне удалось сообщить hibernate, что будет много типов для одного типа через устаревший тип (ой),
вот что у меня получилось:

 public class AddressType implements CompositeUserType {

    public static final String NAME = "address";

    private static final int COUNTRY_POSITION = 1;
    private static final int CITY_POSITION = 2;
    private static final int STREET_POSITION = 3;
    private static final int NUMBER_POSITION = 4;
    private static final int ZIP_POSITION = 5;

    private static final int COUNTRY_INDEX = COUNTRY_POSITION - 1;
    private static final int CITY_INDEX = CITY_POSITION - 1;
    private static final int STREET_INDEX = STREET_POSITION - 1;
    private static final int NUMBER_INDEX = NUMBER_POSITION - 1;
    private static final int ZIP_INDEX = ZIP_POSITION - 1;

    @Override
    public String[] getPropertyNames() {
        return new String[]{"country", "city", "street", "number", "zipCode"};
    }

    @Override
    public Type[] getPropertyTypes() {
        return new Type[]{
                new TypeConfiguration().getTypeResolver().getTypeFactory().manyToOne(Country.class.getCanonicalName()),
                StandardBasicTypes.STRING,
                StandardBasicTypes.STRING,
                StandardBasicTypes.STRING,
                StandardBasicTypes.STRING
        };
    }

    @Override
    public Object getPropertyValue(final Object component, final int property) throws HibernateException {
        if (component == null) {
            return null;
        }

        String propertyValue;

        switch (property) {
            case COUNTRY_POSITION:
                propertyValue = ((Address) component).getCountry().getNameIso();
                break;
            case CITY_POSITION:
                propertyValue = ((Address) component).getCity();
                break;
            case STREET_POSITION:
                propertyValue = ((Address) component).getStreet();
                break;
            case NUMBER_POSITION:
                propertyValue = ((Address) component).getNumber();
                break;
            case ZIP_POSITION:
                propertyValue = ((Address) component).getZipCode();
                break;
            default:
                propertyValue = null;
        }

        return propertyValue;
    }


    @Override
    public Class returnedClass() {
        return Address.class;
    }

    @Override
    public Object nullSafeGet(final ResultSet rs,
                              final String[] names,
                              final SharedSessionContractImplementor session,
                              final Object owner) throws HibernateException, SQLException {
        String country = rs.getString(names[COUNTRY_INDEX]);
        String city = rs.getString(names[CITY_INDEX]);
        String street = rs.getString(names[STREET_INDEX]);
        String number = rs.getString(names[NUMBER_INDEX]);
        String zipCode = rs.getString(names[ZIP_INDEX]);

        if (country != null amp;amp; city != null amp;amp; street != null amp;amp; number != null amp;amp; zipCode != null) {

/* 
********
* How to obtain instance of Country at this spot ?
*******
*/
            return new Address(country, city, street, number, zipCode);
        }

        return null;
    }

}
  

Так возможно ли иметь отношение в моем CompositeType или я должен прекратить попытки и использовать только любой код iso страны в моем классе адресов?

Спасибо!

Ответ №1:

В JPA / Hibernate такие типы лучше всего представлять в виде встраиваемых файлов, если вы контролируете код. API составного / пользовательского типа лучше подходит на случай, если вы не можете изменить исходный код типа.

Я бы рекомендовал вам создать встраиваемый файл, который хранит код ISO следующим образом:

 @Embeddable
public class Country {
    private String iso;
}
  

Просто имейте в виду, что при повторном использовании этого типа вам, вероятно, придется изменять имена столбцов @AttributeOverride на сайте использования.