Let's say I have some services fetching type-specific subclasses of an entity.
#Repository
interface MyFooWithIntRepo extends JpaRepository<MyFooWithInt, Long> {}
#Repository
interface MyFooWithFloatRepo extends JpaRepository<MyFooWithFloat, Long> {}
where
public interface MyFoo(){
long getId();
String getName();
String getCode();
...
}
#MappedSuperclass
public abstract class AbstractMyFoo implements MyFoo{
#Column(name = "code")
String code;
... getters, setters, interface implementation ...
#Entity
#Table(name="int_myfoo")
public class MyFooWithInt implements MyFoo
private int foo;
... getters, setters, interface implementation ...
}
#Entity
#Table(name = "float_myfoo")
public class MyFooWithFloat implements MyFoo
private float foo;
... getters, setters, interface implementation ...
}
Database constraints guarantee unique code across all MyFoos.
(The IDs, however, are unique only within the corresponding typed table.)
I now want to add a findByCode method to all repos, s.t. I can do
#Service
public class MyFooService{
private MyFooWithIntRepo intRepo;
private MyFooWithFloatRepo floatRepo;
[...]
/*constructor-inject all the different repos*/
Optional<? extends MyFoo> findByCode(String code){
return combinedRepos()
.map(repo -> repo.findByCode(code)
.findFirst();
}
private Stream<JpaRepository<? extends MyFoo, Long>> combinedRepos(){
return Stream.of(
intRepo,
floatRepo,
...
)
}
}
I could, of course, create a new interface,
public interface MyFooRepo<T extends MyFoo> {
Optional<T> findByCode(String code);
}
and then have
#Repository
public interface MyFooWithIntRepo extends JpaRepository<MyFooWithInt, Long>, MyFooRepo<MyFooWithInt>
The problem with that is that when you cast to MyFooRepo, this way, you hide all the JpaRepository methods.
So either I'd have to add them all to the MyFooRepo interface, which is a hassle (especially because you'd need to rename them to avoid ambiguity),
or I'd have to insert casts all over the place -- which also is a hassle.
I'd much prefer if I could do something like
public interface MyFooRepo<T extends MyFoo> extends JpaRepository<T, Long> {
Optional<T> findByCode(String code);
}
And then
#Repository
public interface MyFooWithIntRepo extends MyFooRepo<MyFooWithInt> {}
Spring JPA doesn't seem to like that, though ...
[...]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myFooRepo' defined in ...
Caused by: java.lang.IllegalArgumentException: No [ManagedType] was found for the key class [ch.cypherk.blah.MyFoo] in the Metamodel - please verify that the [Managed] class was referenced in persistence.xml using a specific <class>ch.cypherk.blah.MyFoo</class> property or a global <exclude-unlisted-classes>false</exclude-unlisted-classes> element.
But I don't WANT that class to be turned into a functional JpaRepository, I just want it to be implemented in the MyFooWithIntRepo and the MyFooWithFloatRepo classes.
Any way to achieve that?
In my opinion is better than each entity has his own repository. Otherway, if you has a abstract class AbstractMyFoo you could implement a repository with the necessary methods (AbstractMyFooRepo)
Then, when you create classes childs of AbstractMyFoo, you can create a new repo chield of AbstractMyFooRepo.
Related
I have a class like this:
#MappedSuperclass
public abstract class Foo implements Serializable, Cloneable {
private String id;
private Boolean deleted = Boolean.FALSE;
private Boolean active = Boolean.TRUE;
...
}
and a repository like this:
#NoRepositoryBean
public interface FooRepository<F extends Foo> extends JpaRepository<F, String> {
}
now the problem:
when I use class level type parameters(generics) like above, intellij does not make or complete spring-data`s named query but if I remove the generics like below then I can write named queries in the repository interface
#NoRepositoryBean
public interface FooRepository extends JpaRepository<Foo, String> {
Integer countAllByActive(boolean flag);
}
anybody knows why and is there any solution?
I'm developing a multi-module CMS application following Domain-Driven Design principles. I'm trying to figure out how to implement Generic Repository, thus avoiding a lot of boiler-plate code.
The idea is to have a "two-way" mapping strategy (model to entity and vice versa) and Generic Repository implemented in the Persistence module. Further, an interface in the Domain module would act as a contract between Domain and Persistence, so I can use it for later injection in the other layers.
How can I make this interface generic?
To be specific, the problem here is the mapping. Since I'm using a "two-way" mapping strategy, the Domain module has no idea about DB specific entities.
Is there a way to map generic type models between layers? Or use some other mapping strategy while keeping the layers loosely coupled?
Here is a code example to clarify what I'm trying to achieve.
This would be the code example for Generic Repository:
#MappedSuperclass
public abstract class AbstractJpaMappedType {
…
String attribute
}
#Entity
public class ConcreteJpaType extends AbstractJpaMappedType { … }
#NoRepositoryBean
public interface JpaMappedTypeRepository<T extends AbstractJpaMappedType>
extends Repository<T, Long> {
#Query("select t from #{#entityName} t where t.attribute = ?1")
List<T> findAllByAttribute(String attribute);
}
public interface ConcreteRepository
extends JpaMappedTypeRepository<ConcreteType> { … }
Further, I want to make my own Custom Repository to be able to do some mapping of model to entity and vice versa, so I wouldn't have JPA specific annotation in my domain classes, thus making it loosely coupled. I want this Custom Repository to implement an interface from Domain module, allowing me to inject it later in the Services layer.
public class CustomRepositoryImpl implements CustomRepository {
public final JpaMappedTypeRepository<T> repository;
...
}
How can I make this class and this interface generic so that I would be able to do mapping between model and entity, since Domain layer has no information about entity classes?
I figured it out eventually.
The problem, as it was stated in the question, was the mapping between layers. I created a mapping interface to declare mapping methods. I used #ObjectFactory annotation from MapStruct to deal with generic mapping (look here):
public interface EntityMapper<M, E> {
M toModel(E entity);
List<M> toModelList(List<E> entities);
E toEntity(M model);
List<E> toEntityList(List<M> models);
// default object factory methods
}
Then I proceeded with creating a mapper for each of the child classes and extending it with the EntityMapper interface with concrete types that I want to map.
#Mapper(componentModel="spring")
public interface ConcreteEntityMapper extends EntityMapper<ConcreteModelType, ConcreteJpaType> {
}
I created an abstract class where I injected the JPA repository and the mapper, and also implemented common methods.
abstract class CustomRepositoryImpl<T extends AbstractModelMappedType, E extends AbstractJpaMappedType> {
private final JpaMappedTypeRepository<E> repository;
private final EntityMapper<M, E> mapper;
//... common methods for mapping and querying repositories.
}
Then I extended a ConcreteTypeRepositoryImpl with this abstract class, and implemented a generic interface, which I can later use as a reference in other layers.
public interface CustomRepository<M> {
M saveOrUpdate(M model);
Optional<M> getById(Long id);
List<M> getByName(String name);
List<M> getAll();
void delete(Long id);
}
#Component
public class ConcreteTypeRepositoryImpl extends CustomRepositoryImpl<ConcreteModelType,ConcreteJpaType> implements CustomRepository<ConcreteModelType> {
public ConcreteTypeRepositoryImpl(JpaMappedTypeRepository<ConcreteJpaType> repository,
EntityMapper<ConcreteModelType, ConcreteJpaType> mapper) {
super(repository, mapper);
}
}
And that would be it. Now I can inject CustomRepository into other layers and hit desired repository.
My problem is, that spring data couchbase doesn't search for subclasses of searched class. For example:
Model:
#Document
class A {
#Id
String id
}
#Document
class B extends A {}
And repository:
public interface ARepository extends PagingAndSortingRepository<A, String>{
Page<A> findAll(Pageable pageable);
}
Spring data couchbase generate query, that has in where condition
_class="com.example.model.A"
But I want in this query search B documents too. Is some way, how can I do this? When I write own query, I must defining order, limit and offset in query and Pageable is not used. But I want use Pageable.
Consider generic interface based on inheritance.
Firstly create super class:
#Inheritance
public abstract class SuperClass{
#Id
private int id;
}
Then create your subclasses:
public class A extends SuperClass { /* ... */ }
public class B extends SuperClass { /* ... */ }
Create base repository:
#NoRepositoryBean
public interface SuperClassBaseRepository<T extends SuperClass>
extends PagingAndSortingRepository<T, Integer> {
public T findAll();
}
And then create SuperClass repository basing on base repo:
#Transactional
public interface SuperClassRepository extends SuperClassBaseRepository<SuperClass> { /* ... */ }
#Transactional
public interface ARepository extends SuperClassBaseRepository<A> { /* ... */ }
#Transactional
public interface BRepository extends SuperClassBaseRepository<B> { /* ... */ }
SuperClassRepository findAll() will search all A and B classes
We managed to make this work on Spring Data Couchbase 3.2.12. Here's what we did:
We figured out that mappers for each type were only being created if a repository existed for that type, so, besides our superclass repository...
public interface ARepository extends PagingAndSortingRepository<A, String> {
Page<A> findAll(Pageable pageable);
}
We created an empty repository for each of the subtypes such as:
public interface BRepository extends PagingAndSortingRepository<B, String>{
// No methods
}
The presence of this second repo warranted the existence of an appropriate mapper for B, so when findAll (or other methods) are invoked in ARepository, the mapper for each subclass is present. Having done this, we were able to get a list of A that were actually B instances.
Hope this helps and nobody has to lose any more time on this. :)
I wanted to know if i could use the same spring crudRepository and Services interface with multiple classes by attributing a generic type to both of them in order to avoid rewriting the same thing over and over.
i'll try to explain this idea better with an example;
imagine if we had two different classes "Dog" and "Cat"
public class Dog extends Serializable{ //attributes here... }
public class Cat extends Serializable{ //attributes here... }
the service interface will more likely look like this :
public interface Services<T> {
List<T> getAllRecords();
T getRecordById(int id);
T insertRecord(T animal);
// etc
}
and the repository will be like this :
public interface GenericRepository<T> extends CrudRepository<T,Serializable>{ //....}
then we'll be able to implement the services such as this :
#Service
public class CatServiceImpl implements Services<Cat>
{
#Autowired
GenericRepository<Cat> repositoryCat;
//...
}
#Service
public class DogServiceImpl implements Services<Dog>
{
#Autowired
GenericRepository<Dog> repositoryDog;
//...
}
and so on..
the problem is in the controller how can the #AutoWired annotation differentiate between the implementations?
Any suggestions?
I've got a JPA #MappedSuperClass and an #Entity extending it:
#MappedSuperclass
public class BaseClass {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private Boolean active;
//getters & setters
}
#Entity
public class Worker extends BaseClass{
#Column
private String name;
//getters & setters
}
The active field of the base class is a flag for the children entities. Only the active ones should be loaded in the application. Then I've written a generic Spring Data Proxy interface:
public interface Dao<T extends BaseClass, E extends Serializable> extends
CrudRepository<T, E> {
Iterable<T> findByActive(Boolean active);
}
And this one is the interface that should be for Worker data access, properly extending the previous one:
#Transactional
public interface WorkerDao extends Dao<Worker, Long>{}
Well, now in my logic layer I've implemented an abstract class which will wrap the common code for CRUD operations over my entities. I'll have a service for each of them, but I want just to inherit from the abstract one. I want to wire the specific repository for each of the services and provide it to the superclass using an abstract method. That's how my superclass is implemented:
public abstract class GenericService<E extends BaseClass>{
public abstract Dao<E, Long> getDao();
//Here I've got some common operations for managing
//all my application classes, including Worker
}
The problem is that the getDao() method uses the E class parameter, which is guaranteed only to be a child of BaseClass and not a javax.persistence.Entity. When I try to access the DAO from my custom service implementation I get this error:
Caused by: java.lang.IllegalArgumentException: Could not create query metamodel for method public abstract java.lang.Iterable com.mycompany.model.daos.interfaces.Dao.findByActive(java.lang.Boolean)!
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:93)
Caused by: java.lang.IllegalArgumentException: Not an entity: class com.mycompany.model.BaseClass
at org.hibernate.jpa.internal.metamodel.MetamodelImpl.entity(MetamodelImpl.java:203)
Which makes sense, because E is defined as a child of BaseClass. The compiler allows me to write this too:
public abstract class GenericService<E extends BaseClass && Entity>
However I get an error in the child Service that says Worker class is not compatible with the signature for E. Does anybody know how to solve this?
It's just a matter of annotating the abstract Repository as #NoRepositoryBean:
#NoRepositoryBean
public interface Dao<T extends BaseClass, E extends Serializable> extends
CrudRepository<T, E> {
Iterable<T> findByActive(Boolean active);
}
This way Spring relies on the underlying repository implementation to execute the findByActive method.
Regarding to the annotation type restriction issue, it's not possible to declare an annotation restricted type. See the referenced answers below.
See also:
Generic Spring Data JPA repository implementation to load data by class type
Annotations: restrict reference to classes with a annotation