Eclipse Texo ModelEMFConverter 和 Hibernate 代理

Eclipse Texo ModelEMFConverter and Hibernate proxies

我正在尝试将 Eclipse Texo 集成到我现有的 Hibernate 项目中。我在 ECore 中为我的域模型建模,并使用 Texo 和常规 EMF 代码生成从那里生成 EMF 和 POJO 代码。

获取存储在数据库中的实体(POJO)没有问题,现在我想使用 Texo 的 ModelEMFConverter 将 Hibernate 映射的数据模型转换为相应的 EMF 模型。但是,由于 Hibernate 返回的实体被透明代理,此尝试失败。 Texo 的 ModelResolver 无法查找这些代理实体的模型描述符,因为它将实体(即代理 class)的 class 与映射的 class 进行比较es 并在我的情况下失败,但出现异常:

Exception in thread "main" java.lang.IllegalStateException: The class class foobar.Entity_$$_jvst4f2_5 is not managed by this ModelResolver at org.eclipse.emf.texo.utils.Check.isNotNull(Check.java:66) at org.eclipse.emf.texo.model.ModelResolver.getModelDescriptor(ModelResolver.java:366) at org.eclipse.emf.texo.model.ModelResolver.getModelObject(ModelResolver.java:298) at org.eclipse.emf.texo.resolver.DefaultObjectResolver.toUri(DefaultObjectResolver.java:188) at org.eclipse.emf.texo.resolver.DefaultObjectResolver.resolveToEObject(DefaultObjectResolver.java:98) at org.eclipse.emf.texo.converter.ModelEMFConverter.createTarget(ModelEMFConverter.java:146) at org.eclipse.emf.texo.converter.ModelEMFConverter.convertSingleEReference(ModelEMFConverter.java:265) at org.eclipse.emf.texo.converter.ModelEMFConverter.convertContent(ModelEMFConverter.java:189) at org.eclipse.emf.texo.converter.ModelEMFConverter.convert(ModelEMFConverter.java:107) [...]

来自ModelResolver的相关代码位:

  public ModelObject<?> getModelObject(final Object target) {
    /* ... snip ... */

    final ModelDescriptor modelDescriptor = getModelDescriptor(target.getClass(), true);
    return modelDescriptor.createAdapter(target);
  }

我尝试使用以下代码手动解包代理实体,然后再将它们传递给模型转换器:

    final List<Object> objects = entities
            .stream()
            .map(o ->
                o instanceof HibernateProxy ?
                    (Entity) ((HibernateProxy) o).getHibernateLazyInitializer().getImplementation() : o)
            .collect(Collectors.toList());

    final ModelEMFConverter converter = new ModelEMFConverter();
    final Collection<EObject> eObjects = converter.convert(objects);

理论上这种方法似乎可行(我通过单步执行转换代码来检查),但是对于我的数据模型中关联引用的实体,原始 entities 中不包含的实体,它失败了列表。我想避免为了摆脱代理而必须手动遍历整个对象图。

有没有办法从 Hibernate 中检索未代理的实体?或者有人可以建议我如何从不同的角度处理这个模型转换吗?

提前感谢您的帮助!

您可以编写一个通用替换器,它将遍历整个图形并替换给定实体实例的所有代理,如下所示:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;

public class HibernateProxyReplacer {

    @SuppressWarnings("unchecked")
    public <T extends Entity> T replaceProxies(T entity) {
        try {
            return (T) replaceProxies(entity, new ArrayList<Object>());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    private Object replaceProxies(Object entity, List<Object> processedObjects) throws Exception {
        entity = getImplementation(entity);
        if (isProcessed(entity, processedObjects)) {
            return entity;
        }
        processedObjects.add(entity);

        for (Field field : getDeclaredFields(entity)) {
            if (isStaticOrFinal(field)) {
                continue;
            }
            field.setAccessible(true);
            Object value = field.get(entity);
            if (value == null) {
                continue;
            }
            Hibernate.initialize(value);
            if (value instanceof Collection) {
                replaceProxiesInCollection((Collection<Object>) value, processedObjects);
            } else if (value instanceof Entity) {
                field.set(entity, replaceProxies(value, processedObjects));
            }
        }

        return entity;
    }

    private Object getImplementation(Object object) {
        return object instanceof HibernateProxy ? ((HibernateProxy) object).getHibernateLazyInitializer().getImplementation() : object;
    }

    private boolean isStaticOrFinal(Field field) {
        return ((Modifier.STATIC | Modifier.FINAL) & field.getModifiers()) != 0;
    }

    private List<Field> getDeclaredFields(Object object) {
        List<Field> result = new ArrayList<Field>(Arrays.asList(object.getClass().getDeclaredFields()));
        for (Class<?> superclass = object.getClass().getSuperclass(); superclass != null; superclass = superclass.getSuperclass()) {
            result.addAll(Arrays.asList(superclass.getDeclaredFields()));
        }
        return result;
    }

    private void replaceProxiesInCollection(Collection<Object> collection, List<Object> processedObjects) throws Exception {
        Collection<Object> deproxiedCollection = new ArrayList<Object>();
        for (Object object : collection) {
            deproxiedCollection.add(replaceProxies(object, processedObjects));
        }
        collection.clear();
        collection.addAll(deproxiedCollection);
    }

    private boolean isProcessed(Object object, List<Object> processedObjects) {
        for (Object processedObject : processedObjects) {
            // Intentional comparison by reference to avoid relying on equals/hashCode
            if (processedObject == object) {
                return true;
            }
        }
        return false;
    }
}

不要忘记回滚完成此操作的事务(Hibernate 可能认为该对象是脏的,因为我们手动更改了字段值)。或者将其设置为只读(通过将刷新模式设置为手动)。或者显式清除会话而不刷新它,以便 deproxied 图形变得分离。

如果这对您的要求来说是一个障碍,那么您可以通过从托管实体实例读取值并将取消代理的值设置为另一个实例来更改此方法。这样你就可以构建一个新的独立的非托管实体实例,它的整个图在没有任何代理的情况下被初始化。

或者,您可以只存储有关必要更改的信息,稍后在分离实例的事务中应用它们,例如:

commands.add(new ReplaceFieldCommand(field, entity, deproxiedObject));
commands.add(new ReplaceCollectionCommand(collection, entity, deproxiedCollection));