0

I have created a custom repository to override persistence methods and tried to wire it up as described in the spring docs. I receive no errors, all entities and repositories are found on startup, and when I call repo.saveAll(entities), persistence works normally. However, my custom code is never called. I have added log statements and even thrown RuntimeExceptions in my code, just to see if it's being executed, but it's definitely being ignored. What step have I missed?

@Configuration
@Profile("test")
@EnableJpaRepositories(repositoryBaseClass = SetClientInfoRepositoryImpl.class,
        basePackages = {"gov.penndot.hwy.apras.common.repository" }, 
        entityManagerFactoryRef = "serviceEntityManagerFactory", 
        transactionManagerRef = "serviceTransactionManager")
public class TestDatabaseConfig {

    @Bean(name = "serviceDataSource")
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
        dataSource.setUsername("sa");
        dataSource.setPassword("sa");

        return dataSource;
    }

    @Bean
    public EntityManagerFactoryBuilder entityManagerFactoryBuilder() {
        return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), new HashMap<String, Object>(), null);
    }

    @Bean(name = "serviceEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean serviceEntityManagerFactory(EntityManagerFactoryBuilder builder,
            @Qualifier("serviceDataSource") DataSource dataSource) {

        return builder
                .dataSource(dataSource)
                .packages("stuff")
                .persistenceUnit("service")
                .build();
    }

    @Bean(name = "serviceTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("serviceEntityManagerFactory") EntityManagerFactory serviceEntityManagerFactory) {
        return new JpaTransactionManager(serviceEntityManagerFactory);
    }

Repository:

@NoRepositoryBean
    public class SetClientInfoRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> {
        private static final Logger log = LoggerFactory.getLogger(SetClientInfoRepositoryImpl.class);
        private final EntityManager em;

        public SetClientInfoRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
            super(entityInformation, entityManager);
            this.em = entityManager;
        }

        @Transactional
        @Override
        public <S extends T> S save(S entity) {
            setClientInfo();
            return super.save(entity);
        }

        @Transactional
        @Override
        public void deleteById(ID id) {
            setClientInfo();
            super.deleteById(id);
        }

        @Transactional
        @Override
        public void delete(T entity) {
            setClientInfo();
            super.delete(entity);
        }

        @Transactional
        @Override
        public void deleteAll(Iterable<? extends T> entities) {
            setClientInfo();
            super.deleteAll(entities);
        }

        @Transactional
        @Override
        public void deleteInBatch(Iterable<T> entities) {
            setClientInfo();
            super.deleteInBatch(entities);
        }

        @Transactional
        @Override
        public void deleteAll() {
            setClientInfo();
            super.deleteAll();
        }

        @Transactional
        @Override
        public void deleteAllInBatch() {
            setClientInfo();
            super.deleteAllInBatch();
        }

        @Transactional
        @Override
        public <S extends T> S saveAndFlush(S entity) {
            setClientInfo();
            return super.saveAndFlush(entity);
        }

        @Transactional
        @Override
        public <S extends T> List<S> saveAll(Iterable<S> entities) {
            setClientInfo();
            super.saveAll(entities);
            throw new RuntimeException("foo");
        }

        private void setClientInfo() {
            log.debug("Entering setClientInfo method");
            [stuff]
        }
    }
GeneO
  • 3
  • 3
  • To confirm, you're calling the saveAll() method in SetClientInfoRepositoryImpl and not some other class? The typical thing to do here is create an interface for the SetClientInfoRepository, Autowire/Inject that repository wherever you need it, and then call the saveAll() method using that interface. https://stackoverflow.com/questions/13036159/spring-data-override-save-method – Derek Mar 11 '19 at 17:46
  • Are you sure your main configuration class, where you use the `@EnableJpaRepositories` is annotated with `@Profile("!test")` ? – Selindek Mar 11 '19 at 18:32
  • @Derek - (thank you for replying!) I am calling the saveAll() method on a different interface annotated as a Repository. Why do I need another interface, when the method is the same, and the spring docs do not call for one? I have used a custom interface when custom methods are required – GeneO Mar 11 '19 at 19:27
  • @Selindek - (thank you for replying!) Yes, the profile is not the issue. The correct database config is being loaded into spring, because all of the persistence is happening in the h2 db as defined in the config class – GeneO Mar 11 '19 at 19:29
  • OK, then let's be sure than the original `@EnableJpaRepositories` is not picked: comment it out and run the test that way. – Selindek Mar 12 '19 at 16:52
  • @Selindek: Commented out, but same behavior. I have edited to include the full database config class (I have multiple DBs in this service, so required multiple configs.) Could one of the instantiations in this class be overwriting the annotated repositoryBaseClass? When I debug the getTargetRepository method in Spring, it is definitely using SimpleJpaRepository as the base class – GeneO Mar 12 '19 at 19:31

1 Answers1

0

OK, this is quite a desperate idea, but it could worth a try...

Create a custom repository interface:

public interface SetClientInfoRepository<T, ID> extends JpaRepository<T, ID> {
}

Implement this repository interface by your custom base repository:

public class SetClientInfoRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements SetClientInfoRepository<T, ID> {
}

... and finally extend this interface by your repository interfaces instead of JpaRepository

In this way Spring has to create the repository-proxies from your implementation because there are no other classes it could use. Also, if it cannot create the repositories because of any reason, you will get a more informative exception during startup.

Using the custom repository interface is not a bad thing in its own, because there is always a good chance that you want to add some common custom methods to your repositories later and then it will come handy.

Selindek
  • 3,269
  • 1
  • 18
  • 25
  • Thanks for all of your help! Your answer did work. I created a new simple project and got the original baseRepositoryClass annotation working correctly, so I'm sure I must have config screwed up somewhere – GeneO Mar 13 '19 at 14:51
  • I believe that Spring Data JPA (and Spring Data Rest) are (one of) the most complex Spring packages. I wouldn't be surprised if this issue happens because of a bug in it. – Selindek Mar 13 '19 at 15:13