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

前端 未结 3 1390
感情败类
感情败类 2021-02-12 19:00

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

相关标签:
3条回答
  • 2021-02-12 19:48

    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 documentation is 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 QueryDslRepositorySupport you get access to the from(…) method so that you don't have to interact with the EntityManager yourself.

    Alternatively you can let UserRepository implement QueryDslPredicateExecutor and 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 Optional wrapper type OOTB.

    0 讨论(0)
  • 2021-02-12 19:50

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

    But it isn't all straight forward to get the base class working with 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.

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

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

    and then change the UserRepository to use it instead.

    @Repository
    public interface UserRepository extends DomainRepository<User, UUID> {
        public default Optional<User> findByLogin(String login) {
            JPAQuery query = new JPAQuery(getEntityManager());
            ...
        }
    }
    
    0 讨论(0)
  • 2021-02-12 19:51

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

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

    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 @Query if you would need it.

    A Sample can be found here.

    0 讨论(0)
提交回复
热议问题