Using generics in Spring Data JPA repositories
First of all, I know we're raising the bar here quite a bit but this is already tremendously less code than you had to write without the help of Spring Data JPA.
Second, I think you don't need the service class in the first place, if all you do is forward a call to the repository. We recommend using services in front of the repositories if you have business logic that needs orchestration of different repositories within a transaction or has other business logic to encapsulate.
Generally speaking, you can of course do something like this:
interface ProductRepository<T extends Product> extends CrudRepository<T, Long> {
@Query("select p from #{#entityName} p where ?1 member of p.categories")
Iterable<T> findByCategory(String category);
Iterable<T> findByName(String name);
}
This will allow you to use the repository on the client side like this:
class MyClient {
@Autowired
public MyClient(ProductRepository<Car> carRepository,
ProductRepository<Wine> wineRepository) { … }
}
and it will work as expected. However there are a few things to notice:
This only works if the domain classes use single table inheritance. The only information about the domain class we can get at bootstrap time is that it will be Product
objects. So for methods like findAll()
and even findByName(…)
the relevant queries will start with select p from Product p where…
. This is due to the fact that the reflection lookup will never ever be able to produce Wine
or Car
unless you create a dedicated repository interface for it to capture the concrete type information.
Generally speaking, we recommend creating repository interfaces per aggregate root. This means you don't have a repo for every domain class per se. Even more important, a 1:1 abstraction of a service over a repository is completely missing the point as well. If you build services, you don't build one for every repository (a monkey could do that, and we're no monkeys, are we? ;). A service is exposing a higher level API, is much more use-case drive and usually orchestrates calls to multiple repositories.
Also, if you build services on top of repositories, you usually want to enforce the clients to use the service instead of the repository (a classical example here is that a service for user management also triggers password generation and encryption, so that by no means it would be a good idea to let developers use the repository directly as they'd effectively work around the encryption). So you usually want to be selective about who can persist which domain objects to not create dependencies all over the place.
Summary
Yes, you can build generic repositories and use them with multiple domain types but there are quite strict technical limitations. Still, from an architectural point of view, the scenario you describe above shouldn't even pop up as this means you're facing a design smell anyway.
Spring JPA Data Repository: How to create a generic repository?
I see a lot of issues with what you're trying to do here. Remember, when Spring initializes it's going to look for a bean of the type you've declared to inject. To do this, it needs to be a resolvable type, see https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/ResolvableType.html.
The spring data jpa repositories are already generic, you can extend this
public interface MyRepository<T> extends CrudRepository<T, Long>{}
public interface TheirRepository<T> extends MyRepository<T>{}
public interface AnotherRepository<T> extends TheirRepository<T>{}
as far as you want, but ultimately if you want to use it you'll have to declare one that conforms to your entity. So, no matter how you dice this, you're going to end up with having to declare something like
public interface MyGenericRepository<MyActualEntity>{}
To get away from this, I encourage you to go ahead and create all your repositories as you normally would, then make a service class that generically saves, updates, deletes the entities by looking up the repository for the type that your operating on. Something like this:
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.support.Repositories;
import org.springframework.stereotype.Service;
import org.springframework.web.context.WebApplicationContext;
@Service
public class GenericPersistenceService {
private final WebApplicationContext applicationContext;
private Repositories repositories;
public GenericPersistenceService(WebApplicationContext applicationContext) {
repositories = new Repositories(applicationContext);
this.applicationContext = applicationContext;
}
public <T> T save(T object) {
Object repository = repositories.getRepositoryFor(object.getClass()).orElseThrow(
() -> new IllegalStateException(
"Can't find repository for entity of type " + object.getClass()));
CrudRepository<T, Long> crudRepository = (CrudRepository<T, Long>) repository;
return crudRepository.save(object);
}
}
The repositories are populated when the service is created, then when you pass an entity to the save method, it looks up the repository for the given entity and performs the save operation.
How do we create a generic repository in Spring Data JPA?
I am afraid not. If you already tried you would have noticed that your second repository extending CrudRepository<T, Long>
fails with something like:
Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class java.lang.Object.
because with generic T
java can tell only that it is an Object
which is not an entity. And because of that you need to add annotation @NoRepositoryBean
which then only allows you to extend it with a real entity type instead of generic T
.
It might be possible programmatically but most probably would be a lot more painful than just to create repo interfaces.
If you have entity inheritance tree like A->B->C
you are able to create a repo for A
that then handles also B
& C
what comes to properties inherited from A
. So you could issue findAll()
and it would return you all A
s, B
s & C
s but as a list of A
s and you would have to check the actual type of each item separately.
Usually when there is no need to any special treatment for entities and you do not want to write those repository "stubs" you can just use EntityManager
directly. You can implement your own - generic repository like - @Service
class that has @Autowired
entity manager and invoke its find(..)
, persist(..)
and other methods.
Related Topics
Data Access Object (Dao) in Java
Is a Java String Really Immutable
What Is the List of Valid @Suppresswarnings Warning Names in Java
Jax-Rs/Jersey How to Customize Error Handling
Specifying Java Version in Maven - Differences Between Properties and Compiler Plugin
Difference Between Validate(), Revalidate() and Invalidate() in Swing Gui
Jtable How to Refresh Table Model After Insert Delete or Update the Data
Compile Code Fully in Memory with Javax.Tools.Javacompiler
Field Required a Bean of Type That Could Not Be Found.' Error Spring Restful API Using Mongodb
How to Add Hyperlink in Jlabel
Valid Characters in a Java Class Name
Java:Cannot Format Given Object as a Date
Io Error: the Network Adapter Could Not Establish the Connection
Alert Handling in Selenium Webdriver (Selenium 2) with Java
Do JSON Keys Need to Be Unique
Reversing a Linked List in Java, Recursively