Explain the method-resolution order Spring Data uses across fragments and the base repository. How would you override a CRUD method like save()?
answer
- Composition order = extends order, base last
- First matching fragment wins
- Fragments outrank base -> can override save/delete
- Fragment method suppresses query derivation
- Signature must match exactly to override
basics
~20 sSpring Data checks fragments in the order you declared them in the repository's extends clause; the first fragment that declares the method handles the call. Fragments outrank the generated base, so declaring a fragment with save() (listed before the base is implicitly) lets it override the default save().
solid answer
~40 sWhen you invoke a repository method, the proxy resolves it against a composition: your fragments in declaration order, then the store's base implementation (e.g., SimpleJpaRepository) last. The first fragment whose interface declares a matching signature wins, so precedence follows the left-to-right order of the extends clause. Because custom fragments always rank ahead of the base implementation, you can override a CRUD method: declare the method (e.g., `save(S entity)`) on a fragment interface and implement it in that fragment's Impl. Since the fragment sits before the base in the composition, your version intercepts the call. This is how teams add cross-cutting persistence logic (auditing, validation, soft-delete) around a standard operation while still delegating to normal behavior — the fragment can hold a reference to the repository/EntityManager to do the underlying persist itself.
code
java · 21 linespublic interface AuditingFragment<T> {
<S extends T> S save(S entity); // matches CrudRepository#save signature
}
public class AuditingFragmentImpl<T> implements AuditingFragment<T> {
private final EntityManager em;
public AuditingFragmentImpl(EntityManager em) { this.em = em; }
@Override
public <S extends T> S save(S entity) {
// cross-cutting logic BEFORE the real persist
AuditContext.stamp(entity);
return em.merge(entity); // do the actual work ourselves
}
}
// Fragment listed first -> it outranks SimpleJpaRepository's save()
public interface UserRepository
extends JpaRepository<User, Long>, AuditingFragment<User> {
}
// userRepository.save(u) now runs AuditingFragmentImpl.save(), not the base.go deeper
Just know fragments can override defaults and that order matters.
Explain first-match-wins and that fragments precede the base.
Detail the composition, exact-signature overriding of save/delete, and derivation suppression.
Weigh cross-cutting override patterns vs aspects/entity-listeners and govern the risk of overriding shared CRUD.
## The composition and its order Each repository proxy is backed by a `RepositoryComposition` — an **ordered** list of fragments. The order is: 1. The custom fragments you extend, **in the exact left-to-right order** they appear in the repository's `extends` clause. 2. The generated **base implementation** for the store (JPA: `SimpleJpaRepository`) — effectively the *last* fragment. When a method is called, Spring Data walks this list and dispatches to the **first fragment that declares a method with a matching signature**. Because custom fragments precede the base, a fragment method **shadows** (overrides) the base method of the same signature. ## Overriding a base CRUD method To override, say, `save`: ```java interface UserRepositoryCustom { <S extends User> S save(S entity); // same signature as CrudRepository#save } ``` Implement it in `UserRepositoryCustomImpl`, and extend it **on** the repository: ```java interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {} ``` Now every `userRepository.save(u)` routes to your Impl. To still perform the actual persist you inject an `EntityManager` (or the repository itself, carefully avoiding self-invocation loops) and call `em.merge/persist`. This is a clean seam for auditing, validation, or soft-delete on top of standard CRUD. ## Ordering between fragments matters If two fragments both declare `search(String)`, the one listed first wins. So `extends ..., FragmentA, FragmentB` gives `FragmentA` priority over `FragmentB` for shared signatures. Reordering the extends clause changes behavior — a subtle but real source of bugs. ## Default methods vs fragments A **default method** written directly on an interface the repository extends is a special case: default methods are invoked directly by the proxy (via a method handle) and are generally used as-is for simple composition (delegating to other repository methods). They are not backed by an Impl. For contested overrides prefer an explicit fragment, which has well-defined precedence over the base. ## Gotchas - **Query-derivation only fires for methods NOT provided by any fragment.** If a fragment (or default method) declares the method, Spring will **not** try to derive a query from the name — the fragment wins and derivation is skipped. - Overriding `save`/`delete` changes behavior for **all** callers; document it loudly. - Self-invocation from a fragment back through the proxy can bypass or re-trigger interceptors; injecting `EntityManager` is usually cleaner than injecting the repository into its own fragment. - Signature must match exactly (including generics/parameters) to override the base — a near-miss just adds a new method instead of overriding.
- If a fragment declares findByEmail, will Spring still derive a query from the name?No. A method backed by a fragment is served by that fragment; Spring skips query derivation for it. Derivation only applies to methods no fragment (or default method) provides.
- What's a risk of injecting the repository into its own fragment to reuse save()?Self-invocation through the proxy can loop or re-enter interceptors; it also risks the exact override you defined. Injecting EntityManager and doing merge/persist directly is usually cleaner.
saying these in an interview costs you the question
- Saying the base implementation takes precedence over custom fragments
- Believing fragment declaration order is irrelevant
- Thinking a fragment method still triggers name-based query derivation