21

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.

My problem is that before when I wrote this I used the pattern of a separate UserRepositoryCustom interface for the findByLogin and then another class UserRepositoryImpl and in that class I would have the @PersistenceContext to get the current EntityManager.

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

@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);
    }
}
Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
Leon Radley
  • 7,596
  • 5
  • 35
  • 54

3 Answers3

20

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.

Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
  • What if I want have interface default method, which is not trying to access any state of the instance. For example, I want to put into my repository interface an create() method, which creates an instance of entity and initializes it somehow. I have tried to do that, but I have received "PropertyReferenceException: No property create found for type Entity!". Is it not possible to have interface default methods at all? – Victor Dombrovsky Sep 23 '15 at 14:29
  • Oliver I am very interested in your opinion in a question related to Victor`s. Assume this scenario : we have a method that retrieves a list of entities and another one that uses the first but applies a filter and a map on the stream from the first method.(no state is accessed - there is none) Would a default method be the answer here or a custom implementation ? – mvlupan Mar 03 '16 at 11:31
  • 1
    This looks good, but having to have 3 classes for each DAO seems to be a little too tiring. – Sanghyun Lee May 20 '16 at 07:44
  • I appreciate your opinion but there currently is no other way and I guess we can't change the rules for default methods ;). You can have all of that in a single source file though. As it's only type declarations actually, I'd also argue that it's basically in the ballpark of tow additional methods, not types. On top of that, in a real world application you usually have quite a few query methods in the repo so that the additional code required is negligible. – Oliver Drotbohm May 24 '16 at 09:04
3

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.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • I'm aware of this. The above was just a sample, in real life the logic is more advanced and cannot be represented with a @Query or a expression method. – Leon Radley Oct 24 '14 at 08:51
  • 1
    You can always use a Specification in that case. But injecting a `EntityManager` will not work, you would need to store the reference in a singleton and use a static accessor method. Although I would probably just implement it in a custom repository (feels like less magic). – M. Deinum Oct 24 '14 at 08:54
0

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());
        ...
    }
}
Leon Radley
  • 7,596
  • 5
  • 35
  • 54
  • Your using the default methods for a use case they weren't intended for. To make this clear to everyone else finding this answer I down-voted it. Default methods must (cannot) refer to any state of the object as interfaces don't have state. So their implementations need to be composed solely of method calls to other interface methods or external static ones. Here's the documentation for how to add custom behavior to a single repository: http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.single-repository-behaviour. Alternatively look at `QueryDslPredicateExecutor`. – Oliver Drotbohm Oct 25 '14 at 14:57
  • 1
    I don't agree with you, my default method is just another way of expressing a Query. But I could simply implement the QueryDslRepositorySupport to get the `from` method and by doing so I wouldn't have to create my custom factory. Can you please explain why you think that using default methods are bad? doing a custom implementation of a repository was simply the only way to do it before java 8, and now that we have default methods we should use them I think. – Leon Radley Oct 26 '14 at 12:39
  • 1
    It's not that they're bad. Default methods were just not designed to do what you want them to do and have limitations. Such as no access to object state, which is what you need if you want to access e.g. an `EntityManager`. Also, exposing an `EM` at the repository interface totally subverts its (the repositories) purpose abstracting from persistence technology. You're trying to squeeze default methods into something they were not made for and actually end up with a more complex and more suboptimal solution than the simple and documented way that has been in place for ages. – Oliver Drotbohm Oct 27 '14 at 12:29
  • 1
    @OliverGierke Thanks for the explanation, it's more clear to me now – Leon Radley Oct 27 '14 at 15:29
  • 1
    That's great to hear. No offense really. It's really good someone tried it and we now have a way to document why we think the other approach is superior. I guess you're not the only one trying this and I admit it's not obvious what the downsides are in the first place :). Kudos for trying to stretch the borders :). – Oliver Drotbohm Oct 28 '14 at 11:00
  • The critique against default methods I must criticize. Oliver, you said default methods "were just not designed to do what you want them to do and have limitations". This could be correct for the given context - I didn't read the posted answer - but I want to stress that "fragments" weren't designed in Java at all and trying to impose an opinion on what a default method is allowed or not allowed to do is just wrong lol. Spring's "fragments" is a bloaty hack compared to default methods which seems to be working just fine as long as one doesn't need to access state (tested in Spring Boot 2.5.6). – Martin Andersson Jan 18 '22 at 14:08