Должен ли я использовать методы Java 8 по умолчанию для внедренных вручную методов Spring Data repository?
При использовании нового выпуска Spring Data Evans приятно использовать некоторые из приятных вещей, которые поставляются с java 8. Одна из них - это реализация по умолчанию в интерфейсах. В репозитории ниже используется QueryDSL, чтобы сделать запрос безопасным.
Моя проблема в том, что раньше, когда я писал это, я использовал шаблон отдельного интерфейса UserRepositoryCustom
для findByLogin
, а затем еще один класс 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);
}
}
Ответы
Ответ 1
Способы по умолчанию должны использоваться только для делегирования вызовов другим методам репозитория. Методы по умолчанию - по определению - не могут получить доступ к какому-либо состоянию экземпляра (поскольку интерфейс не имеет). Они могут делегировать другим методам интерфейса или вызывать статические из других классов.
Собственно, использование пользовательской реализации, описанной в справочной документации является правильным подходом. Вот короткая версия для справки (в случае, если другие задаются вопросом тоже):
/**
* 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> { … }
Имейте в виду, что соглашения об именах важны здесь (но при необходимости могут быть настроены). Расширяя QueryDslRepositorySupport
, вы получаете доступ к методу from(…)
, чтобы вам не приходилось взаимодействовать с EntityManager
самостоятельно.
В качестве альтернативы вы можете позволить UserRepository
реализовать QueryDslPredicateExecutor
и передать предикаты извне репозитория, но это позволит вам в конечном итоге с клиентами, которые должны работать с Querydsl (что может быть нежелательно), плюс вы не можете получить Optional
тип обертки OOTB.
Ответ 2
Вы не получаете EntityManager
в интерфейсе, хотя вы можете обойти его, выполнив поиск.
Но почему вы это делаете? Spring Данные JPA уже поддерживает возвращаемый тип Optional
, поэтому вам не нужно его реализовывать. Spring Данные сделают это для вас.
public interface UserRepository extends JpaRepository<User, UUID> {
Optional<User> findByLoginIgnoreCase(String login) {
}
Код выше должен быть всем, что вам нужно. Вы даже можете указать запрос с помощью @Query
, если он вам понадобится.
Образец можно найти здесь.
Ответ 3
То, что я закончил, - это создание базы репозитория, которая имеет 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();
}
Тогда реализация
// 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;
}
}
Но тогда spring должен знать, как создавать репозитории домена, поэтому нам нужно создать 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 which is out of scope.
return DomainRepository.class;
}
}
}
И затем сообщить spring boot использовать этот factory при создании репозиториев
// DomainConfig.java
@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = DomainRepositoryFactoryBean.class, basePackages = {"com.mysite.domain"})
@EnableTransactionManagement
public class DomainConfig {
}
а затем измените UserRepository, чтобы использовать его.
@Repository
public interface UserRepository extends DomainRepository<User, UUID> {
public default Optional<User> findByLogin(String login) {
JPAQuery query = new JPAQuery(getEntityManager());
...
}
}