我有一个自定义存储库,声明如下(用 Kotlin 编写):
interface FooRepository : JpaRepository<Foo, Int> {
fun findByFoo(foo: String): List<Foo>
fun findByBar(bar: String): List<Foo> {
//custom implementation
}
}
data class Foo(var id: Int, var foo: String, var bar: String)
这两种方法都满足JPA存储库的命名约定,但我想自己实现第二种方法(
FooRepository.findByBar
)。如何防止 JPA 创建查询?@Query
注释不符合我的要求。如果您希望存储库自定义方法仅委托给另一个方法,那么在接口中编写非抽象方法是可行的方法。
但在 Kotlin 中,您需要添加
-Xjvm-default=all
编译参数,否则 Spring 会认为您的方法没有主体,并且无论如何都会初始化失败。
TL;博士
我今天在 Spring/Kotlin 项目上遇到了同样的问题,这似乎是一个简单的用例,我想知道我们如何没有更多的人抱怨它。
事实证明,这是 Kotlin 默认情况下处理接口中非抽象方法的一个极端情况!
每个人都推荐的解决方案是为自定义方法创建一个单独的接口和实现,但如果您想最终委托给自动生成的方法,这种解决方案实际上并不起作用。
由于我需要相当于委托的
findByBar
,我的第一次尝试是简单地在接口方法中编写一个主体,这完全受 Kotlin 支持,最终委托给自动生成的方法。令我惊讶的是,Spring 仍然崩溃,说我需要添加一个 @Query
注释。
然后,在看到其他人在 Java 中成功使用
default
方法(例如 1 和 2)后,我尝试了一下。我用 Java 创建了同样的东西,在界面中使用了 default
方法,并且它起作用了!
由于 Kotlin 必须与 Java 一样强大,所以我认为我只需要
-Xjvm-default=all
编译器参数(完整的问题历史记录此处)只是时间问题,因此我的非抽象方法已正确生成为default
接口方法。
最佳实践:根据Heshan的建议,您可以将自定义接口添加到存储库接口中。您的实现将被加载(文件名带有“Impl”后缀),并且它将覆盖同名的默认方法。
扩展JpaRepository:为什么要扩展JpaRepository接口?如果您这样做,Spring 将尝试自己创建一个存储库实例。为了防止这种情况,请用 @NoRepositoryBean
注释您的存储库
interface。
@NoRepositoryBean
public interface FooRepositoryCustom extends JpaRepository<Foo, Int> {
}
参考:我正在尝试使用 Spring Data Elasticsearch 做类似的事情 - 覆盖现有的存储库方法。我遵循了他们的文档,请参阅https://docs.spring.io/spring-data/elasticsearch/docs/4.1.15/reference/html/#repositories.custom-implementations
您可以简单地创建另一个类似
FooRepositoryCustom
的接口,并为其创建一个实现类 (FooRepositoryCustomImpl
),并在该 FooRepositoryCustomImpl
类上进行实现,并将 FooRepositoryCustom
接口扩展到您的 FooRepository
接口。
FooRepositoryCustom
:
public interface FooRepositoryCustom {
fun findByBar(bar: String): List<Foo>;
}
FooRepositoryCustomImpl
:
public class FooRepositoryCustomImpl implements FooRepositoryCustom {
@Override
public fun findByBar(bar: String): List<Foo> {
//your implementation goes here
}
}
FooRepository
:
interface FooRepository : JpaRepository<Foo, Int>, FooRepositoryCustom {
}