Related
I had a slow performance of default save method in spring data jpa. So, i decided to make save method work asynchronously cause i did not need response in this specific method.
#Repository
public interface WorkplaceRepo extends JpaRepository<Workplace, Long> {
#Async
public <S extends Workplace> S save(S workplaceE);
It caused the problem that all save methods in whole project started to call this asynchronous method. The question is: how to use both save methods without losing one of them(default version and async version)
i thought to create custom insert method using native query, but the entity have so many columns and foreign keys, and i am not sure that it would work correctly.
I would suggest creating new repository WorkplaceRepoAsync with one save method something like this:
#Repository
public interface WorkplaceRepoAsync extends Repository<Workplace, Long> {
#Async
public <S extends Workplace> S save(S workplaceE);
}
UPDATE
Much better solution will be create WorkplaceService with #Async logic method
#Repository
public interface WorkplaceRepo extends JPARepository<Workplace, Long> {}
#Service
public class WorkplaceService {
#Inject
private WorkplaceRepo workspaceRepo;
#Async
public Workspace saveAsync(Workspace workspace) {
workspaceRepo.save(workplace);
}
}
You can try with the following code to have both methods available:
#Repository
public interface WorkplaceRepo extends JpaRepository<Workplace, Long> {
#Async
default <S extends Workplace> S saveAsync(S workplaceE) {
return this.save(workplaceE);
}
}
This way the inherited not async method save(S entity) from JpaRepository would still be available to be called from your WorkplaceRepo.
For those who faced same issue i would recommend to read this article. It gave good understanding of how spring data works inside, and solution of my problem.
https://medium.com/#sridharmcakec/refactor-jpa-save-method-to-improve-application-performance-b7a77db2794c
My solution:
public interface CustomWorkplaceRepo {
void saveAsync (Workplace workplace);
}
Then I implemented CustomWorkplaceRepo
public class CustomWorkplaceRepoImpl implements CustomWorkplaceRepo {
#PersistenceContext
private EntityManager entityManager;
#Override
#Async
#Transactional
public void saveAsync(Workplace workplace) {
entityManager.persist(workplace);
}
}
The last step was adding to extends new interface in my old Repo
public interface WorkplaceRepo extends JpaRepository<Workplace, Long>, CustomWorkplaceRepo {
That's it. Now, in service when I want to save Workplace Entity it is possible to call "saveAsync" method
I am looking into Spring Data JPA. Consider the below example where I will get all the crud and finder functionality working by default and if I want to customize a finder then that can be also done easily in the interface itself.
#Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {
#Query("<JPQ statement here>")
List<Account> findByCustomer(Customer customer);
}
I would like to know how can I add a complete custom method with its implementation for the above AccountRepository? Since its an Interface I cannot implement the method there.
You need to create a separate interface for your custom methods:
public interface AccountRepository
extends JpaRepository<Account, Long>, AccountRepositoryCustom { ... }
public interface AccountRepositoryCustom {
public void customMethod();
}
and provide an implementation class for that interface:
public class AccountRepositoryImpl implements AccountRepositoryCustom {
#Autowired
#Lazy
AccountRepository accountRepository; /* Optional - if you need it */
public void customMethod() { ... }
}
See also:
4.6 Custom Implementations for Spring Data Repositories
Note that the naming scheme has changed between versions. See https://stackoverflow.com/a/52624752/66686 for details.
In addition to axtavt's answer, don't forget you can inject Entity Manager in your custom implementation if you need it to build your queries:
public class AccountRepositoryImpl implements AccountRepositoryCustom {
#PersistenceContext
private EntityManager em;
public void customMethod() {
...
em.createQuery(yourCriteria);
...
}
}
There's a slightly modified solution that does not require additional interfaces.
As specificed in the documented functionality, the Impl suffix allows us to have such clean solution:
Define in you regular #Repository interface, say MyEntityRepository the custom methods (in addition to your Spring Data methods)
Create a class MyEntityRepositoryImpl (the Impl suffix is the magic) anywhere (doesn't even need to be in the same package) that implements the custom methods only and annotate such class with #Component** (#Repository will not work).
This class can even inject MyEntityRepository via #Autowired for use in the custom methods.
Example:
Entity class (for completeness):
package myapp.domain.myentity;
#Entity
public class MyEntity {
#Id private Long id;
#Column private String comment;
}
Repository interface:
package myapp.domain.myentity;
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
// EXAMPLE SPRING DATA METHOD
List<MyEntity> findByCommentEndsWith(String x);
List<MyEntity> doSomeHql(Long id); // custom method, code at *Impl class below
List<MyEntity> useTheRepo(Long id); // custom method, code at *Impl class below
}
Custom methods implementation bean:
package myapp.infrastructure.myentity;
#Component // Must be #Component !!
public class MyEntityRepositoryImpl { // must have the exact repo name + Impl !!
#PersistenceContext
private EntityManager entityManager;
#Autowired
private MyEntityRepository myEntityRepository;
#SuppressWarnings("unused")
public List<MyEntity> doSomeHql(Long id) {
String hql = "SELECT eFROM MyEntity e WHERE e.id = :id";
TypedQuery<MyEntity> query = entityManager.createQuery(hql, MyEntity.class);
query.setParameter("id", id);
return query.getResultList();
}
#SuppressWarnings("unused")
public List<MyEntity> useTheRepo(Long id) {
List<MyEntity> es = doSomeHql(id);
es.addAll(myEntityRepository.findByCommentEndsWith("DO"));
es.add(myEntityRepository.findById(2L).get());
return es;
}
}
Usage:
// You just autowire the the MyEntityRepository as usual
// (the Impl class is just impl detail, the clients don't even know about it)
#Service
public class SomeService {
#Autowired
private MyEntityRepository myEntityRepository;
public void someMethod(String x, long y) {
// call any method as usual
myEntityRepository.findByCommentEndsWith(x);
myEntityRepository.doSomeHql(y);
}
}
And that's all, no need for any interfaces other than the Spring Data repo one you already have.
The only possible drawbacks I identified are:
The custom methods in the Impl class are marked as unused by the compiler, thus the #SuppressWarnings("unused") suggestion.
You have a limit of one Impl class. (Whereas in the regular fragment interfaces implementation the docs suggest you could have many.)
If you place the Impl class at a different package and your test uses only #DataJpaTest, you have to add #ComponentScan("package.of.the.impl.clazz") to your test, so Spring loads it.
The accepted answer works, but has three problems:
It uses an undocumented Spring Data feature when naming the custom implementation as AccountRepositoryImpl. The documentation clearly states that it has to be called AccountRepositoryCustomImpl, the custom interface name plus Impl
You cannot use constructor injection, only #Autowired, that are considered bad practice
You have a circular dependency inside of the custom implementation (that's why you cannot use constructor injection).
I found a way to make it perfect, though not without using another undocumented Spring Data feature:
public interface AccountRepository extends AccountRepositoryBasic,
AccountRepositoryCustom
{
}
public interface AccountRepositoryBasic extends JpaRepository<Account, Long>
{
// standard Spring Data methods, like findByLogin
}
public interface AccountRepositoryCustom
{
public void customMethod();
}
public class AccountRepositoryCustomImpl implements AccountRepositoryCustom
{
private final AccountRepositoryBasic accountRepositoryBasic;
// constructor-based injection
public AccountRepositoryCustomImpl(
AccountRepositoryBasic accountRepositoryBasic)
{
this.accountRepositoryBasic = accountRepositoryBasic;
}
public void customMethod()
{
// we can call all basic Spring Data methods using
// accountRepositoryBasic
}
}
This is limited in usage, but for simple custom methods you can use default interface methods like:
import demo.database.Customer;
import org.springframework.data.repository.CrudRepository;
public interface CustomerService extends CrudRepository<Customer, Long> {
default void addSomeCustomers() {
Customer[] customers = {
new Customer("Józef", "Nowak", "nowakJ#o2.pl", 679856885, "Rzeszów", "Podkarpackie", "35-061", "Zamknięta 12"),
new Customer("Adrian", "Mularczyk", "adii333#wp.pl", 867569344, "Krosno", "Podkarpackie", "32-442", "Hynka 3/16"),
new Customer("Kazimierz", "Dejna", "sobieski22#weebly.com", 996435876, "Jarosław", "Podkarpackie", "25-122", "Korotyńskiego 11"),
new Customer("Celina", "Dykiel", "celina.dykiel39#yahoo.org", 947845734, "Żywiec", "Śląskie", "54-333", "Polna 29")
};
for (Customer customer : customers) {
save(customer);
}
}
}
EDIT:
In this spring tutorial it is written:
Spring Data JPA also allows you to define other query methods by
simply declaring their method signature.
So it is even possible to just declare method like:
Customer findByHobby(Hobby personHobby);
and if object Hobby is a property of Customer then Spring will automatically define method for you.
Im using the following code in order to access generated find methods from my custom implementation. Getting the implementation through the bean factory prevents circular bean creation problems.
public class MyRepositoryImpl implements MyRepositoryExtensions, BeanFactoryAware {
private BrandRepository myRepository;
public MyBean findOne(int first, int second) {
return myRepository.findOne(new Id(first, second));
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
myRepository = beanFactory.getBean(MyRepository.class);
}
}
Considering your code snippet, please note that you can only pass Native objects to the findBy### method, lets say you want to load a list of accounts that belongs certain costumers, one solution is to do this,
#Query("Select a from Account a where a."#nameoffield"=?1")
List<Account> findByCustomer(String "#nameoffield");
Make sue the name of the table to be queried is thesame as the Entity class.
For further implementations please take a look at this
If you want to be able to do more sophisticated operations you might need access to Spring Data's internals, in which case the following works (as my interim solution to DATAJPA-422):
public class AccountRepositoryImpl implements AccountRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
private JpaEntityInformation<Account, ?> entityInformation;
#PostConstruct
public void postConstruct() {
this.entityInformation = JpaEntityInformationSupport.getMetadata(Account.class, entityManager);
}
#Override
#Transactional
public Account saveWithReferenceToOrganisation(Account entity, long organisationId) {
entity.setOrganisation(entityManager.getReference(Organisation.class, organisationId));
return save(entity);
}
private Account save(Account entity) {
// save in same way as SimpleJpaRepository
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
}
There is another issue to be considered here. Some people expect that adding custom method to your repository will automatically expose them as REST services under '/search' link. This is unfortunately not the case. Spring doesn't support that currently.
This is 'by design' feature, spring data rest explicitly checks if method is a custom method and doesn't expose it as a REST search link:
private boolean isQueryMethodCandidate(Method method) {
return isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method);
}
This is a qoute of Oliver Gierke:
This is by design. Custom repository methods are no query methods as
they can effectively implement any behavior. Thus, it's currently
impossible for us to decide about the HTTP method to expose the method
under. POST would be the safest option but that's not in line with the
generic query methods (which receive GET).
For more details see this issue: https://jira.spring.io/browse/DATAREST-206
I liked Danila's solution and started using it but nobody else on the team liked having to create 4 classes for each repository. Danila's solution is the only one here that let's you use the Spring Data methods in the Impl class. However, I found a way to do it with just a single class:
public interface UserRepository extends MongoAccess, PagingAndSortingRepository<User> {
List<User> getByUsername(String username);
default List<User> getByUsernameCustom(String username) {
// Can call Spring Data methods!
findAll();
// Can write your own!
MongoOperations operations = getMongoOperations();
return operations.find(new Query(Criteria.where("username").is(username)), User.class);
}
}
You just need some way of getting access to your db bean (in this example, MongoOperations). MongoAccess provides that access to all of your repositories by retrieving the bean directly:
public interface MongoAccess {
default MongoOperations getMongoOperations() {
return BeanAccessor.getSingleton(MongoOperations.class);
}
}
Where BeanAccessor is:
#Component
public class BeanAccessor implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static <T> T getSingleton(Class<T> clazz){
return applicationContext.getBean(clazz);
}
public static <T> T getSingleton(String beanName, Class<T> clazz){
return applicationContext.getBean(beanName, clazz);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
BeanAccessor.applicationContext = applicationContext;
}
}
Unfortunately, you can't #Autowire in an interface. You could autowire the bean into a MongoAccessImpl and provide a method in the interface to access it, but Spring Data blows up. I don't think it expects to see an Impl associated even indirectly with PagingAndSortingRepository.
I faced with this using mongo and spring. So let's assume we use MongoRepository to provided base crud operations, and let's say we need to implement some custom criteria query operation using mongoTemplate. To achieve one interface to inject repository for crud and custom we need to specify:
Custom interface:
public interface UserCustomRepository {
List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest);
}
UserRepository interface 'must' first extends UserCustomRepository and then MongoRepository
#Repository
public interface UserRepository extends UserCustomRepository, MongoRepository<User, ObjectId> {
}
UserRepositoryImpl must have the same name as what crud interface with *Impl suffix.
#Component
#NoArgsConstructor
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class UserRepositoryImpl implements UserCustomRepository {
private MongoTemplate mongoTemplate;
#Override
public List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest){
//some impl
}
}
Let's impl some service - here we inject only UserRepository interface and use methods from crud repository and custom class impl.
#Service
#NoArgsConstructor
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class UserService {
private UserRepository userReposityry;
public List<User> getUserByCriteria(UserCriteriaRequest request) {
userRepository.findById(request.getUserId); // Crud repository method
userRepository.findAllUsersBySomeCriteria(request); // custom method.
}
}
I extends the SimpleJpaRepository:
public class ExtendedRepositoryImpl<T extends EntityBean> extends SimpleJpaRepository<T, Long>
implements ExtendedRepository<T> {
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager em;
public ExtendedRepositoryImpl(final JpaEntityInformation<T, ?> entityInformation,
final EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityInformation = entityInformation;
this.em = entityManager;
}
}
and adds this class to #EnableJpaRepositoryries repositoryBaseClass.
I use SimpleJpaRepository as the base class of repository implementation and add custom method in the interface,eg:
public interface UserRepository {
User FindOrInsert(int userId);
}
#Repository
public class UserRepositoryImpl extends SimpleJpaRepository implements UserRepository {
private RedisClient redisClient;
public UserRepositoryImpl(RedisClient redisClient, EntityManager em) {
super(User.class, em);
this.redisClient = redisClient;
}
#Override
public User FindOrInsert(int userId) {
User u = redisClient.getOrSet("test key.. User.class, () -> {
Optional<User> ou = this.findById(Integer.valueOf(userId));
return ou.get();
});
…………
return u;
}
I need to implement the same sequence of commands in a service, but operating on several different database objects, depending on a case. I'm wondering whether it is a good practice to use an inheritance in such case - which would consist in passing a different dao in a specifc class's constructor only. Something like this:
public abstract class Service{
private Dao dao;
public Service(Dao dao){
this.dao = dao;
}
public void mainMethod(){
dao.step1();
subMethod();
dao.step2();
}
public void subMethod(){
//...
}
}
public class ServiceImpl1 extends Service{
public ServiceImpl1(DaoImpl1 daoImpl1){
super(daoImpl1);
}
}
Inheritance is useful for delegating messages (method calls) to the superclass with few changes. You're not delegating messages to the parent class though, you're changing the parameter of the constructor. So there is no need to inherit anything.
This is fine:
public class Service{
private Dao dao;
public Service(Dao dao){
this.dao = dao;
}
public void mainMethod(){
dao.step1();
subMethod();
dao.step2();
}
public void subMethod(){
//...
}
}
You can then make instances like
Service posgresService = new Service(new PosgresDao());
Service redisService = new Service(new RedisDao());
See depenency injection for more details
I think that if -sequence of commands- refers to the exact same commands, I think you should create overloaded methods such as: if not, inheritance would be a good choice in order to keep orden in the code.
Since you are talking about inheritance and from the sample code provide, it seems like there will be one ServiceImpl class for each DaoImpl class. It will work, however you could also settle with a single ServiceImpl class for all Dao classes:
public class ServiceImpl implements Service {
public void mainMethod(Dao dao) {
dao.step1();
subMethod();
dao.step2();
}
public void subMethod() {
// ...
}
}
elsewhere
Service service = new ServiceImpl();
// ...
Dao dao1 = new DaoImpl1();
Dao dao2 = new DaoImpl2();
// ...
service.mainMethod(dao1);
service.mainMethod(dao2);
I'm pretty new to layered architecture, + spring + hibernate
after reading some guides on how the class hierarchy is supposed to be -
i came up with this structure:
public interface GenericDAO {
public <T> T getItemById(long id, Class<T> c);
public <T> int save(T... objectsToSave);
public <T> int saveOrUpdate(T... objectsToSave);
public <T> int delete(T... objectsToDelete);
.
.
}
now all my other daos impls are using this generic dao as a private field in order to use its basic methods:
i.e:
#Repository
public class UserDAOImpl implements UserDao {
#Autowired
private GenericDAO dao;
#Override
public int deleteUser(User u) {
return dao.delete(u);
}
.
.
.
}
My services are like this :
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserDao userDao;
#Transactional(readOnly = false)
public int deleteUser(User u) {
return userDao.deleteUser(u);
}
.
.
.
}
I don't get why i need a UserDaoImpl , CarDaoImpl, XDaoImpl in my project? it seems really redundant since all the XDaoImpls looks the same:
#Repository
public class UserDAOImpl implements UserDao {
#Autowired
private GenericDAO dao;
#Override
public int deleteUser(User u) {
return dao.delete(u);
}
.
.
.
}
#Repository
public class CarDAOImpl implements CarDao {
#Autowired
private GenericDAO dao;
#Override
public int deleteCar(Car c) {
return dao.delete(c);
}
.
.
.
}
#Repository
public class XDAOImpl implements XDao {
#Autowired
private GenericDAO dao;
#Override
public int deleteX(X c) {
return dao.delete(c);
}
.
.
.
}
I could just not create any XDaoImpl and just use the GenericDaoImpl instead and save alot of classes creation, no?
If ill need any complex actions like deleteUserCar(User u) i can just implement the logic in the service:
UserService {
public void deleteUserCar(User u) {
Car c = u.getCar();
CarService cs.deleteCar(c);
}
}
Am i missing something?
can anyone please offer an example that using only GenericDaoImpl instead of XDaoImpl will make me regret it?
thanks
Your service will later on invoke businesslogic instead of just passing methods to the DAO. It may validate values (e.g. does it exist and is it supposed to be unique), run calculations (e.g. int getStock() { return bought - sold; } and so on.
A generic DAO is great, though consider a abstract class instead of an interface. This way you don't need to create multiple create()s, just extend the abstract DAO (e.g. CarDAO extends AbstractDAO<Car>).
Your extended DAO will pass the class it handles to the generic abstract DAO (as seen in previous example).
Your extended DAO will later on implement extra methods that only apply on that particular object, e.g.: List<Car> getCarsWithColor(Color color).
Your Service -> DAO relationship is not always one-on-one. Consider these DAOs: TruckDAO, CarDAO, VanDAO with objects Truck extends Vehicle, Car extends Vehicle, Van extends Vehicle. Do you need three services, or will a VehicleService cover it (will you run logic for all Vehicles perhaps)?
Reconsider the use of interfaces, this question applies to C# but the concept is the same.
My advice is: Just keep it simple! Please don't create too much abstraction that leads to too much complexity, remember that if you create create so much code that you don't need, that code you will be forced to mantain: tons of trash that you don't even know what was its purpose, tons of code that obscure the real objectives of your application.
So in this particular case I advice to :
Forget about creating DAO interfaces: They are intended to abstract the DAO implementations in order to switch 'easily' from databases (eg. MySQL to SQLServer) but think!: that is a really rare thing: it is more common to switch systems than to switch databases
Put that GenericDao in the trash (The world is not about CRUDs, go to your stakeholders and ask what they really need)
Use a simple DAO Layer and Service Layer, you can implement that with Spring: I imagine
that you can use a SimpleJDBCTemplate in DAO Layer and call that DAO methods in Service layer
Why use Spring after all? Have you asked yourself what's its purpose?, Currently I work with MyBatis in this way: I create Mappers (something analogous to DAOs) that are called by POJO services. Simple, useful and straightforward, no Spring, plain old Java, works like champ.
I am looking into Spring Data JPA. Consider the below example where I will get all the crud and finder functionality working by default and if I want to customize a finder then that can be also done easily in the interface itself.
#Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {
#Query("<JPQ statement here>")
List<Account> findByCustomer(Customer customer);
}
I would like to know how can I add a complete custom method with its implementation for the above AccountRepository? Since its an Interface I cannot implement the method there.
You need to create a separate interface for your custom methods:
public interface AccountRepository
extends JpaRepository<Account, Long>, AccountRepositoryCustom { ... }
public interface AccountRepositoryCustom {
public void customMethod();
}
and provide an implementation class for that interface:
public class AccountRepositoryImpl implements AccountRepositoryCustom {
#Autowired
#Lazy
AccountRepository accountRepository; /* Optional - if you need it */
public void customMethod() { ... }
}
See also:
4.6 Custom Implementations for Spring Data Repositories
Note that the naming scheme has changed between versions. See https://stackoverflow.com/a/52624752/66686 for details.
In addition to axtavt's answer, don't forget you can inject Entity Manager in your custom implementation if you need it to build your queries:
public class AccountRepositoryImpl implements AccountRepositoryCustom {
#PersistenceContext
private EntityManager em;
public void customMethod() {
...
em.createQuery(yourCriteria);
...
}
}
There's a slightly modified solution that does not require additional interfaces.
As specificed in the documented functionality, the Impl suffix allows us to have such clean solution:
Define in you regular #Repository interface, say MyEntityRepository the custom methods (in addition to your Spring Data methods)
Create a class MyEntityRepositoryImpl (the Impl suffix is the magic) anywhere (doesn't even need to be in the same package) that implements the custom methods only and annotate such class with #Component** (#Repository will not work).
This class can even inject MyEntityRepository via #Autowired for use in the custom methods.
Example:
Entity class (for completeness):
package myapp.domain.myentity;
#Entity
public class MyEntity {
#Id private Long id;
#Column private String comment;
}
Repository interface:
package myapp.domain.myentity;
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
// EXAMPLE SPRING DATA METHOD
List<MyEntity> findByCommentEndsWith(String x);
List<MyEntity> doSomeHql(Long id); // custom method, code at *Impl class below
List<MyEntity> useTheRepo(Long id); // custom method, code at *Impl class below
}
Custom methods implementation bean:
package myapp.infrastructure.myentity;
#Component // Must be #Component !!
public class MyEntityRepositoryImpl { // must have the exact repo name + Impl !!
#PersistenceContext
private EntityManager entityManager;
#Autowired
private MyEntityRepository myEntityRepository;
#SuppressWarnings("unused")
public List<MyEntity> doSomeHql(Long id) {
String hql = "SELECT eFROM MyEntity e WHERE e.id = :id";
TypedQuery<MyEntity> query = entityManager.createQuery(hql, MyEntity.class);
query.setParameter("id", id);
return query.getResultList();
}
#SuppressWarnings("unused")
public List<MyEntity> useTheRepo(Long id) {
List<MyEntity> es = doSomeHql(id);
es.addAll(myEntityRepository.findByCommentEndsWith("DO"));
es.add(myEntityRepository.findById(2L).get());
return es;
}
}
Usage:
// You just autowire the the MyEntityRepository as usual
// (the Impl class is just impl detail, the clients don't even know about it)
#Service
public class SomeService {
#Autowired
private MyEntityRepository myEntityRepository;
public void someMethod(String x, long y) {
// call any method as usual
myEntityRepository.findByCommentEndsWith(x);
myEntityRepository.doSomeHql(y);
}
}
And that's all, no need for any interfaces other than the Spring Data repo one you already have.
The only possible drawbacks I identified are:
The custom methods in the Impl class are marked as unused by the compiler, thus the #SuppressWarnings("unused") suggestion.
You have a limit of one Impl class. (Whereas in the regular fragment interfaces implementation the docs suggest you could have many.)
If you place the Impl class at a different package and your test uses only #DataJpaTest, you have to add #ComponentScan("package.of.the.impl.clazz") to your test, so Spring loads it.
The accepted answer works, but has three problems:
It uses an undocumented Spring Data feature when naming the custom implementation as AccountRepositoryImpl. The documentation clearly states that it has to be called AccountRepositoryCustomImpl, the custom interface name plus Impl
You cannot use constructor injection, only #Autowired, that are considered bad practice
You have a circular dependency inside of the custom implementation (that's why you cannot use constructor injection).
I found a way to make it perfect, though not without using another undocumented Spring Data feature:
public interface AccountRepository extends AccountRepositoryBasic,
AccountRepositoryCustom
{
}
public interface AccountRepositoryBasic extends JpaRepository<Account, Long>
{
// standard Spring Data methods, like findByLogin
}
public interface AccountRepositoryCustom
{
public void customMethod();
}
public class AccountRepositoryCustomImpl implements AccountRepositoryCustom
{
private final AccountRepositoryBasic accountRepositoryBasic;
// constructor-based injection
public AccountRepositoryCustomImpl(
AccountRepositoryBasic accountRepositoryBasic)
{
this.accountRepositoryBasic = accountRepositoryBasic;
}
public void customMethod()
{
// we can call all basic Spring Data methods using
// accountRepositoryBasic
}
}
This is limited in usage, but for simple custom methods you can use default interface methods like:
import demo.database.Customer;
import org.springframework.data.repository.CrudRepository;
public interface CustomerService extends CrudRepository<Customer, Long> {
default void addSomeCustomers() {
Customer[] customers = {
new Customer("Józef", "Nowak", "nowakJ#o2.pl", 679856885, "Rzeszów", "Podkarpackie", "35-061", "Zamknięta 12"),
new Customer("Adrian", "Mularczyk", "adii333#wp.pl", 867569344, "Krosno", "Podkarpackie", "32-442", "Hynka 3/16"),
new Customer("Kazimierz", "Dejna", "sobieski22#weebly.com", 996435876, "Jarosław", "Podkarpackie", "25-122", "Korotyńskiego 11"),
new Customer("Celina", "Dykiel", "celina.dykiel39#yahoo.org", 947845734, "Żywiec", "Śląskie", "54-333", "Polna 29")
};
for (Customer customer : customers) {
save(customer);
}
}
}
EDIT:
In this spring tutorial it is written:
Spring Data JPA also allows you to define other query methods by
simply declaring their method signature.
So it is even possible to just declare method like:
Customer findByHobby(Hobby personHobby);
and if object Hobby is a property of Customer then Spring will automatically define method for you.
Im using the following code in order to access generated find methods from my custom implementation. Getting the implementation through the bean factory prevents circular bean creation problems.
public class MyRepositoryImpl implements MyRepositoryExtensions, BeanFactoryAware {
private BrandRepository myRepository;
public MyBean findOne(int first, int second) {
return myRepository.findOne(new Id(first, second));
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
myRepository = beanFactory.getBean(MyRepository.class);
}
}
Considering your code snippet, please note that you can only pass Native objects to the findBy### method, lets say you want to load a list of accounts that belongs certain costumers, one solution is to do this,
#Query("Select a from Account a where a."#nameoffield"=?1")
List<Account> findByCustomer(String "#nameoffield");
Make sue the name of the table to be queried is thesame as the Entity class.
For further implementations please take a look at this
If you want to be able to do more sophisticated operations you might need access to Spring Data's internals, in which case the following works (as my interim solution to DATAJPA-422):
public class AccountRepositoryImpl implements AccountRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
private JpaEntityInformation<Account, ?> entityInformation;
#PostConstruct
public void postConstruct() {
this.entityInformation = JpaEntityInformationSupport.getMetadata(Account.class, entityManager);
}
#Override
#Transactional
public Account saveWithReferenceToOrganisation(Account entity, long organisationId) {
entity.setOrganisation(entityManager.getReference(Organisation.class, organisationId));
return save(entity);
}
private Account save(Account entity) {
// save in same way as SimpleJpaRepository
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
}
There is another issue to be considered here. Some people expect that adding custom method to your repository will automatically expose them as REST services under '/search' link. This is unfortunately not the case. Spring doesn't support that currently.
This is 'by design' feature, spring data rest explicitly checks if method is a custom method and doesn't expose it as a REST search link:
private boolean isQueryMethodCandidate(Method method) {
return isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method);
}
This is a qoute of Oliver Gierke:
This is by design. Custom repository methods are no query methods as
they can effectively implement any behavior. Thus, it's currently
impossible for us to decide about the HTTP method to expose the method
under. POST would be the safest option but that's not in line with the
generic query methods (which receive GET).
For more details see this issue: https://jira.spring.io/browse/DATAREST-206
I liked Danila's solution and started using it but nobody else on the team liked having to create 4 classes for each repository. Danila's solution is the only one here that let's you use the Spring Data methods in the Impl class. However, I found a way to do it with just a single class:
public interface UserRepository extends MongoAccess, PagingAndSortingRepository<User> {
List<User> getByUsername(String username);
default List<User> getByUsernameCustom(String username) {
// Can call Spring Data methods!
findAll();
// Can write your own!
MongoOperations operations = getMongoOperations();
return operations.find(new Query(Criteria.where("username").is(username)), User.class);
}
}
You just need some way of getting access to your db bean (in this example, MongoOperations). MongoAccess provides that access to all of your repositories by retrieving the bean directly:
public interface MongoAccess {
default MongoOperations getMongoOperations() {
return BeanAccessor.getSingleton(MongoOperations.class);
}
}
Where BeanAccessor is:
#Component
public class BeanAccessor implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static <T> T getSingleton(Class<T> clazz){
return applicationContext.getBean(clazz);
}
public static <T> T getSingleton(String beanName, Class<T> clazz){
return applicationContext.getBean(beanName, clazz);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
BeanAccessor.applicationContext = applicationContext;
}
}
Unfortunately, you can't #Autowire in an interface. You could autowire the bean into a MongoAccessImpl and provide a method in the interface to access it, but Spring Data blows up. I don't think it expects to see an Impl associated even indirectly with PagingAndSortingRepository.
I faced with this using mongo and spring. So let's assume we use MongoRepository to provided base crud operations, and let's say we need to implement some custom criteria query operation using mongoTemplate. To achieve one interface to inject repository for crud and custom we need to specify:
Custom interface:
public interface UserCustomRepository {
List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest);
}
UserRepository interface 'must' first extends UserCustomRepository and then MongoRepository
#Repository
public interface UserRepository extends UserCustomRepository, MongoRepository<User, ObjectId> {
}
UserRepositoryImpl must have the same name as what crud interface with *Impl suffix.
#Component
#NoArgsConstructor
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class UserRepositoryImpl implements UserCustomRepository {
private MongoTemplate mongoTemplate;
#Override
public List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest){
//some impl
}
}
Let's impl some service - here we inject only UserRepository interface and use methods from crud repository and custom class impl.
#Service
#NoArgsConstructor
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class UserService {
private UserRepository userReposityry;
public List<User> getUserByCriteria(UserCriteriaRequest request) {
userRepository.findById(request.getUserId); // Crud repository method
userRepository.findAllUsersBySomeCriteria(request); // custom method.
}
}
I extends the SimpleJpaRepository:
public class ExtendedRepositoryImpl<T extends EntityBean> extends SimpleJpaRepository<T, Long>
implements ExtendedRepository<T> {
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager em;
public ExtendedRepositoryImpl(final JpaEntityInformation<T, ?> entityInformation,
final EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityInformation = entityInformation;
this.em = entityManager;
}
}
and adds this class to #EnableJpaRepositoryries repositoryBaseClass.
I use SimpleJpaRepository as the base class of repository implementation and add custom method in the interface,eg:
public interface UserRepository {
User FindOrInsert(int userId);
}
#Repository
public class UserRepositoryImpl extends SimpleJpaRepository implements UserRepository {
private RedisClient redisClient;
public UserRepositoryImpl(RedisClient redisClient, EntityManager em) {
super(User.class, em);
this.redisClient = redisClient;
}
#Override
public User FindOrInsert(int userId) {
User u = redisClient.getOrSet("test key.. User.class, () -> {
Optional<User> ou = this.findById(Integer.valueOf(userId));
return ou.get();
});
…………
return u;
}