My Avatar

Shilong ZHAO

Spring Boot + Spring Data JPA + H2 + Web Application Example

2017-03-29 00:00:00 +0200

In case you have any questions or suggestions, you can leave comments HERE . Thanks!

In this post, we will develop a simple web application with Spring Boot, Spring Data JPA and H2. We choose to use Spring Boot since it saves us from configuring the application context like DataSource and EntityManager. The application will be packaged as JAR in local development and will be packaged as WAR when being deployed in remote server thanks to the profile management of Spring. Also the application will provide a RESTful interface with help of Spring Data REST.

First we show the project structure

We have used some tricks in pom.xml so that a JAR is generated in local environment instead of WAR. And also the scope of dependency spring-boot-starter-tomcat is set to provided so that the embedded Tomcat is excluded, makes our development environment equal with deployment.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>it.dreamhack</groupId>
    <artifactId>boot-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- deploy as JAR in local environment and WAR in deployment -->
    <packaging>${artifact-packaging}</packaging>

    <name>${project.artifactId}</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <startClass>MainApplication</startClass>
    </properties>

    <dependencies>

        <!-- Databases: H2 embedded data base -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- deploy war to external container, not in embedded container -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- typical WEB MVC + embedded container support. -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- freemarker template support -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <!-- For restful service -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <profiles>

        <profile>
            <!-- used on localhost machine -->
            <id>local</id>
            <activation>
                <!-- default profile -->
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <!-- run as jar -->
                <artifact-packaging>jar</artifact-packaging>
            </properties>
        </profile>
        <!-- The deployment profile-->
        <profile>
            <id>remote</id>
            <properties>
                <artifact-packaging>war</artifact-packaging>
            </properties>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <scope>provided</scope>
                </dependency>
            </dependencies>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <configuration>
                            <!-- this will get rid of version info from war file name -->
                            <finalName>${project.artifactId}</finalName>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>

    </profiles>
</project>

Our main application class is annotated with @SpringBootApplication and extends class SpringBootServletInitializer, which is

An opinionated WebApplicationInitializer to run a SpringApplication from a traditional WAR deployment. Binds Servlet, Filter and ServletContextInitializer beans from the application context to the servlet container.

To configure the application either override the configure(SpringApplicationBuilder) method (calling SpringApplicationBuilder.sources(Object…)) or make the initializer itself a @Configuration.

The MainApplication source is shown below, and we have left out the import statements in all code snippets in this post to save space. The MainApplication must in the top level/root package in order to be able to scan other beans.

package it.dreamhack;

@SpringBootApplication(scanBasePackages = {"it.dreamhack"})
public class MainApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(MainApplication.class);
    }
}

The a trivial User model is defined in it.dreamhack.model.User.java

package it.dreamhack.model;

@Entity
public class User {
    @Id @GeneratedValue(strategy= GenerationType.AUTO)
    private long userId;

    private String name;

    protected User() {}

    public User(String name) {
        this.name = name;
    }

    public long getUserId() {
        return userId;
    }

    public void setUserId(long uesrId) {
        this.userId = uesrId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return String.format("Customer[userId = %d, name = %s]", userId, name);
    }
}

We have written two different repositories, namely UserRepository and UserRestRepository. Note that in Spring Data JPA, we do not have to implement the data access methods at all. The methods are parsed by the names and are AUTOMATICALLY generated!

package it.dreamhack.service;

@Repository
public interface UserRepository extends CrudRepository<User, Long>{
    User findByUserId(Long id);
}

and

package it.dreamhack.service;

@RepositoryRestResource(collectionResourceRel = "user", path = "user")
public interface UserRestRepository extends PagingAndSortingRepository<User, Long> {
    List<User> findByName(@Param("name") String name);
}

Here, we may also want to define the base path for RESTful service, and this can be specified in the application.properties.

spring.datasource.driver-class-name=org.h2.Driver

spring.datasource.url=jdbc:h2:~/test

# Show or not log for each sql query
spring.jpa.show-sql = true

# Hibernate ddl auto (create, create-drop, update), so that Hibernate will read schema.sql & data.sql
spring.jpa.hibernate.ddl-auto = none

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

# base path for RESTFUL Service
spring.data.rest.base-path=/rest

In the end, we write our controller. If @RestController is used instead of @Controller the the controller class will return Json instead of HTTP pages.

package it.dreamhack.controller;

@Controller
public class HelloController {

    @Autowired
    private UserRepository userRepository;

    @RequestMapping("/")
    public String index() {
        return "greeting";
    }

    @RequestMapping(value = "/getUser/{id}", method = RequestMethod.GET)
    public String getUserInfo(@PathVariable(value = "id") Long id, ModelMap modelMap) {
        User user = userRepository.findByUserId(id);
        if(user == null) return "error";
        modelMap.addAttribute("username", user.getName());
        return "user";
    }
}

The schema.sql and data.sql should also be provided, so that Hibernate can automatically load and insert user entries.

You may need to configure you IDE to run the project. If IntelliJ Idea is used:

To use the application, simply visit http://localhost:8080 or http://localhost:8080/getUser/1 for example. And to access the REST interface, you can run

curl http://localhost:8080/rest/users

which will list all users. Or to get user by ID

curl http://localhost:8080/rest/users/1

Or if you want to add users by Json run

curl -i -X POST -H "Conte"name\":\"zhao\"}" http://localhost:8080/rest/users

The source code is available at GitLab.