Eclipselink:从未调用过 Spatial 的自定义 StructConverter

Eclipselink: Custom StructConverter for Spatial never called

我 运行 遇到了一个关于复杂对象和 Eclipselink 的问题,我似乎无法解决。我需要将空间数据作为 SDO_GEOMETRY 写入/读取到我的 Oracle 数据库。

我以前使用oracle.spatial.geometry.JGeometry,但出于查询原因我需要切换到org.geolatte.geom.Geometry(这是QueryDSL使用的空间类型)。所以我写了一个 org.eclipse.persistence.platform.database.converters.StructConverter 的自定义实现来处理从 org.geolatte.geom.GeometrySDO_GEOMETRY / Struct.

的转换

我使用 org.eclipse.persistence.platform.database.oracle.converters.JGeometryConverter 作为模板。

import com.mysema.query.sql.spatial.JGeometryConverter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Struct;
import oracle.spatial.geometry.JGeometry;
import org.eclipse.persistence.platform.database.converters.StructConverter;
import org.geolatte.geom.Geometry;

public class GeometryConverter implements StructConverter {
    private static final String JGEOMETRY_DB_TYPE = "MDSYS.SDO_GEOMETRY";
    private Class JGEOMETRY_CLASS;
    private MethodHandle loadJSMethod;
    private MethodHandle storeJSMethod;

    public GeometryConverter() {
        try {
            JGEOMETRY_CLASS = Class.forName("oracle.spatial.geometry.JGeometry");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        try {
            loadJSMethod = lookup.unreflect(JGEOMETRY_CLASS.getMethod("loadJS", new Class[] {
                Struct.class
            }));
            storeJSMethod = lookup.unreflect(JGEOMETRY_CLASS.getMethod("storeJS", new Class[] {
                JGEOMETRY_CLASS,
                Connection.class
            }));
        } catch (IllegalAccessException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String getStructName() {
        return JGEOMETRY_DB_TYPE;
    }

    @Override
    public Class getJavaType() {
        return Geometry.class;
    }

    @Override
    public Object convertToObject(Struct struct) throws SQLException {
        System.out.println("-------------------- CALLED convertToObject");
        if (struct == null) {
            return null;
        }
        try {
            return JGeometryConverter.convert((JGeometry)loadJSMethod.invokeWithArguments(new Object[] {
                struct
            }));

        } catch (Throwable throwable) {
            throw new SQLException(throwable);
        }
    }

    @Override
    public Struct convertToStruct(Object geometry, Connection connection) throws SQLException {
        System.out.println("-------------------- CALLED convertToStruct");
        if (geometry == null) {
            return null;
        }
        try {
            return (Struct) storeJSMethod.invokeWithArguments(new Object[] {
                JGeometryConverter.convert((Geometry)geometry), connection
            });
        } catch (Throwable throwable) {
            throw new SQLException(throwable);
        }
    }
}

我的实体 class 注释如下:

//Real package replaced by 'myPkg' for privacy reasons
@StructConverter(name = "Geometry", converter = "myPkg.GeometryConverter")
@Table(name = "ENTRY", catalog = "")
public class Entry implements Serializable {
    ...

    @Column(name = "LOCATION")
    @Convert("Geometry")
    private Geometry location;
    ...
}

但是由于某些奇怪的原因,在调用 entityManager.persist(myEntryObject) 时我的转换器的方法 convertToStruct 没有被调用。 相反,我得到以下异常:

Caused by: java.sql.SQLException: Invalid column type
    at oracle.jdbc.driver.OraclePreparedStatement.setObjectCritical(OraclePreparedStatement.java:8488)
    at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:7995)
    at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:8735)
    at oracle.jdbc.driver.OraclePreparedStatement.setObject(OraclePreparedStatement.java:8714)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.setObject(OraclePreparedStatementWrapper.java:219)
    at org.eclipse.persistence.internal.databaseaccess.DatabasePlatform.setParameterValueInDatabaseCall(DatabasePlatform.java:2506)
    at org.eclipse.persistence.platform.database.oracle.Oracle9Platform.setParameterValueInDatabaseCall(Oracle9Platform.java:525)
    at org.eclipse.persistence.internal.databaseaccess.BindCallCustomParameter.set(BindCallCustomParameter.java:69)
    at org.eclipse.persistence.internal.databaseaccess.DatabasePlatform.setParameterValueInDatabaseCall(DatabasePlatform.java:2500)
    at org.eclipse.persistence.platform.database.oracle.Oracle9Platform.setParameterValueInDatabaseCall(Oracle9Platform.java:525)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseCall.prepareStatement(DatabaseCall.java:797)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:621)
    ... 101 more

奇怪的是,当通过 EntityManager 检索 Entry 对象时,convertToObject 确实被调用并且它完美地工作。但是每次我尝试插入/更新 Entry 它都不起作用。

我是否需要做任何额外的配置才能让 EclipseLink 识别我的转换器?还有什么可能导致此问题?

希望有人能帮助我。提前谢谢你。

编辑

我的持久化单元是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd">
  <persistence-unit name="jtaUnit" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>java:app/aphrodite4</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
        <property name="toplink.target-database" value ="oracle.toplink.essentials.extension.spatial.Oracle10SpatialPlatform"/>
        <property name="eclipselink.cache.shared.default" value="false"/>
        <property name="javax.persistence.validation.group.pre-persist" value="javax.validation.groups.Default"/>
        <property name="javax.persistence.validation.group.pre-update" value="javax.validation.groups.Default,myPkg.ConstraintGroups.Update"/>
    </properties>
  </persistence-unit>
</persistence>

我正在按以下方式加载它:

@PersistenceContext(unitName = "jtaUnit")
protected EntityManager em;

在没有找到上述问题的解决方案后,我找到了解决方法。

我编写了以下数据库函数:

create or replace FUNCTION WITHIN_DISTANCE(g sdo_geometry, lon number, lat number, distLimit number)
RETURN VARCHAR2 AS 

dist NUMBER;
result VARCHAR2(5);
tmpGeo SDO_GEOMETRY;

BEGIN
  tmpGeo := SDO_GEOMETRY(2001, 8307, SDO_POINT_TYPE(lon, lat, NULL), NULL, NULL);
  SELECT SDO_GEOM.SDO_DISTANCE(g, tmpGeo, 0.005) INTO dist FROM dual;

  IF dist <= distLimit THEN
    result := 'TRUE';
  ELSE
    result := 'FALSE';
  END IF;

  RETURN result;

END WITHIN_DISTANCE;

要在 QueryDsl 查询中使用此函数,我使用以下代码:

Predicate pred = Expressions.stringTemplate("function('WITHIN_DISTANCE', {0}, {1}, {2}, {3})" , 
                        QEntry.entry.location,
                        loc.getLon(),
                        loc.getLat(),
                        crit.getRadius()).eq("TRUE");