Question

I have an unknown JPA entity and need to know it's version. I could not find a way to do this generically.

I tried the metamodel, but don't know what to pass to getVersion() method:

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

Same pattern is used in metaClass.getId(), but there is a complementary method getIdType() -- getVersionType() is missing.

Secondly, there is entityManagerFactory.getPersistenceUnitUtil().getIdentifier(entity) method, but not getVersion(entity) method.

Is there some way to get version from an unknown entity?

Was it helpful?

Solution 5

I solved it in the following way: The javax.persistence.Version class specifies supported datatypes, only seven are supported, so I try all of them one after other. The JPA spec apparently misses the getVersionType() method, I think it is a bug.

/**
 * 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");
                            }
                        }
                    }
                }
            }
        }
    }
}

OTHER TIPS

Just a bit cleaner than the answer with 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;
}

IMHO, you should put the type of the field annotated with the @Version annotation in that class. I think this will work with inheritance too, out of the box.

If you don't know the type beforehand, I'd go with standard reflection. I'd iterate through all the declared fields, and check annotations if they contain the @Version annotation. If not found, check superclass, until found...

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;
}

Beware While the Class.getFields() seems to be a nicer alternative, as it only returns the public fields, it would not work:

Returns an array containing Field objects reflecting all the accessible public fields of the class or interface represented by this Class object.

EDIT The above works only in a case, when the @Version annotation is strictly used on Fields. However, as Konstantin Pribluda pointed out, this annotation can be put on methods too, which means two things:

  • methods must be checked too
  • interfaces must be checked too, as methods can be defined by interfaces...

This also means that our simple cycle must be transformed into a recursive graph traversal...

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;
}

Am I missing something? I'd fix this by implementing an interface on the versionable entities that exposes the version field. No need for reflection.

But that is assuming you can modify the entity classes of course.

If you are using EclipseLink, you can get the version using,

Session session = em.unwrap(Session.class);
session.getDescriptor(Employee.class).getOptimisticLockingPolicy().getWriteLockValue(object, id, session);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top