javaspring-boothibernatejpaspring-data-jpa

Using LocalContainerEntityManagerFactoryBean.setManagedTypes instead of @EntityScan (Spring Boot)


When using @EntityScan all entities in the specified basePackages are picked up by the scan. For certain reasons, we would like to avoid that.

We had the idea to replace the @EntityScan annotation with manual configuration to include only specific entity classes, instead of every entity in the package, by calling factoryBean.setManagedTypes(managedTypes).

There is one baeldung post that goes into this direction, but the focus is not on setting single managedTypes.

Actually, we were surprised that we couldn't find any blogpost, stackoverflow question, etc. dealing with the question "Is it possible to configure @EntityScan or an alternative to only track single classes, not full packages?".

Are there any issues or drawbacks in doing so, in a Spring Boot application?

The javadoc of LocalContainerEntityManagerFactoryBean.setManagedTypes even says:

Set the PersistenceManagedTypes to use to build the list of managed types as an alternative to entity scanning. -- https://docs.spring.io...orm/jpa/LocalContainerEntityManagerFactoryBean

I created an example project to demonstrate the idea: https://github.com/frankkriegl-zero/jpa-managed-types-sample/tree/stackoverflow-question-79541611

From the configuration class JpaConfig:

@Configuration
public class JpaConfig {

List<String> sharedManagedEntityClassNames = Stream.of(
            external.model.Book.class // located in a 'remote' package, outside of org.example.bookstore
    ).map(Class::getName).toList();

List<String> managedEntityPackages = List.of(
            "org.example.bookstore.model"
    );

@Bean(name = "persistenceManagedTypes")
PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
    var scanResult = new PersistenceManagedTypesScanner(resourceLoader)
            .scan(managedEntityPackages.toArray(new String[0]));

    // merge the shared managed entity class names with the scanned ones
    List<String> managedClassNames = new ArrayList<>();
    managedClassNames.addAll(sharedManagedEntityClassNames);
    managedClassNames.addAll(scanResult.getManagedClassNames());

    return PersistenceManagedTypes.of(managedClassNames, Collections.emptyList());
}

// entityManagerFactoryBean

}

This is how we override the entityManagerFactory bean:

@Bean(name = "entityManagerFactory")
public EntityManagerFactory entityManagerFactory(DataSource dataSource,
                                                 PersistenceManagedTypes managedTypes) {
    LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

    factoryBean.setManagedTypes(managedTypes); // alternative to @EntityScan and @EnableJpaRepositories

    factoryBean.afterPropertiesSet();
    return factoryBean.getObject();
}

We do not feel too confident about creating our own emFactory bean, as this is the backbone of every JPA related logic in a JPA-based application:

  1. We are uncertain if it could create additional maintenance effort in case the framework implementations (Spring Boot or Hibernate) change here.
  2. Since this is a critical component with rather complex configuration settings, we are somewhat afraid of overseeing something and break the application's persistence layer.

Any advice or experience with using setManagedTypes(PersistenceManagedTypes) instead of @EntityScan?

Thanks!


Solution

  • If my memory serves me right that could be implemented using following way:

    @Bean
    public HibernatePropertiesCustomizer extraEntities() {
        return map -> {
            List<Class> extra = new ArrayList<>();
            extra.add(MyEntity.class);
            var existing = map.get(AvailableSettings.LOADED_CLASSES);
            if (existing != null) {
                extra.addAll((Collection<Class>) existing);
            }
            map.put(AvailableSettings.LOADED_CLASSES, extra);
        };
    }