0

I have 2 Eclipse projects and each one is has services managed by Spring. I use Spring Boot starter dependencies for each of them. Each one works properly, and can be tested with JUnit launched via SpringRunner.class and @SpringBootTest.

Now, I want to call some services from project 1 in project 2, so I add a dependency in project 2 pom.xml and I add @ComponentScan(basePackages="com.project1")

From then on, I can't launch any JUnit, it complains about dataSource not being set, like if configs where mixing randomly.

My question is : what are the recommended practices when you create a Spring Boot App and you want to isolate some features in a separate project (here XML features) ? If u can't have 2 spring boot app with one dependant of the other, what are the spring dependencies you need so the spring boot project can deal with the non spring boot dependency, and so that u can still launch JUnit using Spring runner locally ?

Do I need to pick Spring dependencies one by one (core, bean, context, test, log4j, slf4j, junit, hamcrest, ...) like before Spring boot exist to do this ?

See my comment on why the possible duplicate is different.

After removing all Spring boot dependencies from my module project, I still have the error as soon as I add the "ComponentScan" to scan the module services.

Here is my DB config (main project depending on a xml module) to be clear on the package config. This config WORKS perfectly until I add the ComponentScan on a package from the module project :

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages="fr.my.project.repository")
class PersistenceContext {

  private static final String[] ENTITY_PACKAGES = { "fr.my.project.model" };

  private static final String PROP_DB_DRIVER_CLASS = "db.driver";
  private static final String PROP_DB_PASSWORD = "db.password";
  private static final String PROP_DB_URL = "db.url";
  private static final String PROP_DB_USER = "db.username";

  private static final String PROP_HIBERNATE_DIALECT = "hibernate.dialect";
  private static final String PROP_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
  private static final String PROP_HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
  private static final String PROP_HIBERNATE_SHOW_SQL = "hibernate.show_sql";

  /**
   * Creates and configures the HikariCP datasource bean.
   * 
   * @param env
   *          The runtime environment of our application.
   * @return
   */
  @Bean(destroyMethod = "close")
  DataSource dataSource(Environment env) {
    HikariConfig dataSourceConfig = new HikariConfig();
    dataSourceConfig.setDriverClassName(env.getRequiredProperty(PROP_DB_DRIVER_CLASS));
    dataSourceConfig.setJdbcUrl(env.getRequiredProperty(PROP_DB_URL));
    dataSourceConfig.setUsername(env.getRequiredProperty(PROP_DB_USER));
    dataSourceConfig.setPassword(env.getRequiredProperty(PROP_DB_PASSWORD));

    return new HikariDataSource(dataSourceConfig);
  }

  /**
   * Creates the bean that creates the JPA entity manager factory.
   * 
   * @param dataSource
   *          The datasource that provides the database connections.
   * @param env
   *          The runtime environment of our application.
   * @return
   */
  @Bean
  LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, Environment env) {
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
    entityManagerFactoryBean.setDataSource(dataSource);
    entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    entityManagerFactoryBean.setPackagesToScan(ENTITY_PACKAGES);

    Properties jpaProperties = new Properties();

    // Configures the used database dialect. This allows Hibernate to create SQL
    // that is optimized for the used database.
    jpaProperties.put(PROP_HIBERNATE_DIALECT, env.getRequiredProperty(PROP_HIBERNATE_DIALECT));

    // Specifies the action that is invoked to the database when the Hibernate
    // SessionFactory is created or closed.
    jpaProperties.put(PROP_HIBERNATE_HBM2DDL_AUTO, env.getRequiredProperty(PROP_HIBERNATE_HBM2DDL_AUTO));

    // If the value of this property is true, Hibernate writes all SQL
    // statements to the console.
    jpaProperties.put(PROP_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROP_HIBERNATE_SHOW_SQL));

    // If the value of this property is true, Hibernate will use prettyprint
    // when it writes SQL to the console.
    jpaProperties.put(PROP_HIBERNATE_FORMAT_SQL, env.getRequiredProperty(PROP_HIBERNATE_FORMAT_SQL));

    entityManagerFactoryBean.setJpaProperties(jpaProperties);

    return entityManagerFactoryBean;
  }

  /**
   * Creates the transaction manager bean that integrates the used JPA provider with the Spring transaction mechanism.
   * 
   * @param entityManagerFactory
   *          The used JPA entity manager factory.
   * @return
   */
  @Bean
  JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
  }
}

and after adding :

@ComponentScan(basePackages="fr.my.module.xml.service")

I get this error when launching any Junit :

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.tomcat.jdbc.pool.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
Tristan
  • 8,733
  • 7
  • 48
  • 96
  • I have read the other similar questions and haven't found the answer to this question. – Tristan Apr 11 '18 at 13:48
  • Possible duplicate of [SpringBoot ComponentScan issue with multi-module project](https://stackoverflow.com/questions/30587377/springboot-componentscan-issue-with-multi-module-project) – Herr Derb Apr 11 '18 at 14:08
  • Depending on your configuration `@ComponentScan(basePackages="com.project1")` might not be enough. – Herr Derb Apr 11 '18 at 14:09
  • What I show is only the additional config to scan the services in the dependency project. In my main project, where I do have JPA things, I have specific specification of "packages" for entities and repositories, so the possible "duplicate" is not helpful. – Tristan Apr 11 '18 at 14:23
  • Exactly. And currently you probably lacking `@EnableJpaRepositories(basePackages= com.project1)` and `@EntityScan(basePackages= com.project1)` as described in the duplicate. `@ComponentScan(basePackages="com.project1")` does not do that itself. – Herr Derb Apr 11 '18 at 14:26
  • The please add your configuration to the question. Otherwise we need to guess. – Herr Derb Apr 11 '18 at 14:32
  • "I already have specific specification of "packages" for entities and repositories, so the possible "duplicate" is not helpful" like written above. You may finally read this sentence ;) – Tristan Apr 11 '18 at 14:33

2 Answers2

1

Here is a temporary answer on how to configure the dependency project, but I hope some easier way benefiting of Spring Boot shortcuts for all app modules exist.

  • pom.xml with manual minimal dependencies :

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.14.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.3.14.RELEASE</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.11</version>
    </dependency>
    

Manual test config :

@RunWith(SpringRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes=AppConfig.class)
public class XmlTest {

Manual app config :

@Configuration
@ComponentScan(basePackages="my.package.xml")
public class AppConfig {

}

Sooooooo after doing all these tries, Spring Boot may not be the cause of this problem at all.

The thing is I was adding @ComponentScan(basePackages="fr.package.xml") hoping to complete the default package scanning, but it was overriding it.

The proper way to add a package, is to redeclare explicitely the default package before adding the new package : @ComponentScan(basePackages={"fr.package.xml", "fr.package.persistence"})

Tristan
  • 8,733
  • 7
  • 48
  • 96
0

My other answer was about setting up manual minimal dependencies for a module in a Spring Boot app. But here is an example of using Spring boot special dependencies in the module which is not the main app :

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Then, you don't declare "@SpringBootApplication" in a main class in src/main/java where it may break the global packaging, but you set it up inside your test class :

@RunWith(SpringRunner.class)
@SpringBootTest("service.message=Hello")
public class MyServiceTest {

    @Autowired
    private MyService myService;

    @Test
    public void contextLoads() {
        assertThat(myService.message()).isNotNull();
    }

    @SpringBootApplication
    static class TestConfiguration {
    }

}

source : https://github.com/spring-guides/gs-multi-module/tree/master/complete

Tristan
  • 8,733
  • 7
  • 48
  • 96