57

I'm considering spring data for a project. Is it possible to override the per default generated save method? And if yes, how?

Ignat Sachivko
  • 314
  • 2
  • 9
beginner_
  • 7,230
  • 18
  • 70
  • 127
  • What do you want to achieve? Maybe AOP is a better approach? – Tomasz Nurkiewicz Oct 23 '12 at 17:44
  • 1
    Don't use id to determine whether the concerned entity is new or not. the entity is immutable meaning if a user changes it, the system should create a new one with the changed data (or use existing one that equals that data. Thats the only concern i have else spring data + querydsl looks very promising for my project. – beginner_ Oct 23 '12 at 17:53
  • What about implementing [`Persistable`](http://static.springsource.org/spring-data/data-commons/docs/1.3.2.RELEASE/api/org/springframework/data/domain/Persistable.html)? Would this work for you? Also are you using Spring Data JPA or some other back-end database? – Tomasz Nurkiewicz Oct 23 '12 at 18:17
  • Would be Spring data JPA. I have all my entites properly annotated. My only concern is the question. How to override save method of Crude or Jpa repository? – beginner_ Oct 23 '12 at 19:24

13 Answers13

46

Simply create your custom interface as usual and declare there the methods you want to ovverride with the same signature of the one exposed by CrudRepository (or JpaRepository, etc.). Suppose you have a MyEntity entity and a MyEntityRepository repository and you want to override the default auto-generated save method of MyEntityRepository which takes an only entity instance, then define:

public interface MyEntityRepositoryCustom {
  <S extends MyEntity> S save(S entity);
}

and implement this method as you like in your MyEntityRepositoryImpl, as usual:

@Transactional
public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {
  public <S extends MyEntity> S save(S entity) {
    // your implementation
  }
}

And then, as usual, let MyEntityRepository extend MyEntityRepositoryCustom.

Doing this, Spring Data JPA will call the save method of your MyEntityRepositoryImpl instead of the default implementation. At least this works for me with the delete method in Spring Data JPA 1.7.2.


"ambiguous reference" error

As reported by some of the commenters, probably starting from a certain Spring Data JPA version or javac version (I can't say when it started to fail, but I know for sure that it worked before) the javac compiler began to give a compilation error on the overridden method: "ambiguous reference". Eclipse JDT does not return this error and code works at runtime, in fact I opened Bug 463770 for this reason: either it's a javac bug or a JDT bug that does not conform to javac. This said, Lucas has posted the workaround below, which at a first sight might seem to be identical to the one described above. Actually, the difference stands on the MyEntityRepositoryCustom, declaration which must include the generic type <S>, even if it's apparently useless. So, if you encounter this error change the custom interface declaration as:

public interface MyEntityRepositoryCustom<S> {
  <S extends MyEntity> S save(S entity);
}

and let the standard repository interface implement MyEntityRepositoryCustom<MyEntity> rather than just MyEntityRepositoryCustom.

However, if you don't want to generify your custom interface, you can extract a separate fragment interface particulary for the save method 2. Then the solution looks as follows:

public interface MyEntityRepositoryCustom {
  // custom methods
}

public interface CustomizedSaveRepository<T> {
  <S extends T> S save(S entity);
}

@Transactional
public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom,
 CustomizedSaveRepository<MyEntity> {
  
  @Override
  public MyEntity save(MyEntity entity) {
    // your implementation for CustomizedSaveRepository#save
  }

  // implementations of MyEntityRepositoryCustom
}
Michael
  • 53
  • 5
Mauro Molinari
  • 1,246
  • 2
  • 14
  • 24
  • 7
    This one works indeed. Important bit to make it work is to preserve the naming convention. That is, the MyEntityRepositoryImpl class name must be built like `
    Impl` and _not_ as for example `MyEntityRepositoryCustomImpl`. It won't work in the latter case.
    – Sergey Shcherbakov Sep 24 '16 at 12:12
  • 7
    Cool, but how do you call the default JPARepository.save method from MyEntityRepositoryImpl? – Daniel Pinyol Jun 23 '17 at 13:09
  • 2
    @DanielPinyol You let Spring inject the entity manager in your `MyEntityRepositoryImpl` and then call `persist(Object)` on it, rather than on the default `JPARepository` implementation. You can use `@PersistenceContext` for this purpose. – Mauro Molinari Jun 23 '17 at 17:27
  • 20
    I just get this error: java: `reference to save is ambiguous` – orrymr Jul 03 '17 at 15:30
  • @SergeyShcherbakov, I have noticed the requirement for that naming convention as well (and your comment led me to the fix - thank you), but the docs (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations - see specifically examples 25 - 29) seems to indicate the opposite. The docs seem to say that the impl should be named after the *custom* interface. Am I missing something? – matt forsythe Nov 28 '17 at 19:59
  • @SergeyShcherbakov Did some more digging and answered my own question: your comment is true for older (1.x) versions of spring-data. In version 2.x of spring-data, this requirement changes a bit. – matt forsythe Nov 28 '17 at 20:10
  • 1
    @orrymr try to follow the CustomizedSave example from this article https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#repositories.custom-implementations – Sergey Shcherbakov Jan 23 '18 at 15:14
  • 3
    This doesn't work in Spring Boot 2.1.1 with `ambiguous reference` error. – Lym Zoy Jan 16 '19 at 13:24
  • 2
    I've just posted the correct way to override the save method – Lucas Jul 22 '19 at 16:01
  • 1
    @orrymr I just updated my answer to include the fix for the "ambiguous reference" error. – Mauro Molinari Oct 15 '20 at 07:51
  • @LymZoy I just updated my answer to include the fix for the "ambiguous reference" error. – Mauro Molinari Oct 15 '20 at 07:51
  • > And then, as usual, let MyEntityRepository implement MyEntityRepositoryCustom. --- Severe mistake: not "implement", but "extend". This mistake (together with the Spring black magic) cost my some time for digging. – Vijit Oct 16 '20 at 11:20
  • @Vijit I fixed the word, thanks, however it was a trivial error for whoever knows and uses Spring Data: `MyEntityRepository` is of course an interface. – Mauro Molinari Oct 16 '20 at 19:53
  • @SergeyShcherbakov comment regarding the convention is great. I just want to point out that the postfix is customizable using `EnableJpaRepositories#repositoryImplementationPostfix()`. – Aria May 29 '23 at 14:54
23

To provide override of default generated save method you need to use aggregation of Spring Data repository implementation inside your own custom repository implementation.

Repository interface:

public interface UserRepository extends CrudRepository<User, String>{

}

Your repository implementation:

@Repository("customUserRepository")
public class CustomUserRepository implements UserRepository {

    @Autowired
    @Qualifier("userRepository") // inject Spring implementation here
    private UserRepository userRepository;

    public User save(User user) {
        User user = userRepository.save(entity);
        // Your custom code goes here
        return user;
    }

    // Delegate other methods here ...

    @Override
    public User findOne(String s) {
        return userRepository.findOne(s);
    }
}

Then use your custom implementation in your service:

@Autowired
@Qualifier("customUserRepository")
private UserRepository userRepository;
Ignat Sachivko
  • 314
  • 2
  • 9
  • Good one. Thanks. A small suggestion. When I tried, I got into circular dependency issue. So adding @Lazy for userRepository took care of it. – SriSri May 08 '18 at 16:44
  • 5
    Nice one :) I just used a `Qualifier` on the original Spring Reposiory, and annotated my overriden version with `@Primary` to prevent using names everywhere! – sjahan Jan 18 '19 at 08:34
  • @Ignat Sachivko It has been a few years, since you published your solution. Nonetheless, it suites my needs very well, and I'd be glad to make it working. My issue is that when I run the code an exception pops up `org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.lvr.tests.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}`. However, IntelliJ perfectly understands bean definitions and goes from `@Autowire` to `CustomUserRepository`. Do you have any ideas why it is not working? – Lev Nov 20 '19 at 09:09
19

Didn't get this to work nicely so I put my required logic into a service class and left the repositories save method untouched.

beginner_
  • 7,230
  • 18
  • 70
  • 127
11

I guess you extend SimpleJpaRepository :

public class **CustomSimpleJpaRepository** extends SimpleJpaRepository {

@Transactional
public <S extends T> S save(S entity) { //do what you want instead }
}

Then make sure this is used instead of the default SimpleJpaRepository by extending:

public class CustomJpaRepositoryFactory extends JpaRepositoryFactory {

    protected <T, ID extends Serializable> JpaRepository<?, ?> getTargetRepository(RepositoryMetadata metadata, EntityManager entityManager) {

      Class<?> repositoryInterface = metadata.getRepositoryInterface();
      JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());

      SimpleJpaRepository<?, ?> repo = isQueryDslExecutor(repositoryInterface) ? new QueryDslJpaRepository(
            entityInformation, entityManager) : new CustomSimpleJpaRepository(entityInformation, entityManager);
    repo.setLockMetadataProvider(lockModePostProcessor.getLockMetadataProvider());

      return repo;
  }
}

Not done yet, we also need to have your own factory bean to use it in the config xml:

public class CustomRepositoryFactoryBean <T extends JpaRepository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> {

protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
    return new **CustomJpaRepositoryFactory**(entityManager);
}

}

the config:

<jpa:repositories base-package="bla.bla.dao" factory-class="xxxx.**CustomRepositoryFactoryBean**"/>

Hope it helps.

Balamaci Serban
  • 381
  • 2
  • 5
  • The `isQueryDslExecutor()` method seems to be private code and the `LockModePostProcessor` class doesn't exist. I do have the `LockModeRepositoryPostProcessor` however with the same method. Is this what you meant? – coderatchet May 19 '14 at 04:33
10

In order to properly override the save method, you have to create a interface with the proper signature of the original method declared on CrudRepository, including the generics

public interface MyCustomRepository<T> {
    <S extends T> S save(S entity);
}

Then, create your implementation ( the suffix Impl is important at the name of the class)

public class MyCustomRepositoryImpl implements MyCustomRepository<MyBean> {

    @Autowired
    private EntityManager entityManager;


    @Override
    public <S extends MyBean> S save(S entity) {
       /**
         your custom implementation comes here ...
         i think the default one is just        
        return this.entityManager.persist(entity);
       */
    }

}

Finally, extend your repository with the previously created interface

@RepositoryRestResource
@Repository
public interface MyBeanRepository extends PagingAndSortingRepository<MyBean, Long>, MyCustomRepository<MyBean> {}
Lucas
  • 3,059
  • 5
  • 33
  • 48
6

I am using Spring Boot 2.1.4 on OpenJDK 11 and also keep getting the ambiguous reference error from the compiler (although the Eclipse JDT compiler that my IDE is using has no problem with it, so I didn't discover this issue until I tried to build it outside my IDE).

I basically ended up defining a method with a different name in my extension interface, and then used a default override in my main repository interface to call it when the normal save() was called.

Here is an example:

Define the interface for your custom logic as usual:

public interface MyEntityRepositoryCustomSaveAction {
    public MyEntity saveSafely(MyEntity entity);
}

Make your repository extend that interface:

public interface MyEntityRepository extends JpaRepository<MyEntity, MyEntityId>,
  MyEntityRepositoryCustomSaveAction {

    @Override
    @SuppressWarnings("unchecked")
    default MyEntity save(MyEntity entity)
    {
        return saveSafely(entity);
    }
}

Note that we have overridden save() from JpaRepository (well, really CrudRepository which JpaRepository extends) to call our custom method. The compiler warns about the unchecked conversion, so up to you if you want to silence it with @SuppressWarnings .

Follow the convention for the Impl class with your custom logic

public class MyEntityRepositoryCustomSaveActionImpl implements 
  MyEntityRepositoryCustomSaveAction {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public MyEntity saveSafely(MyEntity entity) {
       //whatever custom logic you need
    }

}
Erin Drummond
  • 5,347
  • 6
  • 35
  • 41
5

This could be helpful if you are going to reuse the original method. Just inject EntityManager in the implementing class.

public interface MyEntityRepositoryCustom {
  <S extends MyEntity> S save(S entity);
}

public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {

    // optionally specify unitName, if there are more than one
    @PersistenceContext(unitName = PRIMARY_ENTITY_MANAGER_FACTORY)
    private EntityManager entityManager;

    /**
     * @see org.springframework.data.jpa.repository.support.SimpleJpaRepository
     */
    @Transactional
    public <S extends MyEntity> S save(S entity) {
        // do your logic here
        JpaEntityInformation<MyEntity, ?> entityInformation = JpaEntityInformationSupport.getMetadata(MyEntity.class, entityManager);
        if (entityInformation.isNew(entity)) {
            entityManager.persist(entity);
            return entity;
        } else {
            return entityManager.merge(entity);
        }
    }
}
ytterrr
  • 3,036
  • 6
  • 23
  • 32
  • your implementation class should be named MyEntityRepositoryCustomImpl instead of MyEntityRepositoryImpl. right? – Bruno Negrão Zica Apr 28 '18 at 17:08
  • Have access to the original method is key. Unfortunately I got an exception `Cannot resolve method getMetadata(...`. I checked the code and the `JpaEntityInformationSupport` class does not contain such a method (`getMetadata`), at least since `spring-data-jpa` 2.1, so I guest this used to work in old versions of Spring :( – Mariano Ruiz Jul 04 '19 at 13:43
5

If you are using interfaces only you can use default methods to do simple overrides of the CrudRepository or JpaRepository:


public interface MyCustomRepository extends CrudRepository<T, ID> {

  @Override
  default <S extends T> S save(S entity)
  {
    throw new UnsupportedOperationException("writes not allowed");
  }
}
M.Paniccia
  • 157
  • 2
  • 7
1

Use JPA Event listeners like @PrePersist, @PreUpdate. This will work if the underlying JPA provider supports this features. This is JPA 2 feature so the latest Hibernate, EclipseLink etc should support it.

bhantol2
  • 27
  • 1
  • 1
    This does not work because the logic/code that needs to be run must access the database and I do not want to have references to data access layer in my entity classes. – beginner_ Jul 13 '13 at 08:01
  • There are several problems. First, these annotation may have provider specific behavior; second, they're very limited in what they can do; third, this doesn't answer the op question because it's unrelated to spring data; and, finally, none of these annotations serve the purpose of overriding the save method, not even JPA's persist method for all that's worth. This answer should be widely downvoted. – Igor Donin Aug 09 '17 at 17:57
1

What works for me (Spring boot 2.x Java 11), even if not perfectly clean. It compiles with IDE and Maven and Gradle. The above solution by Lucas does not work for me for the JpaRepository.

public interface MyRepo extends JpaRepository<MyType, Long>, MyRepoCustom{

   //Implemented in MyRepoCustom
   public MyType save(MyType mytypeEntity);
}

The custom interface (repeats the declaration, which is not nice):

public interface MyRepoCustom{
    public MyType save(MyType mytypeEntity);
}

The custom Impl:

@Repository
public class MyRepoImpl implements MyRepoCustom{
    @PersistenceContext
    private EntityManager em;

    @Transactional
    public MyType save(MyType mytypeEntity) {
       //type safe implementation
    }
}
Michael
  • 192
  • 1
  • 5
1

I am in the process of updating an application from Spring Boot 1.5 to Spring Boot 2.0, I found that suddenly some of the custom behavior we have in our save methods was not working anymore, turned out we had to update the signature of the save methods on our repositories to work. Note that I had to add the generic class parameter and the generic parameter on the function to make both the build inside Eclipse and via the CLI (gradle) to work.

So I changed my custom repository interface like this:

interface ScoreRepositoryCustom {
  Score save(Score score);
}

to this (to match the signature in CrudRepository):

interface ScoreRepositoryCustom<T> {
  <S extends T> S save(S to);
}
0

The solution from @ytterrr works but for older Spring Data versions, for Spring Data 2.1 at least, this is the way to not just override any repository method but also access to the underlying features (access to the entity manager to persist, delete, find...):

public interface MyEntityRepositoryCustom {
  <S extends MyEntity> S save(S entity);
}

public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {

    final JpaEntityInformation<MyEntity, ?> entityInformation;
    EntityManager em;

    public MyEntityRepositoryImpl(EntityManager entityManager) {
        this.entityInformation = JpaEntityInformationSupport.getEntityInformation(MyEntity.class, entityManager);
        this.em = entityManager;
    }

    /**
     * @see org.springframework.data.jpa.repository.support.SimpleJpaRepository
     */
    @Transactional
    public <S extends MyEntity> S save(S entity) {

        // do your logic here

        if (entityInformation.isNew(entity)) {
            em.persist(entity);
            return entity;
        } else {
            return em.merge(entity);
        }
    }
}
Mariano Ruiz
  • 4,314
  • 2
  • 38
  • 34
0

A solution to call the default super save method implementation to avoid call entityManager.persist(), and safely override all save / saveAndFlush / saveAll methods in one override.

The interface :

public interface MyEntityRepository extends JpaRepository<MyEntity, UUID>, MyEntityRepositoryCustom<MyEntity> {

}

The custom interface :

public interface MyEntityRepositoryCustom<T> {
    <S extends T> S save(S entity);
}

The custom implementation :

public class MyEntityRepositoryImpl extends SimpleJpaRepository<MyEntity, UUID>
implements MyEntityRepositoryCustom<MyEntity> {

    public MyEntityRepositoryImpl(EntityManager entityManager) {
        super(MyEntity.class, entityManager);
    }

    @Override
    public <S extends MyEntity> S save(S myEntity) {
        // my custom implementation (business or technical) on save and call super default execution!
        return super.save(myEntity);
    }

}

The extra of this pattern, the saveAll and the saveAndFlush methods call the overrided save from the custom implementation ! That way, your specific behaviour is safely executed in all save alternative methods. Even if an other developer call indifferently one of save method without knowledge about overridden method and specific behaviour to safely preserve.

Exemple of use :

@Autowired
private MyEntityRepository myEntityRepository;

myEntityRepository.save(new MyEntity());

myEntityRepository.saveAndFlush(new MyEntity());

myEntityRepository.saveAll(List.of(new MyEntity()));
jpep1
  • 210
  • 2
  • 8