Spring Boot H2 datasource with Thymeleaf using Gradle

In this example, we will see how to build a simple web application in Spring boot with Thymeleaf as template engine and H2 database as embedded or in-memory database. Gradle has been used as build and dependency management tool.

We will create a sample Student Information System where we can enter Student record in one panel and see the existing records in another panel. Here is a screenshot of the web page for this:

Student Information System

The sample application is developed by the following source files:

  1. build.gradle
  2. SpringBootH2Example.java
  3. StudentController.java
  4. Student.java
  5. StudentRepository.java
  6. StudentService.java
  7. student.html
  8. layout.html
  9. application.properties
The files are placed as per following folder structure:
Root
│   build.gradle
│
└───src
    └───main
        ├───java
        │   └───com
        │       └───firstfewlines
        │           │   SpringBootH2Example.java
        │           │
        │           ├───controller
        │           │       StudentController.java
        │           │
        │           ├───domain
        │           │       Student.java
        │           │
        │           ├───repository
        │           │       StudentRepository.java
        │           │
        │           └───service
        │                   StudentService.java
        │
        └───resources
            │   application.properties
            │
            └───templates
                │   student.html
                │
                └───layout
                        layout.html
                


Now let us see the content of the files:

1. build.gradle

The build.gradle files defines all the dependencies to Spring boot, Thymeleaf and Hibernate and H2 database library.

buildscript {
    ext {
        springBootVersion = '1.5.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

group 'com.firstfewlines'
version '1.0.0-SNAPSHOT'

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
    compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
    compile "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
    compile "org.springframework.boot:spring-boot-devtools:$springBootVersion"
    runtime "com.h2database:h2"
}


2. SpringBootH2Example.java

It is being the main Application entry point, initializes the Spring boot application.

package com.firstfewlines;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootH2Example {
    public static void main(String [] argv){
        SpringApplication.run(SpringBootH2Example.class, argv);
    }
}


3. StudentController.java

It is the only controller class of the application. A controller class defines the Web Service end points needed in the application.

package com.firstfewlines.controller;

import com.firstfewlines.domain.Student;
import com.firstfewlines.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.text.DateFormat;
import java.text.SimpleDateFormat;


@Controller
@RequestMapping("/")
class StudentController {
    final static DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

    @Autowired
    StudentService studentService;

    @RequestMapping(method = RequestMethod.GET)
    String home() {
        return "student";
    }

    @RequestMapping(value = "student", method = RequestMethod.POST, consumes = MediaType.ALL_VALUE)
    @ResponseStatus(value = HttpStatus.OK)
    ModelAndView addStudent(@RequestParam Integer rollNo,
                            @RequestParam String name,
                            @RequestParam String dateOfBirth ) throws Exception {

        ModelAndView modelAndView = new ModelAndView("student");
        try {
            Student student = new Student();
            student.setRollNo(rollNo);
            student.setName(name);
            student.setDateOfBirth(df.parse(dateOfBirth));
            student = studentService.addStudent(student);
            modelAndView.addObject("message", "Student added with name: " + student.getName());
        }
        catch (Exception ex){
            modelAndView.addObject("message", "Failed to add student: " + ex.getMessage());
        }
        modelAndView.addObject("students", studentService.getStudents());
        return modelAndView;
    }
}


4. Student.java

This is the domain class defining the Student table in database.

package com.firstfewlines.domain;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

@Entity
@Table(name="student_mst", schema = "public")
public class Student {

    @Transient
    static final DateFormat df = new SimpleDateFormat("dd-MM-yyyy");

    int rollNo;
    String name;
    String stream;
    Date dateOfBirth;
    int totalMarks;

    @Id
    public int getRollNo() {
        return rollNo;
    }

    public void setRollNo(int rollNo) {
        this.rollNo = rollNo;
    }

    public String getName() {
        return name;
    }

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

    public String getStream() {
        return stream;
    }

    public void setStream(String stream) {
        this.stream = stream;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    @Transient
    public String getDateOfBirthFormatted() {
        return df.format(dateOfBirth);
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    public int getTotalMarks() {
        return totalMarks;
    }

    public void setTotalMarks(int totalMarks) {
        this.totalMarks = totalMarks;
    }
}


5. StudentRepository.java

This is an interface to save & retrieve Student object to and from database. The spring boot automatically creates concrete implementation of it. Our duty is to just reference it using @Autowired annotation as it is done in StudentService.java.

Notice that, there are no methods in this interface. It only extends from CrudRepository which has defined commonly needed methods for saving and retrieving the domain object to/from database. If we want, we can define our own methods here as well.

package com.firstfewlines.repository;

import com.firstfewlines.domain.Student;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends CrudRepository<Student, Integer> {

}


6. StudentService.java

This is the Service class to provide basic services for manipulating and retrieving Student. This works as a wrapper over StudentRepository. Student controller is referencing this service by @Autowired.

This is better practice to use Service classes from Controller classes instead of Controller classes directly handles database operations.

package com.firstfewlines.service;

import com.firstfewlines.domain.Student;
import com.firstfewlines.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StudentService {

    @Autowired
    StudentRepository studentRepository;

    public Student addStudent(Student student) {
        studentRepository.save(student);
        return student;
    }

    public Iterable<Student> getStudents(){
        return studentRepository.findAll();
    }
}


7. student.html

This HTML contains the form to enter student and display existing students in a table. This is actually a block of HTML using Thymeleaf template engine. In runtime, it is rendered under the template that is defined in layout.html.

<!DOCTYPE HTML>
<html layout:decorator="layout/layout" xmlns:th="http://www.thymeleaf.org">
<body>
<th:block layout:fragment="content">

    <div class="row">
        <div class="col-md-4 col-sm-4">
            <div class="panel panel-default">
                <div class="panel-heading">Add new Student</div>
                <div class="panel-body">
                    <form method="post" class="form-group" action="/student">
                        <div class="form-group">
                            <label for="rollNo">Roll No:&nbsp;</label>
                            <input class="form-control" type="number" name="rollNo" id="rollNo" maxlength="10"/>
                        </div>

                        <div class="form-group">
                            <label for="name">Name:&nbsp;</label>
                            <input class="form-control" type="text" name="name" id="name" maxlength="50"/>
                        </div>

                        <div class="form-group">
                            <label for="dateOfBirth">Date Of Birth:&nbsp;</label>
                            <input class="form-control" type="date" name="dateOfBirth" id="dateOfBirth" maxlength="20"/>
                        </div>

                        <input class="btn btn-primary" type="submit" value="Save"/>

                    </form>
                </div>
                <div class="panel-footer"><span th:text="${message}">Status</span></div>
            </div>
        </div>

        <div class="col-md-8 col-sm-8">
            <div class="panel panel-default">
                <div class="panel-heading">Student List</div>
                <div class="panel-body">
                    <table class="table table-striped">
                        <thead>
                            <tr>
                                <th>Roll No</th>
                                <th>Name</th>
                                <th>Date of Birth</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr th:each="student : ${students}">
                                <td th:text="${student.getRollNo()}">Roll No</td>
                                <td th:text="${student.getName()}">Name</td>
                                <td th:text="${student.getDateOfBirthFormatted()}">Date of Birth</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</th:block>
</body>
</html>


8. layout.html

This is the main Thymeleaf layout of the Application. It defines header, menu, footer and includes all the needed JavaScripts and CSSs like Bootstrap.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>
    <meta charset="utf-8"></meta>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"></meta>
    <meta name="viewport" content="width=device-width, initial-scale=1"></meta>

    <meta name="description" content="Student Information System demo using SpringBoot, ThymeLeaf, H2 database"></meta>
    <meta name="author" content="www.firstfewlines.com"></meta>
    <title>Student Information System demo</title>

    <script src="https://code.jquery.com/jquery-3.2.1.min.js"
            integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
            crossorigin="anonymous"></script>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"></link>

    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
          integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"></link>

    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
            integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
            crossorigin="anonymous"></script>

</head>
<body>
<div class="container-fluid">
    <nav class="navbar navbar-full navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand" href="#">Student Information System</a>
            </div>
            <ul class="navbar-nav nav">
                <li class="nav-item active"><a class="nav-link" href="/">Home</a></li>
                <li class="nav-item"><a class="nav-link" href="http://www.firstfewlines.com">About</a></li>
            </ul>
        </div>
    </nav>

    <div layout:fragment="content"></div>

    <footer style="text-align:center;">
        <hr/>
        <p>Copyright © 2017 <a href="http://www.firstfewlines.com">www.firstfewlines.com</a></p>
    </footer>
</div>
</body>
</html>


9. application.properties

Last but not the least, the application properties. It defines the application settings like database connection, server port etc.

server.port=8090

spring.datasource.url = jdbc:h2:mem:student
spring.datasource.username = sa
spring.datasource.password =

# JPA/Hibernate properties
spring.jpa.show-sql = true

# Hibernate ddl auto (create, create-drop, update): with "update" the database
# schema will be automatically updated accordingly to java entities found in project
spring.jpa.hibernate.ddl-auto = update

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.naming.strategy = org.hibernate.cfg.ImprovedNamingStrategy

How to run this Spring boot application?

  • Make sure you have latest version of Gradle installed and PATH is configured.
  • Go to the directory where you unzipped the file i.e. where the build.gradle is there
  • Run gradle clean bootRun command
  • This should run the application with output similar to below
  • Now open your browser and point to http://localhost:8090/

Alternatively, you can use IntelliJ IDEa or Eclipse to import the Gradle project and run from there.

output:
Starting a Gradle Daemon, 1 busy and 2 stopped Daemons could not be reused, use --status for details
:clean
:compileJava
:processResources
:classes
:findMainClass
:bootRun
01:52:08.953 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
01:52:08.955 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
01:52:08.955 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/D:/WS/postsrc/spring-boot-h2-datasource-thymeleaf-gradle-example/build/classes/main/, file:/D:/WS/postsrc/spring-boot-h2-datasource-thymeleaf-gradle-example/build/resources/main/]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.3.RELEASE)

2017-05-30 01:52:09.202  INFO 7088 --- [  restartedMain] com.firstfewlines.SpringBootH2Example    : Starting SpringBootH2Example on hdaswin8h with PID 7088 (D:\WS\postsrc\spring-boot-h2-datasource-thymeleaf-gradle-example\build\classes\main started by hdas in D:\WS\postsrc\spring-boot-h2-datasource-thymeleaf-gradle-example)
2017-05-30 01:52:09.203  INFO 7088 --- [  restartedMain] com.firstfewlines.SpringBootH2Example    : No active profile set, falling back to default profiles: default
2017-05-30 01:52:09.366  INFO 7088 --- [  restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1c71ce69: startup date [Tue May 30 01:52:09 IST 2017]; root of context hierarchy
2017-05-30 01:52:10.785  INFO 7088 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8090 (http)
2017-05-30 01:52:10.795  INFO 7088 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-05-30 01:52:10.796  INFO 7088 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.14
2017-05-30 01:52:10.880  INFO 7088 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-05-30 01:52:10.880  INFO 7088 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1516 ms
2017-05-30 01:52:11.006  INFO 7088 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-05-30 01:52:11.008  INFO 7088 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'webServlet' to [/h2-console/*]
2017-05-30 01:52:11.010  INFO 7088 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-05-30 01:52:11.011  INFO 7088 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-05-30 01:52:11.011  INFO 7088 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-05-30 01:52:11.011  INFO 7088 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-05-30 01:52:11.387  INFO 7088 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2017-05-30 01:52:11.400  INFO 7088 --- [  restartedMain] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
        name: default
        ...]
2017-05-30 01:52:11.539  INFO 7088 --- [  restartedMain] org.hibernate.Version                    : HHH000412: Hibernate Core {5.0.12.Final}
2017-05-30 01:52:11.540  INFO 7088 --- [  restartedMain] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2017-05-30 01:52:11.541  INFO 7088 --- [  restartedMain] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2017-05-30 01:52:11.567  INFO 7088 --- [  restartedMain] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2017-05-30 01:52:11.641  INFO 7088 --- [  restartedMain] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2017-05-30 01:52:11.932  INFO 7088 --- [  restartedMain] org.hibernate.tool.hbm2ddl.SchemaUpdate  : HHH000228: Running hbm2ddl schema update
2017-05-30 01:52:11.943  INFO 7088 --- [  restartedMain] rmationExtractorJdbcDatabaseMetaDataImpl : HHH000262: Table not found: student_mst
2017-05-30 01:52:11.971  INFO 7088 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2017-05-30 01:52:12.364  INFO 7088 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1c71ce69: startup date [Tue May 30 01:52:09 IST 2017]; root of context hierarchy
2017-05-30 01:52:12.420  INFO 7088 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/],methods=[GET]}" onto java.lang.String com.firstfewlines.controller.StudentController.home()
2017-05-30 01:52:12.422  INFO 7088 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/student],methods=[POST],consumes=[*/*]}" onto org.springframework.web.servlet.ModelAndView com.firstfewlines.controller.StudentController.addStudent(java.lang.Integer,java.lang.String,java.lang.String) throws java.lang.Exception
2017-05-30 01:52:12.423  INFO 7088 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-05-30 01:52:12.423  INFO 7088 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-05-30 01:52:12.448  INFO 7088 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-05-30 01:52:12.448  INFO 7088 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-05-30 01:52:12.495  INFO 7088 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-05-30 01:52:12.947  INFO 7088 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-05-30 01:52:12.985  INFO 7088 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-05-30 01:52:13.044  INFO 7088 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8090 (http)
2017-05-30 01:52:13.048  INFO 7088 --- [  restartedMain] com.firstfewlines.SpringBootH2Example    : Started SpringBootH2Example in 4.08 seconds (JVM running for 4.419)


References:


Download source:


Hansaraj avatar
About Hansaraj
Hansaraj is a Software Engineer experienced in Java, Groovy, JavaScript, SQL, C#, C++
comments powered by Disqus