I\'m considering spring data for a project. Is it possible to override the per default generated save method? And if yes, how?
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> {}
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");
}
}
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.
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.
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
.
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
}
}
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);
}
}
}