Great question! 🔍 You're right to ask:
"If
Repositoryis just a marker interface and we're extending it inMyBaseRepository, then where is the actual implementation of the methods likesave()orfindById()?"
Let's break it down clearly 👇
The implementation comes from Spring Data itself — specifically from classes like SimpleJpaRepository — which dynamically implements your repository interface at runtime using Spring Data proxies.
- You define this:
@NoRepositoryBean
public interface MyBaseRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}public interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(String email);
}-
Spring Boot + Spring Data JPA (or Mongo) scans all interfaces that extend
Repository,CrudRepository, orJpaRepository. -
For each such interface (like
UserRepository), Spring:-
Detects the domain class (
User) -
Detects the ID type (
Long) -
Finds a matching implementation class like
SimpleJpaRepository<User, Long> -
Creates a proxy at runtime that:
- Implements your
UserRepositoryinterface - Delegates method calls like
save()andfindById()to internal logic inSimpleJpaRepository
- Implements your
-
Even if you didn’t extend CrudRepository, Spring knows how to handle methods like save() and findById() because the method signatures match the known ones in the internal Spring Data API.
So Spring internally does something like:
if method signature == known CRUD method (e.g., save(), findById())
then delegate to built-in implementation (e.g., SimpleJpaRepository)That’s how it works without you having to write any code.
-
Repositoryis a marker interface — just used to tell Spring:“This is a repository interface — apply your magic here.”
-
@EnableJpaRepositoriesin Spring Boot auto-configuration triggers the scanning and proxy creation logic.
You can debug your app and put a breakpoint inside your service class where you call userRepository.save(user).
You’ll see that it hits a Spring-generated proxy backed by SimpleJpaRepository.
| Concept | Meaning |
|---|---|
Repository interface |
Marker only — tells Spring this is a data repo |
MyBaseRepository |
Your own abstraction, defines only the methods you want |
| No actual implementation | Spring Data provides it at runtime via proxies |
| Matches method signatures | If your method signature matches known ones (like save), Spring wires it automatically |
- Spring creates a dynamic proxy for your repository interface (e.g.,
UserRepository) - That proxy delegates method calls to an internal instance of
SimpleJpaRepository<User, Long>(or another store-specific implementation)
| Component | Role |
|---|---|
SimpleJpaRepository |
The actual implementation class with real code (like how to do save(), findById(), etc.) |
| Proxy | The dynamic Spring-generated object that implements your interface (UserRepository) and delegates to SimpleJpaRepository |
Your UserRepository interface |
What you use in your service layer — defines the methods you want |
userRepository.save(user);This is what happens under the hood:
YourService -> UserRepository proxy -> SimpleJpaRepository.save() logic
"Will
SimpleJpaRepositoryonly have the methods that we define?"
No. SimpleJpaRepository has all CRUD methods like:
savefindByIdfindAlldeleteById- etc.
But if your interface (like MyBaseRepository) only declares save() and findById(), then only those two will be visible and usable in your code — even though the underlying SimpleJpaRepository knows how to do much more.
So:
- Your interface defines what's exposed
- Spring handles what's actually implemented behind the scenes
SimpleJpaRepositoryis a concrete class with full implementation.- Spring creates a proxy object for your custom repository (e.g.,
UserRepository) that delegates toSimpleJpaRepository. - You control what methods are exposed by defining them in your base interface (like
MyBaseRepository). - Even though
SimpleJpaRepositoryhas all CRUD logic, you can hide unused/dangerous methods by not including them in your interface.