Reputation: 7622
I'm considering spring data for a project. Is it possible to override the per default generated save method? And if yes, how?
Upvotes: 59
Views: 118058
Reputation: 300
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()));
Upvotes: 1
Reputation: 1405
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
.
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
}
Upvotes: 49
Reputation: 131
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);
}
Upvotes: 1
Reputation: 358
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
}
}
Upvotes: 1
Reputation: 3119
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> {}
Upvotes: 11
Reputation: 4759
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);
}
}
}
Upvotes: 0
Reputation: 5526
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
}
}
Upvotes: 6
Reputation: 177
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");
}
}
Upvotes: 5
Reputation: 3246
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);
}
}
}
Upvotes: 6
Reputation: 354
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;
Upvotes: 25
Reputation: 27
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.
Upvotes: 1
Reputation: 391
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.
Upvotes: 11
Reputation: 7622
Didn't get this to work nicely so I put my required logic into a service class and left the repositories save method untouched.
Upvotes: 21