Reputation: 850
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
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
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
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
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
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