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
Related
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.
I have a question. I have these classes:
public interface CRUDService<MODEL extends BaseModel<ID>,ID extends Serializable>
{
List<MODEL> findAll();
MODEL findById(ID id);
// + delete, save & update methods
}
public abstract class AbstractCRUDService<MODEL extends BaseModel<ID>,ID extends Serializable> implements CRUDService<MODEL,ID>
{
//overriding the CRUDService interface methods here.
}
Is it better to extend each service from AbstractCRUDService like this:
public class DefaultProductService extends AbstractCRUDService<ProductModel,Long> implements ProductService
{ //some methods here}
or should I remove abstract from AbstractCRUDService and inject this service in the DefaultProductService ?
public class DefaultProductService implements ProductService {
#Autowired
private CRUDService<ProductModel,Long> crudService;
// override "ProductService" methods here.
}
It depends on your requirement.
If all Model Types, need the same CRUD implementation, you can go with your 2nd approach: composition.
However, if different Model objects require different CRUD implementations, the inheritance would fit better. For example, for all ProductModels Del(obj) will remove the object from the DB table, however, for all OrderModels Del(obj) doesn't remove the data, instead, it does something else, throw an exception, for example.
Yes, it is. You need to prefer composition over inheritance
here a really good post to read about it
composition over inheritance
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.
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. :)
Currently I'm working on a service interface which retrieves domain objects based on a primary key. However I get the feeling I'm not efficiently using generics.
Base domain objects look as follows:
public interface DomainObject<PK extends Serializable> extends Serializable {
PK getID();
}
My service interface looks as follows:
public interface LoadService<T extends DomainObject<PK>, PK extends Serializable> {
T load(PK ID);
}
This works, however I have to specify the PK type in the service generics, even though the PK type is already known inside T. Is there any way I can get around having to define my PK again in the LoadService interface? Something like:
LoadService<T extends DomainObject<? extends Serializable as PK> { ... }
Help will be greatly appreciated!
There is no way to avoid that because you use PK class at the 'LoadService'. I mean that you can define service like
interface LoadService<T extends DomainObject<?>> {
void store(T data);
}
However, that's not the option if you use PK class because compiler checks that PK type is compatible with the domain object type.
Another option is to remove type parameter from DomainObject, i.e. perform the following:
interface DomainObject extends Serializable {
Serializable getID();
}
Try using multiple bounds for the type parameter, if T both extends DomainObject and implements Serializable:
interface LoadService<T extends DomainObject<T> & Serializable> {
}