获取 JPA 实体版本的通用方法

Posted

技术标签:

【中文标题】获取 JPA 实体版本的通用方法【英文标题】:Generic way to get JPA entity version 【发布时间】:2012-11-07 07:33:33 【问题描述】:

我有一个未知的 JPA 实体,需要知道它的版本。我找不到通用的方法。

我尝试了元模型,但不知道将什么传递给 getVersion() 方法:

Object entity = ...;
Metamodel metamodel = entityManager.getMetamodel();
IdentifiableType<?> metaClass = 
    (IdentifiableType<?>)metamodel.managedType(entity.getClass());
metaClass.getVersion(<what to put here?>);

metaClass.getId() 中使用了相同的模式,但有一个补充方法getIdType() -- 缺少getVersionType()

其次,有entityManagerFactory.getPersistenceUnitUtil().getIdentifier(entity)方法,但没有getVersion(entity)方法。

有没有办法从未知实体获取版本?

【问题讨论】:

【参考方案1】:

比 try/catch 的答案更简洁:

public static SingularAttribute<?, ?> getVersionAttribute(ManagedType<?> managedType) 
    if (!(managedType instanceof IdentifiableType<?>))
        return null;

    IdentifiableType<?> identifiableType = (IdentifiableType<?>)managedType;
    if (!identifiableType.hasVersionAttribute())
        return null;

    for (SingularAttribute<?, ?> attribute : identifiableType.getSingularAttributes()) 
        if (attribute.isVersion())
            return attribute;
    
    return null;

【讨论】:

【参考方案2】:

恕我直言,您应该将带有 @Version 注释的字段类型放在该类中。我认为这也适用于继承,开箱即用。

如果您事先不知道类型,我会使用标准反射。如果它们包含@Version 注释,我将遍历所有the declared fields 和check annotations。如果没有找到,check superclass,直到找到...

public static Field getVersionField(Object entity) 
    Class<?> myClass = entity.getClass();
    do 
        Field[] fields = myClass.getDeclaredFields();
        for(Field field:fields) 
            if(field.getAnnotation(Version.class)!=null) 
                return field;
            
        
     while((myClass=myClass.getSuperclass())!=null);
    return null;

小心虽然Class.getFields() 似乎是一个更好的选择,因为它只返回公共字段,但它不起作用:

返回一个包含 Field 对象的数组,该数组反映了该 Class 对象表示的类或接口的所有可访问的公共字段

EDIT 以上仅适用于在字段上严格使用@Version 注释的情况。然而,正如 Konstantin Pribluda 所指出的,这个注解也可以放在方法上,这意味着两件事:

方法也必须检查 接口也必须检查,因为方法可以由接口定义...

这也意味着我们的简单循环必须转化为递归图遍历...

public static Method getVersionMethod(Class<?> myClass) 
    Method[] methods = myClass.getDeclaredMethods();
    for(Method method : methods) 
        if(method.getAnnotation(Version.class)!=null) 
            return method;
        
    

    if(myClass.getSuperclass()!=null) 
        Class<?> returned = getVersionMethod(myClass.getSuperclass());
        if(returned!=null) 
            return returned;
        
    

    for(Class<?> interfaceToCheck : myClass.getInterfaces()) 
        Class<?> returned = getVersionMethod(interfaceToCheck);
        if(returned!=null) 
            return returned;
        
    

    return null;

【讨论】:

呃,丑陋,但也许这是一个解决方案......最好是通过myClass.getFields()而不需要去超类 @Oliv Better would be to go through myClass.getFields() 不可能! Check the API doc! Returns an array containing Field objects reflecting all the accessible public fields of the class or interface represented by this Class object getFields() 只列出 public 字段! 这些注解可以在字段、getter 甚至接口上——您也必须检查它们以及在 xml 映射声明中。真是一团糟…… @KonstantinPribluda 你是对的,忘记检查注释的范围,我认为它只能在变量上......这真的让它一团糟......不过我会更新我的回答,它可能碰巧对某人有帮助......【参考方案3】:

我错过了什么吗?我将通过在公开版本字段的可版本化实体上实现一个接口来解决此问题。无需反思。

但这是假设您当然可以修改实体类。

【讨论】:

我正在编写一个通用工具,不希望强制开发人员实现某些接口。【参考方案4】:

如果你使用的是EclipseLink,你可以获取使用的版本,

Session session = em.unwrap(Session.class);
session.getDescriptor(Employee.class).getOptimisticLockingPolicy().getWriteLockValue(object, id, session);

【讨论】:

我必须是通用的,提供者中立的。【参考方案5】:

我通过以下方式解决了它:javax.persistence.Version 类指定了支持的数据类型,仅支持七种,所以我一个接一个地尝试所有这些。 JPA 规范显然遗漏了getVersionType() 方法,我认为这是一个错误。

/**
 * Finds the version attribute for ManagedType instance.
 * @param managedType The metaobject
 * @return The version attribute or null, if it does not have one
 * @throws UnsupportedOperationException
 * If it has a version attribute, but is not one of supported types, as specified in @link Version documentation
 * (currently int, Integer, short, Short, long, Long, Timestamp).
 */
private static <T> SingularAttribute<? super T, ?> findVersionAttribute(ManagedType<T> managedType) 
    if ( !(managedType instanceof IdentifiableType))
        return null;

    IdentifiableType<T> identifiableType = (IdentifiableType<T>)managedType;
    if ( ! identifiableType.hasVersionAttribute())
        return null;

    try 
        return identifiableType.getVersion(int.class);
     catch (IllegalArgumentException e) 
        try 
            return identifiableType.getVersion(Integer.class);
         catch (IllegalArgumentException e1) 
            try 
                return identifiableType.getVersion(long.class);
             catch (IllegalArgumentException e2) 
                try 
                    return identifiableType.getVersion(Long.class);
                 catch (IllegalArgumentException e3) 
                    try 
                        return identifiableType.getVersion(short.class);
                     catch (IllegalArgumentException e4) 
                        try 
                            return identifiableType.getVersion(Short.class);
                         catch (IllegalArgumentException e5) 
                            try 
                                return identifiableType.getVersion(Timestamp.class);
                             catch (IllegalArgumentException e6) 
                                throw new UnsupportedOperationException("The version attribute is not of supported type");
                            
                        
                    
                
            
        
    

【讨论】:

以上是关于获取 JPA 实体版本的通用方法的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JPA 存储通用实体?

spring jpa之实体属性类型转换器AttributeConverter,自定义Converter,通用Converter

通用 JSF 实体转换器 [重复]

从不同核心数据实体获取数据并将结果转换为相应类的通用方法

EclipseLink + JPA + 通用实体 + SINGLE_TABLE 继承

EF Generic Repository 从新插入的通用实体获取 Id