如何通过Spring JPA实现PostgreSQL自定义类型与Java对象的映射?
用Spring JPA实现PostgreSQL自定义类型与Java对象双向映射的最简方案
前提准备
假设我们要映射的PostgreSQL自定义类型是一个地址结构:
CREATE TYPE address AS ( street VARCHAR(100), city VARCHAR(50), zip VARCHAR(10) );
方案一:Hibernate 6+ 最简实现(推荐)
Hibernate 6原生支持PostgreSQL的自定义struct类型,无需编写复杂转换器,仅需两步完成映射:
- 创建对应Java POJO
定义与自定义类型字段一一匹配的Java类,需包含无参构造器(JPA要求),并重写equals()和hashCode():
import java.util.Objects; public class Address { private String street; private String city; private String zip; public Address() {} public Address(String street, String city, String zip) { this.street = street; this.city = city; this.zip = zip; } // Getter、Setter方法 public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getZip() { return zip; } public void setZip(String zip) { this.zip = zip; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Address address = (Address) o; return Objects.equals(street, address.street) && Objects.equals(city, address.city) && Objects.equals(zip, address.zip); } @Override public int hashCode() { return Objects.hash(street, city, zip); } }
- 在实体类中映射自定义类型
使用Hibernate的@Struct注解指定PostgreSQL自定义类型名称,直接将POJO作为实体字段:
import jakarta.persistence.Entity; import jakarta.persistence.Id; import org.hibernate.annotations.Struct; @Entity public class User { @Id private Long id; private String name; // 映射PostgreSQL的address自定义类型 @Struct(name = "address") private Address address; // Getter、Setter、构造器省略 }
此方案自动完成双向转换:从数据库读取时将address类型转为Address对象,写入时将Address对象转为PostgreSQL的address类型。
方案二:Hibernate 5及以下版本实现
若使用低版本Hibernate,需自定义UserType处理转换逻辑:
- 实现UserType接口
编写自定义类型转换器,处理Java对象与SQL类型的双向转换:
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.usertype.UserType; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.Objects; public class AddressUserType implements UserType { @Override public int[] sqlTypes() { return new int[]{Types.OTHER}; } @Override public Class<Address> returnedClass() { return Address.class; } @Override public boolean equals(Object x, Object y) { return Objects.equals(x, y); } @Override public int hashCode(Object x) { return Objects.hashCode(x); } @Override public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException { String structStr = rs.getString(names[0]); if (structStr == null) return null; // 解析PostgreSQL返回的struct字符串(格式:(street,city,zip)) structStr = structStr.substring(1, structStr.length() - 1); String[] parts = structStr.split(",", 3); return new Address(parts[0], parts[1], parts[2]); } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws SQLException { if (value == null) { st.setNull(index, Types.OTHER); return; } Address address = (Address) value; // 构造PostgreSQL struct格式字符串 String structStr = String.format("('%s','%s','%s')", address.getStreet(), address.getCity(), address.getZip()); st.setObject(index, structStr, Types.OTHER); } // 以下方法按默认逻辑实现 @Override public Object deepCopy(Object value) { if (value == null) return null; Address addr = (Address) value; return new Address(addr.getStreet(), addr.getCity(), addr.getZip()); } @Override public boolean isMutable() { return true; } @Override public Serializable disassemble(Object value) { return (Serializable) deepCopy(value); } @Override public Object assemble(Serializable cached, Object owner) { return deepCopy(cached); } @Override public Object replace(Object original, Object target, Object owner) { return deepCopy(original); } }
- 在实体类中指定自定义类型
使用@Type注解绑定自定义的UserType:
import jakarta.persistence.Entity; import jakarta.persistence.Id; import org.hibernate.annotations.Type; @Entity public class User { @Id private Long id; private String name; @Type(type = "com.example.AddressUserType") private Address address; // Getter、Setter、构造器省略 }
注意事项
- 确保PostgreSQL驱动版本适配(推荐使用
org.postgresql:postgresql42.2+) - Hibernate 6+的
@Struct注解需依赖org.hibernate:hibernate-core6.0+ - 自定义UserType时,若字段包含特殊字符(如逗号),建议改用JDBC的Struct API处理,避免字符串解析出错
内容的提问来源于stack exchange,提问作者SpongeBob
相关产品推荐
相关产品推荐

