Spring Data: Override save method

JavaSpring Data

Java Problem Overview


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

Java Solutions


Solution 1 - Java

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
}

Solution 2 - Java

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;

Solution 3 - Java

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

Solution 4 - Java

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.

Solution 5 - Java

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> {}

Solution 6 - Java

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
    }

}

Solution 7 - Java

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);
        }
    }
}

Solution 8 - Java

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");
  }
}

Solution 9 - Java

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.

Solution 10 - Java

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
    }
}

Solution 11 - Java

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);
        }
    }
}

Solution 12 - Java

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);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionbeginner_View Question on Stackoverflow
Solution 1 - JavaMauro MolinariView Answer on Stackoverflow
Solution 2 - JavaIgnat SachivkoView Answer on Stackoverflow
Solution 3 - Javabeginner_View Answer on Stackoverflow
Solution 4 - JavaBalamaci SerbanView Answer on Stackoverflow
Solution 5 - JavaLucasView Answer on Stackoverflow
Solution 6 - JavaErin DrummondView Answer on Stackoverflow
Solution 7 - JavaytterrrView Answer on Stackoverflow
Solution 8 - JavaM.PanicciaView Answer on Stackoverflow
Solution 9 - Javabhantol2View Answer on Stackoverflow
Solution 10 - JavaMichaelView Answer on Stackoverflow
Solution 11 - JavaMariano RuizView Answer on Stackoverflow
Solution 12 - JavaJohan Hendrik EhlersView Answer on Stackoverflow