springboot~SpringData自(zi)定義Repository的(de)正(zheng)確方式(shi)
獲取Spring Data自定義Repository中的實際類型
在Spring Data中(zhong),當您實(shi)現(xian)自(zi)定義Repository時(shi),由(you)于Java類型擦除(chu)的原因(yin),泛型參(can)數T在運行時(shi)確實(shi)會被擦除(chu)為Object類型。不過,有幾種方(fang)法可(ke)以獲取實(shi)際的類型信息。
你想在自定義的 Spring Data Neo4j Repository 接口中通過默認方法獲取泛型 T 的實際類型,這個想法很自然,但遺憾的是,由于 Java 泛型在編譯后的"類型擦除"機制,直接在接口的默認方法中可靠地獲取 T 的實際類型(Class
Java 的(de)(de)泛型(xing)(xing)(xing)主要在(zai)編譯(yi)階段提供類型(xing)(xing)(xing)安全(quan)檢查,編譯(yi)后泛型(xing)(xing)(xing)類型(xing)(xing)(xing)信(xin)息(如 T)會被(bei)擦除(除非是(shi)繼(ji)承自泛型(xing)(xing)(xing)父類或實(shi)(shi)現(xian)了(le)泛型(xing)(xing)(xing)接(jie)(jie)口,且這些(xie)泛型(xing)(xing)(xing)類型(xing)(xing)(xing)已被(bei)具體化)。在(zai)你的(de)(de) CustomNeo4jRepository<T, ID> 接(jie)(jie)口中,T 是(shi)一(yi)個類型(xing)(xing)(xing)參數。接(jie)(jie)口的(de)(de)默認方(fang)法(fa)中,無法(fa)直(zhi)接(jie)(jie)獲取實(shi)(shi)現(xian)類所指定的(de)(de) T 的(de)(de)具體類型(xing)(xing)(xing)。
問題描述
直接獲取泛型實際類型的挑戰
Java 的泛(fan)型主要(yao)在編(bian)譯階段提供類型安全檢查,編(bian)譯后泛(fan)型類型信息(如 T)會被(bei)擦除(除非是繼承自泛(fan)型父類或(huo)實現了泛(fan)型接口(kou),且這(zhe)些泛(fan)型類型已被(bei)具體化)。在你的 CustomNeo4jRepository<T, ID> 接口(kou)中,T 是一個類型參數。接口(kou)的默認方法(fa)中,無法(fa)直接獲(huo)取實現類所指(zhi)定的 T 的具體類型。
例如,你希望這樣:
public interface CustomNeo4jRepository<T, ID> {
default Class<T> getEntityType() {
// 無法直接在此獲取到 UserNode 等具體類型
// 編譯后 T 會被擦除為 Object
return ...;
}
}
當 UserRepository 繼承 CustomNeo4jRepository<UserNode, Long> 時,JVM 在運(yun)行時看到(dao)的仍然(ran)是(shi) CustomNeo4jRepository<Object, Serializable>,無法感(gan)知(zhi)到(dao) UserNode。
即使嘗試通過反射獲取泛型信息(如 getClass().getGenericInterfaces()),其結果也取決于接口是如何被繼承和代理的。Spring Data 通常會為 Repository 接口創建代理對象,這使得通過反射獲取到的泛型信息很可能是 T 本身(一個 TypeVariable),而非具體的 UserNode 類型,因此難以直接轉換為 Class
問題回顧(為什么難)
- Java 泛型在運行時會被擦除(type erasure),直接用 T 在運行時無法得到 Class。
- Spring Data 在創建 Repository 時會用生成的實現類 / 代理類,進一步增加了通過 getClass() 找泛型信息的不穩定性。
先要明確一點
- 你提到沒有 Neo4jRepositoryFactory,且 Neo4jRepositoryFactoryBean 是 final,這說明你在用的是較新的 SDN 版本(例如 SDN6系列或更高),API 與老版本不同。基于此,不建議嘗試繼承 factory bean 或直接替換內部工廠,而應采用官方推薦的擴展點:repository fragments、repository base class(如果支持)或 AOP/Service 包裝器等。
解決方案[已成功]
- 添加自定義的注解@EnableNeo4jCustomRepository,這個注入用來為@EnableNeo4jRepositories注解添加默認值,避免開發人員直接干預它
- 為CustomNeo4jRepository接口添加注解@NoRepositoryBean,不讓jpa使用代理建立實現類
- 為CustomNeo4jRepository類添加基類SimpleNeo4jRepository,讓它有操作neo4j數據庫的基本能力,它在上面去擴展個性化方法
- 開發人員在業務項目中,直接引用@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 {
}
/**
* 個性化接口規范
* @author lind
* @date 2025/9/1 14:57
* @since 1.0.0
*/
@NoRepositoryBean // 不讓jpa使用代理建立實現類
public interface CustomNeo4jRepository<T, ID> {
/**
* 根據節點名稱模糊查詢并分頁
* @param namePattern 名稱模式(如"%知%")
* @param pageable 分頁信息
* @return 分頁后的節點列表
*/
Page<T> findByNameLike(String namePattern, Pageable pageable);
}
/**
* 個性化接口實現
* @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);
// 獲取總數用于分頁
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);
}
}