如何使用Hibernate将MariaDB Point字段映射至自定义Vector2类型?
解决Hibernate映射MariaDB Point到自定义Vector2类型的问题
我来帮你搞定这个MariaDB Point字段到自定义Vector2类的映射需求,下面给你两种实用的方案,你可以根据自己的项目场景来选择:
方案一:使用JPA AttributeConverter(简单易实现)
这种方案是最直接的,不需要深入Hibernate的底层API,只需要实现一个转换器类,告诉JPA怎么把Vector2和数据库的Point类型互相转换。
首先,假设你的Vector2类结构是这样的(如果你的类不一样,调整转换器里的字段即可):
public class Vector2 { private double x; private double y; // 无参构造器(JPA必须要有,用来实例化对象) public Vector2() {} public Vector2(double x, double y) { this.x = x; this.y = y; } // Getter和Setter public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } }
接下来实现转换器,这里推荐用MariaDB JDBC驱动提供的Geometry类来处理,比字符串解析更严谨:
import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; import org.mariadb.jdbc.Geometry; @Converter(autoApply = false) // 不要自动应用到所有字段,只在需要的地方指定 public class Vector2ToPointConverter implements AttributeConverter<Vector2, Geometry> { // 把Vector2转成数据库能识别的Point类型 @Override public Geometry convertToDatabaseColumn(Vector2 vector2) { if (vector2 == null) { return null; } return Geometry.createPoint(vector2.getX(), vector2.getY()); } // 把数据库的Point转成自定义的Vector2 @Override public Vector2 convertToEntityAttribute(Geometry geometry) { if (geometry == null || geometry.getType() != Geometry.GeometryType.POINT) { return null; } double[] coords = geometry.getCoordinates(); return new Vector2(coords[0], coords[1]); } }
然后在你的Ship实体类的position字段上加上注解,指定使用这个转换器:
import jakarta.persistence.*; @Entity @Table(name = "ships") public class Ship { @ManyToOne @JoinColumn(name = "accounts_id", nullable = false) public Account account; @ManyToOne @JoinColumn(name = "maps_id", nullable = false) public Map map; @Column(name = "position", nullable = false) @Convert(converter = Vector2ToPointConverter.class) public Vector2 position; // 记得加上构造器、Getter/Setter等方法 }
注意:这个方案需要你引入MariaDB的JDBC驱动依赖,比如Maven:
<dependency> <groupId>org.mariadb.jdbc</groupId> <artifactId>mariadb-java-client</artifactId> <version>3.3.3</version> <!-- 替换成最新稳定版 --> </dependency>
方案二:自定义Hibernate BasicType(适合复杂场景)
如果你需要在HQL查询中直接使用Vector2类型,或者需要更深度的Hibernate集成(比如自定义类型的缓存、SQL类型处理),可以用Hibernate的自定义类型方案。
首先实现UserType接口(Hibernate 5+也可以用BasicType,不过UserType更常用):
import org.hibernate.HibernateException; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.usertype.UserType; import org.mariadb.jdbc.Geometry; 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 Vector2Type implements UserType { @Override public int[] sqlTypes() { // 对应MariaDB的POINT类型,JDBC类型用Types.OTHER return new int[]{Types.OTHER}; } @Override public Class<Vector2> returnedClass() { return Vector2.class; } @Override public boolean equals(Object x, Object y) throws HibernateException { if (x == y) return true; if (x == null || y == null) return false; Vector2 v1 = (Vector2) x; Vector2 v2 = (Vector2) y; return Double.compare(v1.getX(), v2.getX()) == 0 && Double.compare(v1.getY(), v2.getY()) == 0; } @Override public int hashCode(Object x) throws HibernateException { return Objects.hash(((Vector2) x).getX(), ((Vector2) x).getY()); } @Override public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException { Geometry geometry = rs.getObject(names[0], Geometry.class); if (rs.wasNull() || geometry == null || geometry.getType() != Geometry.GeometryType.POINT) { return null; } double[] coords = geometry.getCoordinates(); return new Vector2(coords[0], coords[1]); } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { if (value == null) { st.setNull(index, Types.OTHER); return; } Vector2 vector2 = (Vector2) value; Geometry point = Geometry.createPoint(vector2.getX(), vector2.getY()); st.setObject(index, point, Types.OTHER); } @Override public Object deepCopy(Object value) throws HibernateException { if (value == null) return null; Vector2 v = (Vector2) value; return new Vector2(v.getX(), v.getY()); } @Override public boolean isMutable() { return true; // Vector2的字段可以修改,所以标记为可变 } @Override public Serializable disassemble(Object value) throws HibernateException { return (Serializable) deepCopy(value); } @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { return deepCopy(cached); } @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return deepCopy(original); } }
然后在Ship实体类的position字段上标注使用这个自定义类型:
import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import org.hibernate.annotations.Type; @Entity @Table(name = "ships") public class Ship { // ... 其他字段 @Column(name = "position", nullable = false) @Type(type = "com.yourpackage.Vector2Type") // 替换成你的类的全限定名 public Vector2 position; // ... Getter/Setter等 }
如果想全局注册这个类型,避免每次都写全限定名,可以在Hibernate配置文件(比如hibernate.cfg.xml)里添加:
<hibernate-configuration> <session-factory> <!-- 其他配置项 --> <typedef name="vector2" class="com.yourpackage.Vector2Type"/> </session-factory> </hibernate-configuration>
之后实体类里就可以简化成@Type(type = "vector2")。
一些注意事项
- 确保你的Vector2类有无参构造器,JPA和Hibernate都需要用它来实例化对象。
- 测试的时候一定要验证写入和读取流程:保存一个Ship对象到数据库,然后查询出来检查position的x、y值是否正确。
- 如果使用Spring Boot,可以通过
HibernatePropertiesCustomizer来全局注册自定义类型,不需要手动修改配置文件。
内容的提问来源于stack exchange,提问作者aiko
相关产品推荐
相关产品推荐

