Michael Koch
Michael Koch

Reputation: 1242

How to inject configuration in a custom Spring Data JPA repository

I want to add a custom bulk save method based on the code from JPA batch inserts with Hibernate & Spring Data to all Spring Data JPA repositories in my application. The Spring Data doc explains how this can be done with a custom repository base class like the one shown below. My question is how to set the batchSize property in the example below. Injection with @Value as shown below does not work.

@NoRepositoryBean
public interface BulkRepository<T, ID extends Serializable>
  extends JpaRepository<T, ID> {

  void bulkSave(Iterable<T> entities);
}

public class BulkRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BulkRepository<T, ID> {

  private final EntityManager entityManager;

  @Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
  private int batchSize;

  public BulkRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);   
     this.entityManager = entityManager;
  }

  public void bulkSave(Iterable<T> entities) {
    // implementation using batchSize goes here
  }
}

Do I have to use a custom JpaRepositoryFactoryBean to build a configured BulkRepositoryImpl? Or is there a more direct way?

Upvotes: 1

Views: 3131

Answers (2)

dimathe47
dimathe47

Reputation: 346

You can get it from EntityManager.

String defaultBatchSize = "100";
String batchSizeString = (String) entityManager.getEntityManagerFactory().getProperties()
                         .getOrDefault("hibernate.jdbc.batch_size", defaultBatchSize);
batchSize = Integer.parseInt(batchSizeString);

Upvotes: 0

Marco Schmidt
Marco Schmidt

Reputation: 36

I came across the exact same problem and could not find any way around defining my own JpaRepositoryFactoryBean class. It seems that the dependencies of the custom repository base class are not automatically injected the way they are in a standard bean (see here and here). Also, when creating instances of repository interfaces, the default JpaRepositoryFactory only passes instances of JpaEntityInformation and EntityManager to the class constructor (see here). This, as far as I can tell, effectively prevents you from including additional dependencies for classes extending SimpleJpaRepository.

I ended up defining the custom factory the following way:

@Configuration
@ConfigurationProperties(prefix = "spring.jpa.properties.hibernate.jdbc")
public class RepositoryConfiguration {
    private int batchSize;
}

public class MyCustomRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {

    private RepositoryConfiguration repositoryConfiguration;

    public MyCustomRepositoryFactoryBean(Class<? extends R> repositoryInterface, RepositoryConfiguration repositoryConfiguration) {
        super(repositoryInterface);
        this.repositoryConfiguration = repositoryConfiguration;
    }

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new MyCustomRepositoryFactory(entityManager, repositoryConfiguration);
    }

    private static class MyCustomRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {

        private RepositoryConfiguration repositoryConfiguration;

        MyCustomRepositoryFactory(EntityManager entityManager, RepositoryConfiguration repositoryConfiguration) {
            super(entityManager);
            this.repositoryConfiguration = repositoryConfiguration;
        }

        @Override
        @SuppressWarnings("unchecked")
        protected SimpleJpaRepository<?, ?> getTargetRepository(RepositoryInformation information, 
                EntityManager entityManager) {

            JpaEntityInformation<T, ?> entityInformation = 
                    (JpaEntityInformation<T, ?>) getEntityInformation(information.getDomainType());

            return new MyCustomRepositoryImpl<T, I>(
                    entityInformation, 
                    entityManager, 
                    repositoryConfiguration);
        }

        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return MyCustomRepositoryImpl.class;
        }
    }
}

While it is not possible to inject fields with @Value in MyCustomRepositoryFactoryBean either, Spring resolves the dependencies passed to the constructor, so you can just provide your properties through a bean (RepositoryConfiguration in the code above) and pass it down to MyCustomRepositoryImpl. Lastly, you will need to instruct Spring Data to use your FactoryBean class when creating repositories by adding

@EnableJpaRepositories(
        repositoryFactoryBeanClass = MyCustomRepositoryFactoryBean.class
)

to a @Configuration annotated bean. It's a lot of boilerplate, but it works.

N.B. I am using spring-data-jpa:1.11.8.RELEASE.

Upvotes: 2

Related Questions