Spring Data - 从Repository中获取实体名称/类型

4

我希望能够从实现Spring Data JPA Repository接口的实例中获取实体类型或类名。

我有许多接口都继承自一个基础接口,这个基础接口又继承了Repository接口,并定义了一些基本查询。

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

public interface SampleEnumerationRepository extends EnumerationRepository<SampleEnumeration> {

}

Spring 允许我将所有这些接口的实现作为集合注入到一个 bean 中

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

我想把所有这些实现放入一个Map中,以便在通用方法中轻松访问。我想使用实体类型或名称作为键,但不确定在哪里获取它。是否有任何方法可以获取其中一个属性?最好是类类型。
我能够通过以下查询实现所需的行为。但如果可能的话,我想避免使用此解决方案,因为它会创建对底层数据库的依赖。
@Query(value = "select '#{#entityName}' from dual", nativeQuery = true)
public String getEntityName();

尝试移除 "from dual" 并删除 nativeQuery 部分,它应该作为普通 JPA 查询工作。 - Lukas Bradley
不幸的是,那个方法没有起作用。现在我遇到了一个异常,因为查询不完整。Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected end of subtree [select 'SampleEnumeration'] - tkralik
我百分之百肯定我在一个个人项目上做过同样的事情,但现在找不到了。我今晚稍后会再搜索一下。与此同时,我建议暂时保留“from dual”和本机设置。 - Lukas Bradley
5个回答

6

@jens-schauder的回答在我的情况下没有起作用,但是它指引了我正确的方向。

Spring为我注入了实现了Spring Repository接口的接口。因此,我需要获取所有接口,过滤掉Spring内部的接口,最终得到我定义的接口。然而,这个接口还没有泛型,所以我需要获取它的超级接口,其中包含了泛型类型。

我并不太关心性能,因为这个方法只在Spring容器初始化期间调用。

幸运的是,多态在这种情况下非常有效。所以我只需要在超级接口上实现默认方法就可以了。像这样:

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

}

我希望这对其他遇到类似问题的用户有所帮助。

我有同样的用例,这确实非常有用。谢谢! - N Dierauf
对我来说,执行这个代码块需要大约400毫秒的时间。虽然不是最快的解决方案,但它能完成任务。 - pavlee

1

如果类型参数绑定到接口类型,您可以使用此答案获取类型参数:https://dev59.com/cXI-5IYBdhLWcg3wW24L#1901275

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

0

我通过使用typetools https://github.com/jhalterman/typetools来解决这个问题。

这是我的代码:

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

0

试试这个

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

@Andrey,它对你来说表现如何? - Pawel Zieminski

0

我会复制 我的回答在这里,因为它与另一个类似的问题相同


根据我最近的经验,我认为在Spring Boot中的某些类或接口中进行反射是有必要的。

正如@ekcrisp在类似问题的回答中所说

您可以使用Spring的GenericTypeResolver从Repository中获取实体类。

我的经验:不幸的是,常见的反射方式无法在Spring Boot的repository中获取<T>实体类。我尝试了很多种方法。但是这个GenericTypeResolver确实可以从JpaRepository<T, ID>中获取泛型<T>类型。

换句话说,下面的函数可以将类的所有泛型类型作为数组提供。然后您可以选择其中之一:

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

本答案涉及以下堆栈及其版本:

JDK 11,Spring Boot 2.7.11与Spring MVC,Spring Data JPA和其他库。发布于2023年5月11日。


如果您想在本地“ObjectUtil”解决方案中添加一个解决方案,也许这些方法可以帮助您获取任何位置的通用类型,例如在JpaRepository<T, ID>中获取<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() );
    }
    
    // ... 
}

因此,我们可以像下面的示例一样使用(我创建了一个QueryUtil来专门处理持久性问题):

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

}

希望这仍然有用。 这里它被用于由方面制作的定制审计逻辑,以便对实体进行审计。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接