Как подружить Postgres Network Address Types и hibernate
Всем привет, сегодня я расскажу как подружить Postgres Network Address Types c hibernate.
Наверное все понимают что для хранкния мак адреса в PostgresDB использовать varchar это не совсем правильно. Когда есть специальный тип macaddr.
В проекте использовался hibernate 3.6.7.Final.
Сначала я попробовал просто запустить проект вот с такой реализацией:
/**
* Сущность для таблицы mac_adr
*
@author <a href="mailto:onixbed@gmail.com">amaksimov</a>
*/
@Entity
@Table(name = "mac_adr")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@SequenceGenerator(name = "sequencePooledMacEntity", sequenceName = "mac_pooled_seq")
public class MacAdrEntity {
private Long id;
private PgMacaddr mac;
@Id
@GeneratedValue(generator = "sequenceMacAdrEntity")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "mac")
public PgMacaddr getMac() {
return mac;
}
public void setMac(String mac) {
this.mac = mac;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
@Transient
public MacAddress getDTO() {
MacAddress ret = new MacAddress();
try {
ret.setMacString(mac.getMac());
} catch (ParseException e) {
e.printStackTrace();
}
return ret;
}
}
Это простая сущность где есть идентификатор и мак адрес, но когда я попытался записать в таблицу мак программа свалилась с ошибкой
column "mac" is of type macaddr but expression is of type character varying
Ок я решил указать явный каст
@ColumnTransformer( write="macaddr(?)" )
@Column(name = "mac")
public PgMacaddr getMac() {
return mac;
}
Отлично, подумал я. Такое решение позволило читать и записывать мак адрес. Но через неделю мне понадобилось выполнить вот такой запрос:
select macAdrEntity from MacAdrEntity as macAdrEntity where macAdrEntity.mac in (:macAddresses)
И тут началось .... я испробовал не один вариант но хибернет кидал одну ошибку за другой. Я уже подумал лучше использовать строку в БД.
Наконец я нашел еще один вариант на просторах интернета:
@ColumnTransformer(read = "CAST(mac AS varchar)", write="macaddr(?)" )
@Column(name = "mac")
public PgMacaddr getMac() {
return mac;
}
Но увы он тоже не помог мне, просто выкинул ошибку:
org.hibernate.QueryException: illegal attempt to dereference collection ...
Тогда я решил создать свой класс для macaddr
/**
* Postgre тип macaddr
*
@author <a href="mailto:onixbed@gmail.com">amaksimov</a>
*/
public class PgMacaddr implements Serializable {
private static final long serialVersionUID = 1L;
private String mac;
public PgMacaddr() {
this.mac = null;
}
public PgMacaddr(String mac) {
this.mac = mac;
}
public String getMac() {
return mac;
}
public void setMac(String mac) {
this.mac = mac;
}
}
Тут все просто создаем пользовательский тип отдельный из которого будет преобразован Postgres тип macaddr и на оборот.
Теперь самое сложно нужно создать класс преобразования.
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
import org.postgresql.util.PGobject;
import org.springframework.util.ObjectUtils;
/**
* Конвертация Postgre типа macaddr в java объект {@link PgMacaddr}
*
@author <a href="mailto:onixbed@gmail.com">amaksimov</a>
*/
public class PgMacaddrType implements UserType {
@Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return deepCopy(cached);
}
@Override
public Object deepCopy(Object value) throws HibernateException {
if (value == null)
return null;
else {
PgMacaddr PgMacaddrNew = new PgMacaddr();
PgMacaddr PgMacaddrOriginal = (PgMacaddr) value;
PgMacaddrNew.setMac(PgMacaddrOriginal.getMac());
return PgMacaddrNew;
}
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
Object deepCopy = deepCopy(value);
if ((deepCopy instanceof Serializable))
return (Serializable) deepCopy;
return null;
}
@Override
public boolean equals(Object arg0, Object arg1) throws HibernateException {
return ObjectUtils.nullSafeEquals(arg0, arg1);
}
@Override
public int hashCode(Object arg0) throws HibernateException {
if (arg0 != null)
return arg0.hashCode();
else
return 0;
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, Object arg2)
throws HibernateException, SQLException {
PgMacaddr mac = null;
String strMac = rs.getString(names[0]);
if (strMac != null) {
mac = new PgMacaddr(strMac);
}
return mac;
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
} else {
PGobject pgObj = new PGobject();
pgObj.setType("macaddr");
pgObj.setValue(((PgMacaddr) value).getMac());
st.setObject(index, pgObj);
}
}
@Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return deepCopy(original);
}
@Override
public Class<PgMacaddr> returnedClass() {
return PgMacaddr.class;
}
@Override
public int[] sqlTypes() {
return new int[] { Types.OTHER };
}
}
Не много раскажу подробнее о методах:
- assemble — метод преобразовывает в объект для хранения в кэше.
- deepCopy — метод реализует полное копирование объекта.
- disassemble — метод восстанавливает объект из кэша в Serializable объект.
- equals — метод сравнивает 2 объекта на вавенство.
- hashCode — метод возвращает хеш код
- isMutable — метод возвращает признак мутации объекта
- nullSafeGet — метод преобразовывает значение из БД в java объект.
- nullSafeSet — метод преобразовывает java объект в БД объект для записи.
- replace — метод вносит изменения в старый объект из нового.
- returnedClass — метод возрощает тип класс, методом nullSafeGet.
- sqlTypes —тип колонки в БД.
Отлично создаем аналогичне классы для inet, cidr и у нас полный комплект. Теперь наш проект поддерживает сетевые типы
Всем пасибо за внимание не забывайте подписываться на мой блог. =)