对于手动实现的Spring数据存储库方法,我应该使用Java8默认方法吗?

对于手动实现的Spring数据存储库方法,我应该使用Java8默认方法吗?,java,spring,spring-data,spring-data-jpa,Java,Spring,Spring Data,Spring Data Jpa,在使用新的SpringDataEvans版本时,能够使用Java8附带的一些好东西是很好的。其中之一是接口中的默认实现。下面的存储库使用QueryDSL使查询类型安全 我的问题是,在我写这篇文章之前,我为findByLogin使用了一个单独的UserRepositoryCustom接口的模式,然后是另一个类UserRepositoryImpl,在这个类中,我将使用@PersistenceContext来获取当前的EntityManager 当我没有类时,如何获取EntityManager?有可能

在使用新的SpringDataEvans版本时,能够使用Java8附带的一些好东西是很好的。其中之一是接口中的默认实现。下面的存储库使用QueryDSL使查询类型安全

我的问题是,在我写这篇文章之前,我为
findByLogin
使用了一个单独的
UserRepositoryCustom
接口的模式,然后是另一个类
UserRepositoryImpl
,在这个类中,我将使用
@PersistenceContext
来获取当前的
EntityManager

当我没有类时,如何获取
EntityManager
?有可能吗

@Repository
public interface UserRepository extends JpaRepository<User, UUID> {

    final QUser qUser = QUser.user;

    // How do I get the entityManager since this is a interface, i cannot have any variables?
    //@PersistenceContext
    //EntityManager entityManager;

    public default Optional<User> findByLogin(String login) {
        JPAQuery query = new JPAQuery(entityManager);
        User user = query
                .from(qUser)
                .where(
                        qUser.deleter.isNull(),
                        qUser.locked.isFalse(),
                        qUser.login.equalsIgnoreCase(login)
                )
                .singleResult(qUser);

        return Optional.ofNullable(user);
    }
}
@存储库
公共接口用户存储库扩展了JpaRepository{
最终QUser QUser=QUser.user;
//如何获取entityManager因为这是一个接口,我不能有任何变量?
//@持久上下文
//实体管理器实体管理器;
公共默认可选findByLogin(字符串登录){
JPAQuery query=新的JPAQuery(entityManager);
用户=查询
.来自(库塞尔)
.在哪里(
qUser.deleter.isNull(),
qUser.locked.isFalse(),
qUser.login.equalsIgnoreCase(登录)
)
.singleResult(qUser);
返回可选。不可用(用户);
}
}

您无法在界面中获得
EntityManager
,尽管您可以通过查找来解决此问题

但你为什么要这么做?SpringDataJPA已经支持
可选的
返回类型,因此您不需要实现它。Spring数据将为您提供这些功能

public interface UserRepository extends JpaRepository<User, UUID> {

    Optional<User> findByLoginIgnoreCase(String login) {
}
public interface UserRepository扩展了JpaRepository{
可选findByLoginIgnoreCase(字符串登录){
}
上面的代码应该是您所需要的全部。如果需要,您甚至可以使用
@query
指定查询


可以找到一个示例。

我最后做的是创建一个具有getEntityManager()的存储库库

但是让基类使用spring boot并不是一件简单的事情

// DomainRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

import javax.persistence.EntityManager;
import java.io.Serializable;

@NoRepositoryBean
public interface DomainRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {

    EntityManager getEntityManager();

}
然后将UserRepository更改为使用它

@Repository
public interface UserRepository extends DomainRepository<User, UUID> {
    public default Optional<User> findByLogin(String login) {
        JPAQuery query = new JPAQuery(getEntityManager());
        ...
    }
}
@存储库
公共接口UserRepository扩展了DomainRepository{
公共默认可选findByLogin(字符串登录){
JPAQuery query=newjpaquery(getEntityManager());
...
}
}

默认方法只能用于将调用委托给其他存储库方法。默认方法(根据定义)不能访问实例的任何状态(因为接口没有)。它们只能委托给其他接口方法或调用其他类的静态方法

实际上,使用中所述的自定义实现是正确的方法。以下是供参考的简短版本(以防其他人也想知道):

/**
*要手动实现的方法的接口。
*/
接口UserRepositoryCustom{
可选findByLogin(字符串登录);
}
/**
*这些方法的具体实现。
*/
类UserRepositoryImpl扩展QueryDslRepositorySupport实现UserRepositoryCustom{
私有静态最终QUser=QUser.USER;
@凌驾
公共可选findByLogin(字符串登录){
返回可选的.ofNullable(
来自(用户)。
在哪里(
USER.deleter.isNull(),
USER.locked.isFalse(),
USER.login.equalsIgnoreCase(login))。
单一结果(用户));
}
}
/**
*主存储库接口扩展了自定义存储库接口,以便
*实现的方法被“拉”到API中。
*/
公共接口UserRepository扩展了UserRepositoryCustom,
积垢沉积{…}
请注意,命名约定在这里很重要(但可以根据需要进行自定义)。通过扩展
QueryDslRepositorySupport
您可以从(…)方法访问
,这样您就不必自己与
EntityManager进行交互


或者,您可以让
UserRepository
实现
querydsldpredicateexecutor
并从存储库外部提交谓词,但这会让您的客户端需要使用Querydsl(这可能是不需要的)另外,您不会得到
可选的
包装类型OOTB。

我知道这一点。上面只是一个示例,在现实生活中,逻辑更高级,不能用@Query或表达式方法表示。在这种情况下,您可以始终使用规范。但是注入
EntityManager
将无法工作,您需要存储在单例中引用,并使用静态访问器方法。虽然我可能只是在自定义存储库中实现它(感觉不那么神奇)。您将默认方法用于它们不适用的用例。为了让找到此答案的其他人明白这一点,我否决了它。默认方法必须(不能)将对象的任何状态引用为接口没有状态。因此,它们的实现需要仅由对其他接口方法或外部静态方法的方法调用组成。以下是有关如何将自定义行为添加到单个存储库的文档:。或者,请查看
querydsldpredicateexecutor
。我不同意you,我的默认方法只是表达查询的另一种方式。但是我可以简单地实现QueryDslRepositorySupport以从
方法获得
,这样我就不必创建自定义工厂。你能解释一下为什么你认为使用默认方法不好吗?对存储库进行自定义实现非常简单y是在java 8之前实现这一点的唯一方法,现在我们有了默认方法,我认为我们应该使用它们。这并不是说它们不好。默认方法只是没有设计成做你想让它们做的事情,并且有局限性。例如,不能访问对象状态,这就是你想要的
// DomainRepositoryFactoryBean.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

public class DomainRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {

    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new RepositoryBaseFactory(entityManager);
    }

    private static class RepositoryBaseFactory<T, I extends Serializable> extends JpaRepositoryFactory {

        private EntityManager entityManager;

        public RepositoryBaseFactory(EntityManager entityManager) {
            super(entityManager);

            this.entityManager = entityManager;
        }

        protected Object getTargetRepository(RepositoryMetadata metadata) {

            return new DomainRepositoryImpl<T, I>((Class<T>) metadata.getDomainType(), entityManager);
        }

        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {

            // The RepositoryMetadata can be safely ignored, it is used by the JpaRepositoryFactory
            //to check for QueryDslJpaRepository's which is out of scope.
            return DomainRepository.class;
        }
    }
}
// DomainConfig.java
@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = DomainRepositoryFactoryBean.class, basePackages = {"com.mysite.domain"})
@EnableTransactionManagement
public class DomainConfig {
}
@Repository
public interface UserRepository extends DomainRepository<User, UUID> {
    public default Optional<User> findByLogin(String login) {
        JPAQuery query = new JPAQuery(getEntityManager());
        ...
    }
}
/**
 * Interface for methods you want to implement manually.
 */
interface UserRepositoryCustom {
  Optional<User> findByLogin(String login);
}

/**
 * Implementation of exactly these methods.
 */
class UserRepositoryImpl extends QueryDslRepositorySupport implements UserRepositoryCustom {

  private static final QUser USER = QUser.user;

  @Override
  public Optional<User> findByLogin(String login) {

    return Optional.ofNullable(
      from(USER).
      where(
        USER.deleter.isNull(),
        USER.locked.isFalse(), 
        USER.login.equalsIgnoreCase(login)).
      singleResult(USER));
  }
}

/**
 * The main repository interface extending the custom one so that the manually
 * implemented methods get "pulled" into the API.
 */
public interface UserRepository extends UserRepositoryCustom, 
  CrudRepository<User, Long> { … }