Mathias Hauser

My opinions and thoughts on various things and experiences of software development

How to configure Spring Boot with JPA and multiple projects

Its a common case to split up a project into multiple sub projects with a project containing classes used in multiple projects.
This is a fairly simple task related to dependency management, but can lead to problems in combination with Spring and JPA. I will explain you in this post what needs to be done to get everything up and running.

I recommend downloading the source of this example before you start. Link to the repository blog_springBoot-jpa-multiProject direct link to the source zip source-zip

Content of this post

  • Project structure & Gradle setup – A minimal setup to show that the approach works
  • JPA Entity and Repository – A JPA Test entity and Spring Data JPA repository for db interaction
  • Spring Boot Application configuration class – Set the Spring scan packages
  • Database configuration – Set the JPA scan package

Project structure & Gradle setup

I will use for this example a simple setup with two projects – common and core. The common project is in this case a project used by at least one other project. There will be a JPA entity and the corresponding Spring Data JPA repository in this example. The core project will be an executeable project with a simple Spring Boot Application using the JPA entity and repository from the common project.
The following block shows the folder structure related to project folders and the gradle files. This is followed by a simple explanation of the gradle files without to much detail.

root
- common
--- build.gradle
- core
--- build.gradle
- build.gradle
- settings.gradle

Gradle

The gradle setup of this multi project configuration is not the focus of this post, but there’s nothing spectacular in it. I will give a quick overview of the necessary files, more details can be seen in the gradle documentation -> link

This “build.gradle” file is in the root of the project. This file counts for gradle as some sort of project too so there are some things defined just for all sub projects (common and core) f.e. Java 8 and a constant for the Spring Boot Version

allprojects {
	apply plugin: 'idea'
	apply plugin: 'eclipse'
	
	group = 'mh.dev.blog'
	version = '0.0.1-SNAPSHOT'
}

subprojects {

	apply plugin: 'java'
	
	sourceCompatibility = 1.8
	targetCompatibility = 1.8 
	
	project.ext {
		springBootVersion = '1.1.6.RELEASE'
	}
	
	repositories {
		mavenLocal()
		mavenCentral()
	}
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.1'
}

The “settings.gradle” file includes all sub projects. These are in our case the core and common project.

include 'core', 'common'

This is the “build.gradle” file for the common project based on the Spring Boot documentation with just Spring Boot Starter Data JPA as a dependency.

buildscript {
	repositories {
		maven { url "http://repo.spring.io/libs-release" }
		mavenLocal()
		mavenCentral()
	}
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:$project.ext.springBootVersion")
    }
}
apply plugin: 'spring-boot'

description = 'common'

dependencies {
    compile("org.springframework.boot:spring-boot-starter-data-jpa:$project.ext.springBootVersion")
}

The last “build.gradle” file is responsible for the core project is pretty much the same as the version from the common project with some additional dependencies plus the common project as a dependency.

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-release" }
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:$project.ext.springBootVersion")
    }
}
apply plugin: 'spring-boot'

description = 'core'

dependencies {
    compile("org.springframework.boot:spring-boot-starter-data-jpa:$project.ext.springBootVersion")
    compile("org.yaml:snakeyaml")
    compile("com.h2database:h2")
    compile project(":common")
}

JPA Entity and Repository

The following two classes are located in the common project and are responsible for data access.

The first is the Test class, it is just a simple JPA entity with just an id and a text field and some getters and setters.

package mh.dev.blog.multi.common.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Test {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String text;

    // Getter & Setter
}

The second class is the TestRepository class. It is an extension of the CrudRepository class of Spring Data JPA containing generic methods like save of findAll.

package mh.dev.blog.multi.common.repository;

import mh.dev.blog.multi.common.model.Test;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TestRepository extends CrudRepository<Test, Long> {
}

Application configuration class

Now we come to the real important part, the configuration of the Spring Boot Application. This is standard for the most part, but the two important lines are highlighted in this example.
The first highlighted line configures the component scan of Spring to use the give String as the base package. Keep in mind that I use “mh.dev.blog.multi” as a base package for both applications. You should do the same when using a multi project setup and just add another sub package for each project (“mh.dev.blog.multi.common” and “mh.dev.blog.multi.core” in my case). The second highlighted line tells Spring Data JPA the location of your repository classes. This is in my example “mh.dev.blog.multi.common.repository”. Imporant is here that it might be possible that you have some repository classes in the current project too in that case you must add another entry to that array (f.e. “mh.dev.blog.multi.core.repository”).
The rest of this class is just requesting a TestRepository bean, save a Test entity and print all saved Test entities to the command line.

package mh.dev.blog.multi.core;

import mh.dev.blog.multi.common.model.Test;
import mh.dev.blog.multi.common.repository.TestRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.io.IOException;

@ComponentScan("mh.dev.blog.multi")
@Configuration
@EnableJpaRepositories(basePackages = {"mh.dev.blog.multi.common.repository"})
@EnableAutoConfiguration
@EnableTransactionManagement
public class Application {

    public static void main(String[] args) throws IOException {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class);
        TestRepository repository = context.getBean(TestRepository.class);
        Test test = new Test();
        test.setText("test");
        repository.save(test);
        repository.findAll().iterator().forEachRemaining(f -> System.out.println(f));
        context.close();
    }
}

Database configuration class

The last thing we need to do is to add the database configuration. You can add this class to the core or common project. The advantage in the common project is it can be used by multiple projects using common as a dependency. Important is here that the class is annotated with @Configuration to allow Spring Boot to auto discover this configuration class.
The entityManagerFactory method is responsible for creating a custom EntityManagerFactory. You will copy this class for the most part into any multi module project. With one exception in line 40. There is the configuration of the location of your model classes. I use in this example the project root package, but you can add a list of packages via this method.

package mh.dev.blog.multi.common.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.orm.jpa.SpringNamingStrategy;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;

import javax.sql.DataSource;
import java.util.Map;

@Configuration
public class DatabaseConfiguration implements EnvironmentAware {

    private RelaxedPropertyResolver jpaPropertyResolver;
    @Autowired(required = false)
    private PersistenceUnitManager persistenceUnitManager;

    @Override
    public void setEnvironment(Environment environment) {
        this.jpaPropertyResolver = new RelaxedPropertyResolver(environment, "spring.jpa.");
    }

    @Bean
    @DependsOn("jdbcTemplate")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        if (persistenceUnitManager != null) {
            entityManagerFactoryBean
                    .setPersistenceUnitManager(persistenceUnitManager);
        }
        entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
        entityManagerFactoryBean.setDataSource(dataSource);
        entityManagerFactoryBean.setPackagesToScan("mh.dev.blog.multi");
        entityManagerFactoryBean.getJpaPropertyMap().putAll(jpaPropertyResolver.getSubProperties("properties."));
        Map<String, Object> properties = entityManagerFactoryBean.getJpaPropertyMap();
        properties.put("hibernate.ejb.naming_strategy", jpaPropertyResolver.getProperty("hibernate.naming-strategy", SpringNamingStrategy.class.getName()));
        properties.put("hibernate.hbm2ddl.auto", jpaPropertyResolver.getProperty("hibernate.ddl-auto", "none"));
        return entityManagerFactoryBean;
    }
}

Questions

Feel free to ask any questions about this post and I will try to answer them in the comments or via mail -> dev[at]mathias-hauser.at.
You can also send me a mail if you have a suggestion for another post.

Advertisements

13 responses to “How to configure Spring Boot with JPA and multiple projects

  1. Mathew November 24, 2014 at 6:53 pm

    Hi,
    I downloaded the sample and got the below when I ran gradlew build at the root.

    * What went wrong:
    Execution failed for task ‘:common:bootRepackage’.
    > Unable to find main class

  2. mhdevelopment November 24, 2014 at 8:12 pm

    Hi, Mathew and thanks for your question.
    The problem is that the root of the project is not made to be build, its just the “parent” module. The common project on its own can also not be executed. Only the core project is an actual executable project. So if you want to build this project just execute “gradlew :core:build” in the root folder.
    Another possibility is to directly execute the core application by executing “gradlew :core:bootRun”.
    I hope this answer helps you 🙂

  3. mpiotrow November 28, 2014 at 2:40 pm

    In your common subproject build.gradle file you can add

    springBoot {
    bootRepackage.enabled = false
    }

  4. ray January 14, 2015 at 9:23 pm

    Incase I need to add Spring-Batch would you create new gradle module for it or add it into common? thanks.

  5. mhdevelopment January 14, 2015 at 9:55 pm

    The purpose of this article is to show how to deal with JPA in multi module scenarios. A common module on its own should never be executeable on its own (my opinion). So I would not recommend to do batch processing in there. The reason for this is that if you do it each sub module would contain this batch processing. But you can add the dependency and some generic code to it if you plan to use batch processing in all projects depending on the common module .

    A more realistic scenario is that you have an application and you need add some batch processing to it. Than you can separate the batch processiong into another module.

  6. ray January 14, 2015 at 10:16 pm

    How would you switch easily with spring-boot to mysql?? could you refer to any example thank you

  7. Ben October 21, 2015 at 8:18 pm

    Great article Mathias. Thanks a ton.

    For hibernate.ddl-auto property I had to use “create”. “none” would prevent application startup with SQL errors complaining that tables did not exist.

    Using Spring Boot 1.2.7.RELEASE

    Where did you learn about how to configure the DatabaseConfiguration class? It looks massively complex.

    • mhdevelopment November 2, 2015 at 8:12 pm

      A relative late answer from my side, but in case you read still read it.
      Well that’s usually a bit difficult. It’s a while since I wrote this post, but the general problem does still occur. A lot in the web about spring is based on XML configuration. So you often unable to find anything useful as bean configuration. In that case you have to transform XML configuration to a bean based. I don’t remember how it was exactly with that case, but I have a deeper knowledge of how JPA works maybe this was useful.

      I just can advise you to try to understand the different ways how to declare a bean and the ways how to get the instance. The next step is to really understand the @Configure and @Import mechanic, since it is important for modularization. In case you are using hibernate, learn it, live it, love it. If you’re not following the first to steps you may hate it. I probably could write books about this, I was considering writing a simple game with hibernate pitfalls, but do not have the time right now to do it.

  8. visitor January 18, 2016 at 12:36 pm

    Thank you for your helpful article!
    fyi, as per Spring-Boot-1.2, the SpringNamingStrategy class has been moved to the org.springframework.boot.orm.jpa.hibernate package.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: