中途岛之战 發表於 2025-9-8 16:57:00

springboot~SpringData自定义Repository的正确方式

<h1 id="获取spring-data自定义repository中的实际类型">获取Spring Data自定义Repository中的实际类型</h1>
<p>在Spring Data中,当您实现自定义Repository时,由于Java类型擦除的原因,泛型参数T在运行时确实会被擦除为Object类型。不过,有几种方法可以获取实际的类型信息。</p>
<p>你想在自定义的 Spring Data Neo4j Repository 接口中通过默认方法获取泛型 T 的实际类型,这个想法很自然,但遗憾的是,由于 Java 泛型在编译后的"类型擦除"机制,直接在接口的默认方法中可靠地获取 T 的实际类型(Class<t>)是非常困难甚至不可行的。</t></p>
<p>Java 的泛型主要在编译阶段提供类型安全检查,编译后泛型类型信息(如 T)会被擦除(除非是继承自泛型父类或实现了泛型接口,且这些泛型类型已被具体化)。在你的 CustomNeo4jRepository&lt;T, ID&gt; 接口中,T 是一个类型参数。接口的默认方法中,无法直接获取实现类所指定的 T 的具体类型。</p>
<h2 id="问题描述">问题描述</h2>
<h3 id="直接获取泛型实际类型的挑战">直接获取泛型实际类型的挑战</h3>
<p>Java 的泛型主要在编译阶段提供类型安全检查,编译后泛型类型信息(如 T)会被擦除(除非是继承自泛型父类或实现了泛型接口,且这些泛型类型已被具体化)。在你的 CustomNeo4jRepository&lt;T, ID&gt; 接口中,T 是一个类型参数。接口的默认方法中,无法直接获取实现类所指定的 T 的具体类型。</p>
<p>例如,你希望这样:</p>
<pre><code>public interface CustomNeo4jRepository&lt;T, ID&gt; {
   
    default Class&lt;T&gt; getEntityType() {
      // 无法直接在此获取到 UserNode 等具体类型
      // 编译后 T 会被擦除为 Object
      return ...;
    }
}
</code></pre>
<p>当 UserRepository 继承 CustomNeo4jRepository&lt;UserNode, Long&gt; 时,JVM 在运行时看到的仍然是 CustomNeo4jRepository&lt;Object, Serializable&gt;,无法感知到 UserNode。</p>
<p>即使尝试通过反射获取泛型信息(如 getClass().getGenericInterfaces()),其结果也取决于接口是如何被继承和代理的。Spring Data 通常会为 Repository 接口创建代理对象,这使得通过反射获取到的泛型信息很可能是 T 本身(一个 <code>TypeVariable</code>),而非具体的 UserNode 类型,因此难以直接转换为 Class<t>。</t></p>
<h3 id="问题回顾为什么难">问题回顾(为什么难)</h3>
<ul>
<li>Java 泛型在运行时会被擦除(type erasure),直接用 T 在运行时无法得到 Class。</li>
<li>Spring Data 在创建 Repository 时会用生成的实现类 / 代理类,进一步增加了通过 getClass() 找泛型信息的不稳定性。</li>
</ul>
<h3 id="先要明确一点">先要明确一点</h3>
<ul>
<li>你提到没有 Neo4jRepositoryFactory,且 Neo4jRepositoryFactoryBean 是 final,这说明你在用的是较新的 SDN 版本(例如 SDN6系列或更高),API 与老版本不同。基于此,不建议尝试继承 factory bean 或直接替换内部工厂,而应采用官方推荐的扩展点:repository fragments、repository base class(如果支持)或 AOP/Service 包装器等。</li>
</ul>
<h2 id="解决方案已成功">解决方案[已成功]</h2>
<ol>
<li>添加自定义的注解@EnableNeo4jCustomRepository,这个注入用来为@EnableNeo4jRepositories注解添加默认值,避免开发人员直接干预它</li>
<li>为CustomNeo4jRepository接口添加注解@NoRepositoryBean,不让jpa使用代理建立实现类</li>
<li>为CustomNeo4jRepository类添加基类SimpleNeo4jRepository,让它有操作neo4j数据库的基本能力,它在上面去扩展个性化方法</li>
<li>开发人员在业务项目中,直接引用@EnableNeo4jCustomRepository注解即可</li>
</ol>
<pre><code>/**
* 引入个人性仓储
* @author lind
* @date 2025/9/1 14:57
* @since 1.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableNeo4jAuditing
@EnableNeo4jRepositories(repositoryBaseClass = CustomNeo4jRepositoryImpl.class)
public @interface EnableNeo4jCustomRepository {
}

/**
* 个性化接口规范
* @author lind
* @date 2025/9/1 14:57
* @since 1.0.0
*/
@NoRepositoryBean // 不让jpa使用代理建立实现类
public interface CustomNeo4jRepository&lt;T, ID&gt; {

    /**
   * 根据节点名称模糊查询并分页
   * @param namePattern 名称模式(如"%知%")
   * @param pageable 分页信息
   * @return 分页后的节点列表
   */
    Page&lt;T&gt; findByNameLike(String namePattern, Pageable pageable);
}

/**
* 个性化接口实现
* @author lind
* @date 2025/9/1 14:57
* @since 1.0.0
*/
public class CustomNeo4jRepositoryImpl&lt;T, ID&gt; extends SimpleNeo4jRepository&lt;T,ID&gt;
      implements CustomNeo4jRepository&lt;T, ID&gt; {

    private final Neo4jOperations neo4jOperations;
    Neo4jEntityInformation&lt;T, ID&gt; entityInformation;
    Class&lt;T&gt; domainType;

    protected CustomNeo4jRepositoryImpl(Neo4jOperations neo4jOperations,
                                        Neo4jEntityInformation&lt;T, ID&gt; entityInformation) {
      super(neo4jOperations,entityInformation);
      this.neo4jOperations = neo4jOperations;
      this.entityInformation = entityInformation;
      this.domainType = entityInformation.getJavaType();
    }


    @Override
    public Page&lt;T&gt; findByNameLike(String namePattern, Pageable pageable) {
      String cypherQuery = "MATCH (n:" + domainType.getSimpleName() +
                ") WHERE n.userName CONTAINS $name RETURN n ORDER BY n.userName SKIP $skip LIMIT $limit";
      Map&lt;String, Object&gt; parameters = new HashMap&lt;&gt;();
      parameters.put("name", namePattern.replace("%", ""));
      parameters.put("skip", pageable.getOffset());
      parameters.put("limit", pageable.getPageSize());

      List&lt;T&gt; results = neo4jOperations.findAll(cypherQuery, parameters, domainType);

      // 获取总数用于分页
      String countQuery = "MATCH (n:" + domainType.getSimpleName() + ") WHERE n.name CONTAINS $name RETURN COUNT(n)";
      Long total = neo4jOperations.count(countQuery, parameters);

      return new PageImpl&lt;&gt;(results, pageable, total);
    }
}

@SpringBootApplication
@EnableNeo4jAuditing
@EnableNeo4jCustomRepository
public class NeoApp {
        public static void main(String[] args) {
                SpringApplication.run(NeoApp.class, args);
        }
}

</code></pre>


</div>
<div id="MySignature" role="contentinfo">
    <p></p>
<div class="navgood">
<p>作者:仓储大叔,张占岭,<br>
荣誉:微软MVP<br>QQ:853066980</p>

<p><strong>支付宝扫一扫,为大叔打赏!</strong>
<br><img src="https://images.cnblogs.com/cnblogs_com/lori/237884/o_IMG_7144.JPG"></p>
</div><br><br>
来源:https://www.cnblogs.com/lori/p/19080098
頁: [1]
查看完整版本: springboot~SpringData自定义Repository的正确方式