我应该使用 Java 8 默认方法来手动实现 Spring Data 存储库方法吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26543612/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 10:06:52  来源:igfitidea点击:

Should I use Java 8 default methods for manually implemented Spring Data repository methods?

javaspringspring-dataspring-data-jpa

提问by Leon Radley

When using the new Spring Data Evans release it's nice to be able to use some of the nice stuff that came with java 8. One of them is default implementations in interfaces. The repository below uses QueryDSL to make queries type safe.

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

My problem is that before when I wrote this I used the pattern of a separate UserRepositoryCustominterface for the findByLoginand then another class UserRepositoryImpland in that class I would have the @PersistenceContextto get the current EntityManager.

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

How do I get the EntityManagerwhen I don't have a class? Is it even possible?

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);
    }
}

采纳答案by Oliver Drotbohm

Default methods should only be used to delegate calls to other repository methods. Default methods - by definition - cannot access any state of an instance (as an interface has none). They only can delegate to other interface methods or call static ones of other classes.

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

Actually, using a custom implementation as described in the reference documentationis the right approach. Here's the short version for reference (in case others wonder, too):

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

/**
 * 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> { … }

Be aware that the naming conventions are important here (but can be customized if needed). By extending QueryDslRepositorySupportyou get access to the from(…)method so that you don't have to interact with the EntityManageryourself.

请注意,命名约定在这里很重要(但可以根据需要进行自定义)。通过扩展,QueryDslRepositorySupport您可以访问该from(…)方法,这样您就不必与EntityManager自己进行交互。

Alternatively you can let UserRepositoryimplement QueryDslPredicateExecutorand hand in the predicates from outside the repository but that'd let you end up with the clients needing to work with Querydsl (which might be unwanted) plus you don't get the Optionalwrapper type OOTB.

或者,您可以让UserRepository实现QueryDslPredicateExecutor并从存储库外部提交谓词,但这会让您最终需要使用 Querydsl(这可能是不需要的)的客户端,而且您不会获得Optional包装器类型 OOTB。

回答by M. Deinum

You don't get the EntityManagerin an interface, although you might be able to work around it by doing a lookup.

EntityManager虽然您可以通过查找来解决它,但您不会在界面中获取。

But why are you even doing this? Spring Data JPA already supports the Optionalreturn type so you don't need to implement it. Spring Data will do it for you.

但你为什么要这样做?Spring Data JPA 已经支持Optional返回类型,所以你不需要实现它。Spring Data 将为您完成。

public interface UserRepository extends JpaRepository<User, UUID> {

    Optional<User> findByLoginIgnoreCase(String login) {
}

The code above should be all you need. You could even specify a query with @Queryif you would need it.

上面的代码应该就是你所需要的。@Query如果需要,您甚至可以指定一个查询。

A Sample can be found here.

可以在此处找到示例。

回答by Leon Radley

What i ended up doing is creating a repository base which has a getEntityManager()

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

But it isn't all straight forward to get the base class working with spring boot

但是让基类与 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();

}

Then the implementation

然后执行

// DomainRepositoryImpl.java
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

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

public class DomainRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements DomainRepository<T, ID> {

    private EntityManager entityManager;

    public DomainRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }
}

But then spring needs to know how to create domain repositories so we need to create a factory.

但是 spring 需要知道如何创建域存储库,所以我们需要创建一个工厂。

// 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;
        }
    }
}

And then to tell spring boot to use this factory when creating repositories

然后告诉 spring boot 在创建存储库时使用这个工厂

// DomainConfig.java
@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = DomainRepositoryFactoryBean.class, basePackages = {"com.mysite.domain"})
@EnableTransactionManagement
public class DomainConfig {
}

and then change the UserRepository to use it instead.

然后更改 UserRepository 以使用它。

@Repository
public interface UserRepository extends DomainRepository<User, UUID> {
    public default Optional<User> findByLogin(String login) {
        JPAQuery query = new JPAQuery(getEntityManager());
        ...
    }
}