Using generics in Spring Data JPA repositories
I have a number of simple object types that need to be persisted to a database. I am using Spring JPA to manage this persistence. For each object type I need to build the following:
import org.springframework.data.jpa.repository.JpaRepository;
public interface FacilityRepository extends JpaRepository<Facility, Long> {
}
public interface FacilityService {
public Facility create(Facility facility);
}
@Service
public class FacilityServiceImpl implements FacilityService {
@Resource
private FacilityRepository countryRepository;
@Transactional
public Facility create(Facility facility) {
Facility created = facility;
return facilityRepository.save(created);
}
}
It occurred to me that it may be possible to replace the multiple classes for each object type with three generics based classes, thus saving a lot of boilerplate coding. I am not exactly sure how to go about it and in fact if it is a good idea?
java spring jpa spring-data spring-data-jpa
add a comment |
I have a number of simple object types that need to be persisted to a database. I am using Spring JPA to manage this persistence. For each object type I need to build the following:
import org.springframework.data.jpa.repository.JpaRepository;
public interface FacilityRepository extends JpaRepository<Facility, Long> {
}
public interface FacilityService {
public Facility create(Facility facility);
}
@Service
public class FacilityServiceImpl implements FacilityService {
@Resource
private FacilityRepository countryRepository;
@Transactional
public Facility create(Facility facility) {
Facility created = facility;
return facilityRepository.save(created);
}
}
It occurred to me that it may be possible to replace the multiple classes for each object type with three generics based classes, thus saving a lot of boilerplate coding. I am not exactly sure how to go about it and in fact if it is a good idea?
java spring jpa spring-data spring-data-jpa
add a comment |
I have a number of simple object types that need to be persisted to a database. I am using Spring JPA to manage this persistence. For each object type I need to build the following:
import org.springframework.data.jpa.repository.JpaRepository;
public interface FacilityRepository extends JpaRepository<Facility, Long> {
}
public interface FacilityService {
public Facility create(Facility facility);
}
@Service
public class FacilityServiceImpl implements FacilityService {
@Resource
private FacilityRepository countryRepository;
@Transactional
public Facility create(Facility facility) {
Facility created = facility;
return facilityRepository.save(created);
}
}
It occurred to me that it may be possible to replace the multiple classes for each object type with three generics based classes, thus saving a lot of boilerplate coding. I am not exactly sure how to go about it and in fact if it is a good idea?
java spring jpa spring-data spring-data-jpa
I have a number of simple object types that need to be persisted to a database. I am using Spring JPA to manage this persistence. For each object type I need to build the following:
import org.springframework.data.jpa.repository.JpaRepository;
public interface FacilityRepository extends JpaRepository<Facility, Long> {
}
public interface FacilityService {
public Facility create(Facility facility);
}
@Service
public class FacilityServiceImpl implements FacilityService {
@Resource
private FacilityRepository countryRepository;
@Transactional
public Facility create(Facility facility) {
Facility created = facility;
return facilityRepository.save(created);
}
}
It occurred to me that it may be possible to replace the multiple classes for each object type with three generics based classes, thus saving a lot of boilerplate coding. I am not exactly sure how to go about it and in fact if it is a good idea?
java spring jpa spring-data spring-data-jpa
java spring jpa spring-data spring-data-jpa
edited Sep 2 '15 at 12:34
Oliver Drotbohm
55.5k10170171
55.5k10170171
asked Oct 17 '13 at 3:20
skymanskyman
1,00521640
1,00521640
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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 should even pop up as this means you're facing a design smell anyway.
4
This is really useful information. It would be great to include this kind of higher-level design guidance in the actual reference materials if it's not already there. Like "best practices" callouts or something.
– Willie Wheeler
Oct 18 '13 at 6:43
1
Well, this is software architecture and design 1:1. If we included such stuff in the reference documentation, where should we stop then? ;)
– Oliver Drotbohm
Oct 18 '13 at 6:48
1
I'm facing this very scenario in the design of an application now... providing a 'dto' and a 'repo' for each model... It doesn't smell right.
– Eddie B
Jan 12 '14 at 14:50
@OliverGierke When I am trying to expose my SD Neo4j repository using Spring Data REST where my neo4j repository is exactly the same as above, its not generating_embedded
field and throwing 500 error... else repository with proper Domain class is working fine.. any workout there ?
– agpt
May 6 '14 at 11:59
That's a new question, not a comment, isn't it?
– Oliver Drotbohm
May 7 '14 at 11:57
|
show 1 more comment
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f19417670%2fusing-generics-in-spring-data-jpa-repositories%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
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 should even pop up as this means you're facing a design smell anyway.
4
This is really useful information. It would be great to include this kind of higher-level design guidance in the actual reference materials if it's not already there. Like "best practices" callouts or something.
– Willie Wheeler
Oct 18 '13 at 6:43
1
Well, this is software architecture and design 1:1. If we included such stuff in the reference documentation, where should we stop then? ;)
– Oliver Drotbohm
Oct 18 '13 at 6:48
1
I'm facing this very scenario in the design of an application now... providing a 'dto' and a 'repo' for each model... It doesn't smell right.
– Eddie B
Jan 12 '14 at 14:50
@OliverGierke When I am trying to expose my SD Neo4j repository using Spring Data REST where my neo4j repository is exactly the same as above, its not generating_embedded
field and throwing 500 error... else repository with proper Domain class is working fine.. any workout there ?
– agpt
May 6 '14 at 11:59
That's a new question, not a comment, isn't it?
– Oliver Drotbohm
May 7 '14 at 11:57
|
show 1 more comment
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 should even pop up as this means you're facing a design smell anyway.
4
This is really useful information. It would be great to include this kind of higher-level design guidance in the actual reference materials if it's not already there. Like "best practices" callouts or something.
– Willie Wheeler
Oct 18 '13 at 6:43
1
Well, this is software architecture and design 1:1. If we included such stuff in the reference documentation, where should we stop then? ;)
– Oliver Drotbohm
Oct 18 '13 at 6:48
1
I'm facing this very scenario in the design of an application now... providing a 'dto' and a 'repo' for each model... It doesn't smell right.
– Eddie B
Jan 12 '14 at 14:50
@OliverGierke When I am trying to expose my SD Neo4j repository using Spring Data REST where my neo4j repository is exactly the same as above, its not generating_embedded
field and throwing 500 error... else repository with proper Domain class is working fine.. any workout there ?
– agpt
May 6 '14 at 11:59
That's a new question, not a comment, isn't it?
– Oliver Drotbohm
May 7 '14 at 11:57
|
show 1 more comment
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 should even pop up as this means you're facing a design smell anyway.
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 should even pop up as this means you're facing a design smell anyway.
edited Dec 5 '16 at 12:44
Mubin
3,02011334
3,02011334
answered Oct 18 '13 at 6:30
Oliver DrotbohmOliver Drotbohm
55.5k10170171
55.5k10170171
4
This is really useful information. It would be great to include this kind of higher-level design guidance in the actual reference materials if it's not already there. Like "best practices" callouts or something.
– Willie Wheeler
Oct 18 '13 at 6:43
1
Well, this is software architecture and design 1:1. If we included such stuff in the reference documentation, where should we stop then? ;)
– Oliver Drotbohm
Oct 18 '13 at 6:48
1
I'm facing this very scenario in the design of an application now... providing a 'dto' and a 'repo' for each model... It doesn't smell right.
– Eddie B
Jan 12 '14 at 14:50
@OliverGierke When I am trying to expose my SD Neo4j repository using Spring Data REST where my neo4j repository is exactly the same as above, its not generating_embedded
field and throwing 500 error... else repository with proper Domain class is working fine.. any workout there ?
– agpt
May 6 '14 at 11:59
That's a new question, not a comment, isn't it?
– Oliver Drotbohm
May 7 '14 at 11:57
|
show 1 more comment
4
This is really useful information. It would be great to include this kind of higher-level design guidance in the actual reference materials if it's not already there. Like "best practices" callouts or something.
– Willie Wheeler
Oct 18 '13 at 6:43
1
Well, this is software architecture and design 1:1. If we included such stuff in the reference documentation, where should we stop then? ;)
– Oliver Drotbohm
Oct 18 '13 at 6:48
1
I'm facing this very scenario in the design of an application now... providing a 'dto' and a 'repo' for each model... It doesn't smell right.
– Eddie B
Jan 12 '14 at 14:50
@OliverGierke When I am trying to expose my SD Neo4j repository using Spring Data REST where my neo4j repository is exactly the same as above, its not generating_embedded
field and throwing 500 error... else repository with proper Domain class is working fine.. any workout there ?
– agpt
May 6 '14 at 11:59
That's a new question, not a comment, isn't it?
– Oliver Drotbohm
May 7 '14 at 11:57
4
4
This is really useful information. It would be great to include this kind of higher-level design guidance in the actual reference materials if it's not already there. Like "best practices" callouts or something.
– Willie Wheeler
Oct 18 '13 at 6:43
This is really useful information. It would be great to include this kind of higher-level design guidance in the actual reference materials if it's not already there. Like "best practices" callouts or something.
– Willie Wheeler
Oct 18 '13 at 6:43
1
1
Well, this is software architecture and design 1:1. If we included such stuff in the reference documentation, where should we stop then? ;)
– Oliver Drotbohm
Oct 18 '13 at 6:48
Well, this is software architecture and design 1:1. If we included such stuff in the reference documentation, where should we stop then? ;)
– Oliver Drotbohm
Oct 18 '13 at 6:48
1
1
I'm facing this very scenario in the design of an application now... providing a 'dto' and a 'repo' for each model... It doesn't smell right.
– Eddie B
Jan 12 '14 at 14:50
I'm facing this very scenario in the design of an application now... providing a 'dto' and a 'repo' for each model... It doesn't smell right.
– Eddie B
Jan 12 '14 at 14:50
@OliverGierke When I am trying to expose my SD Neo4j repository using Spring Data REST where my neo4j repository is exactly the same as above, its not generating
_embedded
field and throwing 500 error... else repository with proper Domain class is working fine.. any workout there ?– agpt
May 6 '14 at 11:59
@OliverGierke When I am trying to expose my SD Neo4j repository using Spring Data REST where my neo4j repository is exactly the same as above, its not generating
_embedded
field and throwing 500 error... else repository with proper Domain class is working fine.. any workout there ?– agpt
May 6 '14 at 11:59
That's a new question, not a comment, isn't it?
– Oliver Drotbohm
May 7 '14 at 11:57
That's a new question, not a comment, isn't it?
– Oliver Drotbohm
May 7 '14 at 11:57
|
show 1 more comment
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f19417670%2fusing-generics-in-spring-data-jpa-repositories%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown