Рубрики
Без рубрики

Как сопоставить тип inet PostgreSQL с JPA и гибернацией

Узнайте, как сопоставить тип столбца PostgreSQL inet при использовании JPA и Hibernate. Проект OSS типа hibernate очень удобен для этой цели.

Автор оригинала: Vlad Mihalcea.

В этой статье мы рассмотрим, как сопоставить тип PostgreSQL inet с JPA и Hibernate. Традиционно PostgreSQL предлагает больше типов столбцов , чем другие системы реляционных баз данных.

Ранее я показал вам, как сопоставлять JSON и МАССИВ как типы гибернации, и сопоставление типа PostgreSQL inet будет таким же простым.

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

PostgreSQL inet тип позволяет хранить сетевые адреса как с IP-адресом (IPv4 или IPv6), так и с подсетью.

Хотя вы можете хранить сетевой адрес как VARCHAR или в виде серии байтов или в виде числового типа, inet более компактен и позволяет использовать различные сетевые функции .

Хотя тип столбца net используется для хранения сетевого адреса на стороне базы данных, в модели домена вместо этого мы будем использовать тип класса Inet :

public class Inet 
        implements Serializable {

    private final String address;

    public Inet(String address) {
        this.address = address;
    }

    public String getAddress() {
        return address;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) 
            return true;
            
        if (o == null || getClass() != o.getClass()) 
            return false;

        Inet inet = (Inet) o;

        return address != null ? 
                address.equals(inet.address) : 
                inet.address == null;
    }

    @Override
    public int hashCode() {
        return address != null ? 
                address.hashCode() : 
                0;
    }

    public InetAddress toInetAddress() {
        try {
            String host = address.replaceAll(
                "\\/.*$", ""
            );
            
            return Inet4Address.getByName(host);
        } catch (UnknownHostException e) {
            throw new IllegalStateException(e);
        }
    }
}

Вам не нужно создавать класс Net в своем приложении, пока вы используете типы гибернации project .

При сопоставлении пользовательского типа Hibernate у вас есть два варианта:

  • вы можете реализовать интерфейс типа пользователя
  • вы можете расширить тип AbstractSingleColumnStandardBasicType

Используя прежнюю стратегию, тип PostgreSQL Inet выглядит следующим образом:

public class PostgreSQLInetType 
        extends ImmutableType {

    public PostgreSQLInetType() {
        super(Inet.class);
    }

    @Override
    public int[] sqlTypes() {
        return new int[]{
            Types.OTHER
        };
    }

    @Override
    public Inet get(
            ResultSet rs, 
            String[] names, 
            SessionImplementor session, 
            Object owner
        ) throws SQLException {
        String ip = rs.getString(names[0]);
        return (ip != null) ? new Inet(ip) : null;
    }

    @Override
    public void set(
            PreparedStatement st, 
            Inet value, 
            int index, 
            SessionImplementor session
        ) throws SQLException {
        if (value == null) {
            st.setNull(index, Types.OTHER);
        } else {
            Object holder = ReflectionUtils.newInstance(
                "org.postgresql.util.PGobject"
            );
            ReflectionUtils.invokeSetter(
                holder, 
                "type", 
                "inet"
            );
            ReflectionUtils.invokeSetter(
                holder, 
                "value", 
                value.getAddress()
            );
            st.setObject(index, holder);
        }
    }
}

Лучший способ понять, почему стоит расширить Неизменяемый тип , предлагаемый проектом hibernate-types, – это взглянуть на следующую диаграмму классов:

Обратите внимание, что подавляющее большинство методов пользовательского типа обрабатываются Неизменяемым типом абстрактным базовым классом, в то время как PostgreSQLInetType просто должен реализовать только 3 метода.

Как уже упоминалось, вам не нужно создавать вышеупомянутые классы. Вы можете получить их с помощью типов гибернации зависимости Maven:


    com.vladmihalcea
    hibernate-types-55
    ${hibernate-types.version}

Если вы используете более старые версии Hibernate, ознакомьтесь с репозиторием hibernate-types GitHub для получения дополнительной информации о соответствующей зависимости для вашей текущей версии Hibernate.

Предположим, что нашему приложению необходимо отслеживать IP-адреса клиентов, подключающихся к нашим производственным системам. Объект Событие будет инкапсулировать IP-адрес, как в следующем примере:

@Entity(name = "Event")
@Table(name = "event")
@TypeDef(
    name = "ipv4", 
    typeClass = PostgreSQLInetType.class, 
    defaultForType = Inet.class
)
public class Event {

    @Id
    @GeneratedValue
    private Long id;

    @Column(
        name = "ip", 
        columnDefinition = "inet"
    )
    private Inet ip;

    public Long getId() {
        return id;
    }

    public Inet getIp() {
        return ip;
    }

    public void setIp(String address) {
        this.ip = new Inet(address);
    }
}

Обратите внимание на использование аннотации @TypeDef , которая указывает Hibernate использовать тип PostgreSQL Inet Тип гибернации для обработки свойств Inet сущности.

Теперь, при сохранении следующих двух Событий сущностей:

entityManager.persist(new Event());

Event event = new Event();
event.setIp("192.168.0.123/24");

entityManager.persist(event);

Hibernate создает следующие инструкции SQL INSERT:

INSERT INTO event (ip, id) VALUES (NULL(OTHER), 1)

INSERT INTO event (ip, id) VALUES ('192.168.0.123/24', 2)

Обратите внимание, что первая инструкция INSERT устанавливает для столбца ip значение NULL, как и для связанного с ним свойства сущности, в то время как вторая инструкция INSERT соответственно устанавливает столбец ip . Даже если параметр зарегистрирован как Строка , на сайте базы данных тип столбца не , а значение хранится в проанализированном двоичном формате.

При извлечении второго События сущности мы видим, что атрибут ip правильно извлечен из базового столбца inet базы данных:

Event event = entityManager.find(Event.class, 2L);

assertEquals(
    "192.168.0.123/24", 
    event.getIp().getAddress()
);

assertEquals(
    "192.168.0.123", 
    event.getIp().toInetAddress().getHostAddress()
);

Что хорошо в типе столбца inet , так это то, что мы можем использовать операторы, зависящие от сетевых адресов, такие как && , которые проверяют, принадлежит ли адрес слева адресу подсети справа:

Event event = (Event) entityManager
.createNativeQuery(
    "SELECT e.* " +
    "FROM event e " +
    "WHERE " +
    "   e.ip && CAST(:network AS inet) = true", Event.class)
.setParameter("network", "192.168.0.1/24")
.getSingleResult();

assertEquals(
    "192.168.0.123/24", 
    event.getIp().getAddress()
);

Круто, правда?

Сопоставление нестандартных типов столбцов базы данных с помощью Hibernate довольно просто. Однако с помощью проекта hibernate-types вам даже не нужно писать все эти типы. Просто добавьте зависимость Maven в свой проект pom.xml файл конфигурации и добавьте аннотацию @Type к рассматриваемому атрибуту сущности.