In a new project we would like to use Spring Data JPA and define interfaces for all JPA entities, e.g. like this:
public interface Person extends Serializable {
void setId(Long id);
Long getId();
void setLastName(String lastName);
String getLastName();
void setFirstName(String firstName);
String getFirstName();
// ...
}
@Entity
@Table(name = "t_persons")
public class PersonEntity implements Person {
private static final long serialVersionUID = 1L;
@Id
@Column
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String firstName;
@Column
private String lastName;
// ...
}
However, when declaring a Spring Data repository based on the interface like
public interface PersonRepository extends JpaRepository<Person, Long> {
}
The Spring context fails to initialize with an exception whose cause is
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'personRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not an managed type: interface com.example.Person
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:917)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:860)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:775)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:489)
... 24 more
Caused by: java.lang.IllegalArgumentException: Not an managed type: interface com.example.Person
at org.hibernate.ejb.metamodel.MetamodelImpl.managedType(MetamodelImpl.java:171)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:70)
at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:65)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:146)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:84)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:67)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:150)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:224)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:210)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:84)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1572)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1510)
... 34 more
I haven't found any example of Repository relying on the interface instead of the concrete type so is this possible at all? And if yes, how?
It seems that if we cannot use the interface to declare the repositories, then it will be very difficult to use those interfaces at all since we will end up with explicit casts everywhere in our services, and even unchecked casts as soon as we deal with generics (List
, Iterable
…).
I had the same problem and solved it using @NoRepositoryBean
on the repository interface that uses an interface and not a concrete class in that way (thanks to that blog post):
import org.springframework.data.repository.NoRepositoryBean;
@NoRepositoryBean
public interface PersonRepository<P extends Person> extends JpaRepository<P, Long> {
// code all generic methods using fields in Person interface
}
And then, use a concrete repository that extends the other:
public interface PersonEntityRepository extends PersonRepository<PersonEntity> {
// code all specific methods that use fields in PersonEntity class
}
This annotation is at least present in spring-data-commons-2.1.9.RELEASE.jar
.
Here is a solution to your problem. I don't know why Spring guys decided to base their repositories on concrete classes. But at least they made it possible to change that.
repositoryFactoryBeanClass
in EnableJpaRepositories
e.g. something like that:import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
/**
* @author goraczka
*/
@EnableJpaRepositories(
repositoryFactoryBeanClass = InterfaceBasedJpaRepositoryFactoryBean.class
)
public class DatabaseConfig {
}
InterfaceBasedJpaRepositoryFactoryBean
. It is a Spring hook to enable creating a custom factory for repository beans.import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import javax.persistence.EntityManager;
/**
* @author goraczka
*/
public class InterfaceBasedJpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
extends JpaRepositoryFactoryBean<T, S, ID> {
public InterfaceBasedJpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new InterfaceBasedJpaRepositoryFactory(entityManager);
}
}
EntityManager
.import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.util.Assert;
import javax.persistence.EntityManager;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author goraczka
*/
public class InterfaceBasedJpaRepositoryFactory extends JpaRepositoryFactory {
private final Map<? extends Class<?>, ? extends Class<?>> interfaceToEntityClassMap;
private final EntityManager entityManager;
public InterfaceBasedJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
interfaceToEntityClassMap = entityManager
.getMetamodel()
.getEntities()
.stream()
.flatMap(et -> Arrays.stream(et.getJavaType().getInterfaces())
.map(it -> new AbstractMap.SimpleImmutableEntry<>(it, et.getJavaType())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (possibleDuplicateInterface, v) -> v));
}
@Override
@SuppressWarnings("unchecked")
public <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
Assert.isTrue(domainClass.isInterface(), "You are using interface based jpa repository support. " +
"The entity type used in DAO should be an interface");
Class<T> domainInterface = domainClass;
Class<?> entityClass = interfaceToEntityClassMap.get(domainInterface);
Assert.notNull(entityClass, "Entity class for a interface" + domainInterface + " not found!");
return (JpaEntityInformation<T, ID>) JpaEntityInformationSupport.getEntityInformation(entityClass, entityManager);
}
}
Don't bash me for any mistakes. I did it in 10 minutes after reading this question and realizing that there is no solution for it yet. And I really needed one. I did not create any tests for it yet but seems to work. Improvements are welcomed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With