0

I have a Maven project that represents the data layer of a single sign on (SSO) server. Other client applications shall rely on it for user authentication and authorization.

It is a .jar archive that shall be used by another REST project, also a Maven project.

Both are Spring Boot 2 based projects.

This project shall have some integration tests against the H2 and the MySQL databases.

As it is a library it does not run on its own. But it has some integration tests, that are to be executed. There is therefore no main class.

The command being used to build and execute the tests is mvn clean install -Denv="test"

My tree of source code looks like:

├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── thalasoft
    │   │           └── userdata
    │   │               ├── config
    │   │               │   ├── DatabaseConfiguration.java
    │   │               │   ├── JpaService.java
    │   │               │   └── properties
    │   │               │       ├── AbstractDatabaseProperties.java
    │   │               │       ├── DatabaseH2TestProperties.java
    │   │               │       ├── DatabaseMySQLAcceptanceProperties.java
    │   │               │       ├── DatabaseMySQLPreProdProperties.java
    │   │               │       ├── DatabaseMySQLProdProperties.java
    │   │               │       ├── DatabaseMySQLTestProperties.java
    │   │               │       ├── DatabaseOraclePreProdProperties.java
    │   │               │       ├── DatabaseOracleProdProperties.java
    │   │               │       ├── DatabaseOracleTestProperties.java
    │   │               │       ├── DatabaseProperties.java
    │   │               │       └── PropertyNames.java
    │   │               ├── dialect
    │   │               │   ├── CustomMySQL5InnoDBDialect.java
    │   │               │   └── CustomOracle10gDialect.java
    │   │               ├── exception
    │   │               │   ├── CannotDeleteEntityException.java
    │   │               │   ├── EnrichableException.java
    │   │               │   ├── EntityAlreadyExistsException.java
    │   │               │   ├── EntityNotFoundException.java
    │   │               │   └── NoEntitiesFoundException.java
    │   │               ├── jpa
    │   │               │   ├── domain
    │   │               │   │   ├── AbstractEntity.java
    │   │               │   │   ├── EmailAddress.java
    │   │               │   │   ├── User.java
    │   │               │   │   └── UserRole.java
    │   │               │   └── repository
    │   │               │       ├── GenericRepositoryImpl.java
    │   │               │       ├── GenericRepository.java
    │   │               │       ├── UserRepositoryCustom.java
    │   │               │       ├── UserRepositoryImpl.java
    │   │               │       ├── UserRepository.java
    │   │               │       ├── UserRoleRepositoryCustom.java
    │   │               │       ├── UserRoleRepositoryImpl.java
    │   │               │       └── UserRoleRepository.java
    │   │               └── service
    │   │                   ├── UserRoleServiceImpl.java
    │   │                   ├── UserRoleService.java
    │   │                   ├── UserServiceImpl.java
    │   │                   └── UserService.java
    │   └── resources
    │       ├── application.properties
    │       └── custom
    │           └── typedef.hbm.xml
    └── test
        ├── java
        │   └── com
        │       └── thalasoft
        │           └── userdata
        │               ├── assertion
        │               │   └── UserAssert.java
        │               ├── it
        │               │   ├── jpa
        │               │   │   ├── AbstractRepositoryTest.java
        │               │   │   ├── UserRepositoryTest.java
        │               │   │   └── UserRoleRepositoryTest.java
        │               │   └── service
        │               │       ├── AbstractServiceTest.java
        │               │       └── UserServiceTest.java
        │               └── ut
        │                   ├── AbstractRepositoryTest.java
        │                   └── UserRepositoryTest.java
        └── resources
            ├── h2
            │   └── data-source-test.properties
            ├── mysql
            │   ├── clean-up-before-each-test.sql
            │   └── data-source-test.properties
            └── oracle
                ├── data-source-preprod.properties
                └── data-source-test.properties

The DatabaseH2TestProperties is loaded thanks to a custom annotation define in another Maven toolbox project:

@EnvTest
@DbH2
@Configuration
@PropertySource({ "classpath:h2/data-source-test.properties" })
public class DatabaseH2TestProperties extends AbstractDatabaseProperties {

  private static Logger logger = LoggerFactory.getLogger(DatabaseH2TestProperties.class);

  public DatabaseH2TestProperties() {
    logger.debug("===========>> Loading the classpath h2/data-source-test.properties file");
  }
}

The data-source-test.properties file contains:

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.datasource.driver-class-name=net.sf.log4jdbc.DriverSpy
spring.datasource.url=jdbc:log4jdbc:h2:file:./target/useraccounttest
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.show-sql=true

The tests all are based on the class:

@ContextConfiguration(classes = { DatabaseConfiguration.class })
@RunWith(SpringRunner.class)
@Sql(executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, scripts = { "classpath:mysql/clean-up-before-each-test.sql" })
public abstract class AbstractRepositoryTest {
}

On this class, I wonder if I should not use @SpringBootTest(classes = { DatabaseConfiguration.class }) instead.

And the configuration is done with:

@EnableAutoConfiguration
@ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.userdata" })
public class DatabaseConfiguration {
}

The connection to the H2 database is successful:

02:23:56.299 [main] DEBUG jdbc.audit - 100. Connection.getMetaData() returned dbMeta74: conn99: url=jdbc:h2:file:./target/useraccounttest user=SA  com.zaxxer.hikari.pool.ProxyConnection.getMetaData(ProxyConnection.java:361)
02:23:56.299 [main] DEBUG org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator - Database ->
       name : H2
    version : 1.4.197 (2018-03-18)
      major : 1
      minor : 4
02:23:56.299 [main] DEBUG org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator - Driver ->
       name : H2 JDBC Driver
    version : 1.4.197 (2018-03-18)
      major : 1
      minor : 4
02:23:56.299 [main] DEBUG org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator - JDBC version : 4.0
02:23:56.299 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
02:23:56.305 [main] DEBUG jdbc.audit - 100. Connection.clearWarnings() returned   com.zaxxer.hikari.pool.ProxyConnection.close(ProxyConnection.java:250)

But I get an excetpion.

What the console log has to say:

02:23:56.338 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
02:23:56.338 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'com.thalasoft.userdata.config.JpaService'
02:23:56.338 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'com.thalasoft.userdata.config.properties.DatabaseH2TestProperties'
02:23:56.338 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.thalasoft.userdata.jpa.repository.GenericRepositoryImpl'
02:23:56.338 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'com.thalasoft.userdata.jpa.repository.GenericRepositoryImpl'
02:23:56.338 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
02:23:56.340 [main] WARN org.springframework.context.support.GenericApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.thalasoft.userdata.jpa.repository.GenericRepositoryImpl' defined in file [/home/stephane/dev/java/projects/user-data/target/classes/com/thalasoft/userdata/jpa/repository/GenericRepositoryImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.Class<?>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

Indeed, I have a custom generic repository:

@Repository
@Transactional
public class GenericRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
        implements GenericRepository<T, ID> {

    private EntityManager entityManager;

    private final Class<T> domainClass;

    public GenericRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
        this.domainClass = domainClass;
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }
}

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

  public EntityManager getEntityManager();
}

And it is used as:

public interface UserRepository extends GenericRepository<User, Long>, UserRepositoryCustom {
}

public interface UserRepositoryCustom {

    public User deleteByUserId(Long id) throws EntityNotFoundException;

}

public class UserRepositoryImpl implements UserRepositoryCustom {

    @Autowired
    private UserRepository userRepository;

}

It feels like the domain class cannot be found for the generic type.

Stephane
  • 11,836
  • 25
  • 112
  • 175

1 Answers1

0

Your GenericRepository interface and GenericRepositoryImpl class fit the pattern for repository fragment used in custom repository implementations in Spring Data JPA, so my guess is that Spring Data is trying to instantiate it for use as such. I would try renaming GenericRepositoryImpl to AbstractGenericRepository or making it an abstract class or perhaps both. You could also try removing @Repository annotation - after all class GenericRepositoryImpl is not a repository, but rather a base for implementing one.

Edit: From your code it seems you're using the GenericRepository to create repository with custom method for eleting user by id. That being said I belive this code would be sufficient:

public interface UserRepository extends JpaRepository<User, Long> {
    void deleteByUserId(Long id);
}

and if field userId in your User class is the one annotated as entity id, you do not even need the custom method since JpaRepository extends CrudRepository, which already has method deleteById(ID id).

Konrad Botor
  • 4,765
  • 1
  • 16
  • 26
  • I renamed it as `Abstract...` and added a `@EntityScan(basePackages = { "com.thalasoft.userdata.jpa" })` and a `@EnableJpaRepositories(basePackages = { "com.thalasoft.userdata.jpa" })` annotations but it now gives a `org.springframework.data.mapping.PropertyReferenceException: No property namedQuery found for type User` exception, and I don't know where is this `namedQuery` property coming from. Also, before using Spring Boot (using Spring 4) I used to have another class of mine called `GenericRepositoryFactoryBean` which I don't have anymore, and wonder if I need it. – Stephane Jun 26 '18 at 12:45
  • I removed my custom generic repository and directly use the `JpaRepository` one as you suggested, and the tests are finally running ! I now only get a `Unable to release JDBC Connection used for DDL execution` error but that is unrelated and further down the road I guess. – Stephane Jun 26 '18 at 13:21
  • I added a `;DB_CLOSE_ON_EXIT=FALSE` to my H2 url property as in `jdbc:log4jdbc:h2:file:./target/useraccounttest;DB_CLOSE_ON_EXIT=FALSE` and it solved the connection release issue mentioned in the above comment. – Stephane Jun 26 '18 at 13:28