Автор оригинала: 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
к рассматриваемому атрибуту сущности.