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