springboot~SpringData自定義Repository的正確方式
獲取Spring Data自定義Repository中的實際類型
在Spring Data中,當您實現(xiàn)自定義Repository時,由于Java類型擦除的原因,泛型參數(shù)T在運行時確實會被擦除為Object類型。不過,有幾種方法可以獲取實際的類型信息。
你想在自定義的 Spring Data Neo4j Repository 接口中通過默認方法獲取泛型 T 的實際類型,這個想法很自然,但遺憾的是,由于 Java 泛型在編譯后的"類型擦除"機制,直接在接口的默認方法中可靠地獲取 T 的實際類型(Class
Java 的泛型主要在編譯階段提供類型安全檢查,編譯后泛型類型信息(如 T)會被擦除(除非是繼承自泛型父類或實現(xiàn)了泛型接口,且這些泛型類型已被具體化)。在你的 CustomNeo4jRepository<T, ID> 接口中,T 是一個類型參數(shù)。接口的默認方法中,無法直接獲取實現(xiàn)類所指定的 T 的具體類型。
問題描述
直接獲取泛型實際類型的挑戰(zhàn)
Java 的泛型主要在編譯階段提供類型安全檢查,編譯后泛型類型信息(如 T)會被擦除(除非是繼承自泛型父類或實現(xiàn)了泛型接口,且這些泛型類型已被具體化)。在你的 CustomNeo4jRepository<T, ID> 接口中,T 是一個類型參數(shù)。接口的默認方法中,無法直接獲取實現(xiàn)類所指定的 T 的具體類型。
例如,你希望這樣:
public interface CustomNeo4jRepository<T, ID> {
default Class<T> getEntityType() {
// 無法直接在此獲取到 UserNode 等具體類型
// 編譯后 T 會被擦除為 Object
return ...;
}
}
當 UserRepository 繼承 CustomNeo4jRepository<UserNode, Long> 時,JVM 在運行時看到的仍然是 CustomNeo4jRepository<Object, Serializable>,無法感知到 UserNode。
即使嘗試通過反射獲取泛型信息(如 getClass().getGenericInterfaces()),其結果也取決于接口是如何被繼承和代理的。Spring Data 通常會為 Repository 接口創(chuàng)建代理對象,這使得通過反射獲取到的泛型信息很可能是 T 本身(一個 TypeVariable),而非具體的 UserNode 類型,因此難以直接轉換為 Class
問題回顧(為什么難)
- Java 泛型在運行時會被擦除(type erasure),直接用 T 在運行時無法得到 Class。
- Spring Data 在創(chuàng)建 Repository 時會用生成的實現(xiàn)類 / 代理類,進一步增加了通過 getClass() 找泛型信息的不穩(wěn)定性。
先要明確一點
- 你提到?jīng)]有 Neo4jRepositoryFactory,且 Neo4jRepositoryFactoryBean 是 final,這說明你在用的是較新的 SDN 版本(例如 SDN6系列或更高),API 與老版本不同。基于此,不建議嘗試繼承 factory bean 或直接替換內部工廠,而應采用官方推薦的擴展點:repository fragments、repository base class(如果支持)或 AOP/Service 包裝器等。
解決方案[已成功]
- 添加自定義的注解@EnableNeo4jCustomRepository,這個注入用來為@EnableNeo4jRepositories注解添加默認值,避免開發(fā)人員直接干預它
- 為CustomNeo4jRepository接口添加注解@NoRepositoryBean,不讓jpa使用代理建立實現(xiàn)類
- 為CustomNeo4jRepository類添加基類SimpleNeo4jRepository,讓它有操作neo4j數(shù)據(jù)庫的基本能力,它在上面去擴展個性化方法
- 開發(fā)人員在業(yè)務項目中,直接引用@EnableNeo4jCustomRepository注解即可
/**
* 引入個人性倉儲
* @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 {
}
/**
* 個性化接口規(guī)范
* @author lind
* @date 2025/9/1 14:57
* @since 1.0.0
*/
@NoRepositoryBean // 不讓jpa使用代理建立實現(xiàn)類
public interface CustomNeo4jRepository<T, ID> {
/**
* 根據(jù)節(jié)點名稱模糊查詢并分頁
* @param namePattern 名稱模式(如"%知%")
* @param pageable 分頁信息
* @return 分頁后的節(jié)點列表
*/
Page<T> findByNameLike(String namePattern, Pageable pageable);
}
/**
* 個性化接口實現(xiàn)
* @author lind
* @date 2025/9/1 14:57
* @since 1.0.0
*/
public class CustomNeo4jRepositoryImpl<T, ID> extends SimpleNeo4jRepository<T,ID>
implements CustomNeo4jRepository<T, ID> {
private final Neo4jOperations neo4jOperations;
Neo4jEntityInformation<T, ID> entityInformation;
Class<T> domainType;
protected CustomNeo4jRepositoryImpl(Neo4jOperations neo4jOperations,
Neo4jEntityInformation<T, ID> entityInformation) {
super(neo4jOperations,entityInformation);
this.neo4jOperations = neo4jOperations;
this.entityInformation = entityInformation;
this.domainType = entityInformation.getJavaType();
}
@Override
public Page<T> 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<String, Object> parameters = new HashMap<>();
parameters.put("name", namePattern.replace("%", ""));
parameters.put("skip", pageable.getOffset());
parameters.put("limit", pageable.getPageSize());
List<T> results = neo4jOperations.findAll(cypherQuery, parameters, domainType);
// 獲取總數(shù)用于分頁
String countQuery = "MATCH (n:" + domainType.getSimpleName() + ") WHERE n.name CONTAINS $name RETURN COUNT(n)";
Long total = neo4jOperations.count(countQuery, parameters);
return new PageImpl<>(results, pageable, total);
}
}
@SpringBootApplication
@EnableNeo4jAuditing
@EnableNeo4jCustomRepository
public class NeoApp {
public static void main(String[] args) {
SpringApplication.run(NeoApp.class, args);
}
}
浙公網(wǎng)安備 33010602011771號