skip to content

Walk through how @EnableJpaRepositories turns your repository interfaces into beans. What is the role of RepositoryFactoryBean?

level: seniorimportance: should knowfreq 48%

answer

  1. @EnableJpaRepositories -> @Import registrar
  2. Registrar scans basePackages, registers BeanDefinitions
  3. beanClass = JpaRepositoryFactoryBean (a FactoryBean)
  4. getObject() returns the proxy
  5. bootstrapMode: DEFAULT/LAZY/DEFERRED

basics

~20 s

@EnableJpaRepositories imports a registrar that scans the base packages for repository interfaces. For each one it registers a bean definition whose class is JpaRepositoryFactoryBean. That FactoryBean, at startup, uses a RepositoryFactory to build and hand back the proxy as the actual bean.

solid answer

~40 s

`@EnableJpaRepositories` is a meta-annotation that `@Import`s `JpaRepositoriesRegistrar`, an `ImportBeanDefinitionRegistrar`. At configuration time it scans the configured `basePackages` for interfaces extending a `Repository` marker. For each discovered interface it registers a `BeanDefinition` whose bean class is `JpaRepositoryFactoryBean`, with the repository interface passed as a constructor/property. `JpaRepositoryFactoryBean` is a Spring `FactoryBean<T>`: during bean instantiation its `afterPropertiesSet` creates a `JpaRepositoryFactory` (a `RepositoryFactorySupport`), and `getObject()` returns the generated proxy. So the *bean definition* is a factory-bean, but the *bean you inject* is the proxy the factory produces. Spring Boot's `JpaRepositoriesAutoConfiguration` applies the same annotation automatically if you don't declare it. `bootstrapMode` controls whether proxy creation is eager or deferred/lazy.

code

java · 19 lines
java
@Configuration
@EnableJpaRepositories(
    basePackages = "com.acme.user",
    bootstrapMode = BootstrapMode.DEFERRED,          // instantiate on background executor
    repositoryImplementationPostfix = "Impl",        // UserRepositoryImpl -> custom fragment
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ASSIGNABLE_TYPE, classes = MongoUserRepository.class))
public class UserPersistenceConfig {
    // No repository beans declared by hand.
    // JpaRepositoriesRegistrar registers one JpaRepositoryFactoryBean
    // BeanDefinition per discovered interface under com.acme.user.
}

// The injected bean is the proxy produced by the factory bean's getObject():
@Service
class UserService {
    private final UserRepository repo;              // type = interface
    UserService(UserRepository repo) { this.repo = repo; }   // proxy injected
}

go deeper

for a junior

Likely only knows 'it scans and creates beans'.

for a middle

Can name @EnableJpaRepositories scanning and that a FactoryBean is involved.

for a senior

Distinguishes registrar vs FactoryBean vs factory engine and explains bootstrapMode and multi-store filtering.

for a principal

Reasons about startup-latency vs fail-fast trade-offs, deferred bootstrap executors, and multi-store package hygiene at scale.

**Step 0 — the annotation.** `@EnableJpaRepositories` sits on a `@Configuration` class (or is contributed by Spring Boot auto-config). It is a *meta-annotation* carrying `@Import(JpaRepositoriesRegistrar.class)`. `@Import` of an `ImportBeanDefinitionRegistrar` is Spring's hook for *programmatically registering bean definitions* during context refresh. **Step 1 — scanning.** `JpaRepositoriesRegistrar` (via the shared `RepositoryBeanDefinitionRegistrarSupport` / `RepositoryConfigurationDelegate` machinery in spring-data-commons) reads the annotation attributes: `basePackages` (defaults to the package of the annotated class), `includeFilters`, `excludeFilters`, `repositoryImplementationPostfix` (default `Impl`), `queryLookupStrategy`, `bootstrapMode`, etc. It performs a classpath scan for **interfaces** that extend a Spring Data `Repository` marker interface and are not excluded. **Step 2 — one bean definition per interface.** For every repository interface found, it registers a `RootBeanDefinition` whose **beanClass is `JpaRepositoryFactoryBean`** (the store-specific `RepositoryFactoryBeanSupport` subclass). The repository interface type is supplied to that factory bean (constructor arg), along with references to the `EntityManager`, `queryLookupStrategy`, `namedQueries`, evaluation context, etc. Crucially, the bean *name* is derived from the interface (e.g. `userRepository`) and its *type* is the interface — so injection by interface type works. **Step 3 — FactoryBean produces the proxy.** A `FactoryBean<T>` is a Spring bean that acts as a *factory*: when someone asks the container for the bean, Spring calls `factoryBean.getObject()` and hands back *that*, not the factory itself. `RepositoryFactoryBeanSupport`: - implements `InitializingBean`; in `afterPropertiesSet()` it builds a `RepositoryFactorySupport` (for JPA, `JpaRepositoryFactory`), configures post-processors, query lookup strategy, named queries, and (unless lazy) eagerly creates the repository proxy. - `getObject()` returns the proxy; `getObjectType()` returns the repository interface. So three distinct things are easy to conflate: - **`JpaRepositoriesRegistrar`** — registers bean *definitions* (config-time). - **`JpaRepositoryFactoryBean`** — the FactoryBean *bean* per interface. - **`JpaRepositoryFactory` / `RepositoryFactorySupport`** — the engine that *builds the proxy*. **Spring Boot.** You usually don't write `@EnableJpaRepositories` — `JpaRepositoriesAutoConfiguration` applies it (base package = your main application class's package) when a JPA repository is on the classpath and none is manually configured. Declaring your own `@EnableJpaRepositories` turns the auto-config off and lets you set base packages explicitly. **bootstrapMode (edge case / gotcha).** `@EnableJpaRepositories(bootstrapMode = ...)`: - `DEFAULT`: proxies created eagerly during context refresh (fail-fast, slower startup). - `LAZY`: bean definitions marked lazy — proxy created only on first injection/use. - `DEFERRED`: definitions created eagerly but instantiation happens on a background bootstrap executor, so startup isn't blocked. Requires an async `bootstrapExecutor`. These trade *startup latency* against *fail-fast validation*. **Other gotchas.** - Wrong `basePackages` ⇒ interface never scanned ⇒ no bean ⇒ 'no qualifying bean' at injection. - Multiple stores (JPA + Mongo) in overlapping packages need `includeFilters`/`excludeFilters` so each `@Enable…Repositories` only claims its own interfaces; otherwise one store tries to proxy another's interface. - The `repositoryImplementationPostfix` (default `Impl`) governs how custom-fragment classes (`UserRepositoryImpl`) are discovered and composed into the proxy.

  • What is the difference between JpaRepositoryFactoryBean and JpaRepositoryFactory?
    JpaRepositoryFactoryBean is the Spring FactoryBean registered per interface; it holds config and returns the proxy via getObject(). JpaRepositoryFactory (a RepositoryFactorySupport) is the engine it uses to actually build that proxy.
  • In Spring Boot you rarely write @EnableJpaRepositories — why does it still work?
    JpaRepositoriesAutoConfiguration applies @EnableJpaRepositories for you, defaulting the base package to your @SpringBootApplication's package. Declaring your own annotation disables that auto-config and takes over base-package configuration.
  • What does bootstrapMode=DEFERRED buy you and what does it cost?
    It moves proxy instantiation to a background bootstrap executor so context refresh isn't blocked (faster perceived startup), at the cost of deferring the fail-fast validation of query methods to that background task/first use.

saying these in an interview costs you the question

  • Saying the registrar creates the proxies (it only registers bean definitions)
  • Confusing the FactoryBean (per interface) with the RepositoryFactorySupport engine
  • Believing @EnableJpaRepositories is always required even under Spring Boot

context