tkralik
tkralik

Reputation: 850

Spring Data - get entity name/type from Repository

I am looking for a way to get an entity type or class name from an instance implementing Spring Data JPA Repository interface.

I have got a number of interfaces extending a base interface that extends Repository interface and that defines some base queries.

@NoRepositoryBean
public interface EnumerationRepository<T extends IDatabaseEnumeration> extends Repository<T, String> {
    // ...
}

public interface SampleEnumerationRepository extends EnumerationRepository<SampleEnumeration> {

}

Spring allows me to inject implementations of all these interfaces as a collection into a bean

@Autowired
private Collection<EnumerationRepository<? extends IDatabaseEnumeration>> repositories;

I wanted to put all of these implementations into a Map for an easy access inside a generic method. I wanted to use the entity type or name as the key but I am not sure where to get it. Is there any way to get one of these attributes? Preferably the class type.

I was able to achieve the desired behaviour with a query like this. But I would like to avoid this solution if possible as it creates a dependency on the underlying DB.

@Query(value = "select '#{#entityName}' from dual", nativeQuery = true)
public String getEntityName();

Upvotes: 5

Views: 12909

Answers (5)

Thiago Sperandio
Thiago Sperandio

Reputation: 76

I'll duplicate my answer here because it's the same as another similar question:


It's my opinion based on my recent experience to make reflection in some classes or interfaces in Spring Boot.

As @ekcrisp said in his answer in similar question:

You can use Spring's GenericTypeResolver to get the Entity class from your Repository.

My experience: unfortunately the common ways of reflection don't work to get the <T> entity class in a Spring Boot's repository. I tried in a lot of maneers. But this GenericTypeResolver actually could get the generics <T> type from JpaRepository<T, ID>.

In other words, the function below can provide all generic types of a class as an array. Then you can choose one of them:

public Class<?>[] getGenericType( Class<?> classInstance, Class<?> classToGetGenerics ) {
    return GenericTypeResolver.resolveTypeArguments( classInstance, classToGetGenerics );
}

This answer is about this stack and its versions:

JDK 11, Spring Boot 2.7.11 with Spring MVC, Spring Data JPA, and others libs. Posted in 2023-05-11.


If you want to add a solution in your "ObjectUtil" local solution, maybe these methods can be useful to get generic types, in any position, like get <T> or <ID> in JpaRepository<T, ID>:

@UtilityClass
public class ObjectUtil {
    // ...

    /**
     *
     * @param classInstance
     * @param classToGetGenerics
     *
     * @return the generic classes of the given param, based on class to get generics param.
     *
     * @see GenericTypeResolver#resolveTypeArguments
     */
    public static Class<?>[] getGenericType( Class<?> classInstance, Class<?> classToGetGenerics ) {
        return GenericTypeResolver.resolveTypeArguments( classInstance, classToGetGenerics );
    }

    /**
     *
     * @param classInstance
     * @param classToGetGenerics
     * @param genericPosition
     *
     * @return the generic class of the given param, based on class to get generics and generic position params.
     *
     * @see ObjectUtil#getGenericType
     * @see GenericTypeResolver#resolveTypeArguments
     */
    public static Class<?> getGenericType( Class<?> classInstance, Class<?> classToGetGenerics, int genericPosition ) {
        Class<?>[] typeArguments = getGenericType( classInstance, classToGetGenerics );
        if( typeArguments != null && typeArguments.length >= genericPosition ) {
            return typeArguments[genericPosition];
        }
        throw new IllegalArgumentException( "Could not determine generic type for interface " + classInstance.getName() );
    }
    
    // ... 
}

So we can use like the example below (I created a QueryUtil to handle specifically persistence stuff):

@UtilityClass
public class QueryUtil {

    // ...

    /**
     *
     * @param repositoryClass
     *
     * @return the @Entity class of the given repository param.
     *
     * @see ObjectUtil#getGenericType(Class, Class, int)
     * @see ObjectUtil#getGenericType(Class, Class)
     */
    public static Class<?> getRepositoryEntityType( Class<?> repositoryClass ) {
        return ObjectUtil.getGenericType( repositoryClass, Repository.class, 0 );
    }

}

I hope this is still useful. Here it was used in a customized audit logic, made by aspects, so that the Entity is audited.

Upvotes: 0

ChenKeYu
ChenKeYu

Reputation: 1

I fix this problem by use typetools https://github.com/jhalterman/typetools

this is my code:

Class<?>[] classes = TypeResolver.resolveRawArguments(JpaRepository.class, jpaRepositoryInstance.getClass());
Class<?> entityClz = classes[0];

Upvotes: 0

Pawel Zieminski
Pawel Zieminski

Reputation: 534

Try this

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import org.springframework.aop.support.AopUtils;
import org.springframework.data.repository.Repository;
import org.springframework.util.Assert;

import java.util.List;

public class Repospector {
    private static final TypeResolver RESOLVER = new TypeResolver();

    public Class<?> getEntityClass(Repository<?, ?> repository) {
        Class<?> targetClass = AopUtils.getTargetClass(repository);
        List<ResolvedType> types = RESOLVER.resolve(targetClass).typeParametersFor(Repository.class);
        Assert.state(types.size() == 2, "Call the whambulance!");
        return types.get(0).getErasedType();
    }
}

Upvotes: 0

tkralik
tkralik

Reputation: 850

@jens-schauder 's answer did not work in my case but it showed me the right direction.

Spring injected me the implementation of my interface extending the spring Repository interface. Therefore I had to get all interfaces, filter out spring internal ones, so I ended up with the one I defined. This one however was not generic yet so I had to get its super interface that had the generic type.

I don't really care about performance as this method is called only during Spring container initialization.

Fortunatelly polymorphism works quite well in this case. So I only had to implement default method on the super interface. Like this

@NoRepositoryBean
public interface EnumerationRepository<T extends IDatabaseEnumeration> extends Repository<T, String> {

    // ...

    default Class<T> getEntityClass() {
        Type[] interfaces = getClass().getInterfaces();

        for (Type t : interfaces) {
            if (t instanceof Class<?>) {
                Class<?> clazz = (Class<?>) t;

                if (clazz.getPackage().getName().startsWith(ApplicationConst.BASE_PACKAGE)) {

                    // Repositories should implement only ONE interface from application packages

                    Type genericInterface = clazz.getGenericInterfaces()[0];

                    return (Class<T>) ((ParameterizedType) genericInterface).getActualTypeArguments()[0];
                }
            }
        }

        return null;
    }

}

I hope this might be useful to other users facing similar issues.

Upvotes: 6

Jens Schauder
Jens Schauder

Reputation: 81907

You can get the type arguments if they are bound to a type from the interface, using this answer: https://stackoverflow.com/a/1901275/66686

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)yourRepo.getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

Upvotes: 1

Related Questions