Spring Boot Thymeleaf with JPA

Lasitha Benaragama
7 min readMar 23, 2018

Why Spring Boot?

According to the Spring IO Blog. They say Simplifying Spring for Everyone

  • To provide a radically faster and widely accessible ‘getting started’ experience for all Spring development
  • To be opinionated out of the box, but get out of the way quickly as requirements start to diverge from the defaults
  • To provide a range of non-functional features that are common to large classes of projects (e.g. embedded servers, security, metrics, health checks, externalized configuration)

Why Thymeleaf?

Well well? You can use FreeMarker and EL also, Else you can use angular js Front end with REST controllers. I will tell how to do it. next time?

Why JPA?

IBM says, JPA represents a simplification of the persistence programming model. and its true, You will receive huge optimization and methods if you are using JPA.

How?

That is a million dollar question, Most of you start reading this article to find out that. So straight to the point.

Step — 1 — Create Maven Web App

mvn archetype:generate -DgroupId=com.medium.bootThymeleafJpa -DartifactId=bootThymeleafJpa -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

Step — 2 — Add Maven Dependencies

<?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>

<artifactId>bootThymeleafJpa</artifactId>
<packaging>war</packaging>
<name>BootThymeleafJpa</name>
<description>Thymeleaf, Spring Boot and JPA Sample Application for Medium viewers.</description>
<version>1.0</version>

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

<properties>
<java.version>1.8</java.version>
<h2.version>1.4.187</h2.version>
<start-class>com.bootThymeleaf.SpringBootWebApplication</start-class>
</properties>

<dependencies>

<!-- Add typical dependencies for a web application -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Add typical dependencies for a web application -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- Add Thymeleaf support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- hot swapping, disable cache for template, enable live reload -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- Test Framework support Tell you how later-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- In built tomcat for development comment it when production -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--<scope>provided</scope> --><!--remove this in dev-->
</dependency>

<!-- Add Hikari Connection Pooling support. Later later -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>

<!-- Add H2 database support [for running with local profile] -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>

<!-- Optional, for bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>

</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<finalName>BootThymeleaf</finalName>
</build>
</project>

Lads, Im using inbuilt h2 database. and its easy. Connection pool Hikari. Why Hikari? i will explain later article about that also. But i recommend Hikari over c3po. And add test dependency also i will add article about that also.

Step — 3 — Application Initializer Class.

import com.bootThymeleaf.util.JpaConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Import;

/**
* The type Spring boot web application.
*/
@Import(JpaConfiguration.class)
@SpringBootApplication(scanBasePackages = {"com.bootThymeleaf"})
public class SpringBootWebApplication extends SpringBootServletInitializer {

/** {@inheritDoc} */
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringBootWebApplication.class);
}

/**
* The entry point of application.
*
*
@param args the input arguments
*
@throws Exception the exception
*/
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringBootWebApplication.class, args);
}

}

Make sure to change (scanBasePackages = {“com.bootThymeleaf”}) according to your base package structure.

Step — 4 — application.yml file

This is a Spring boot property file. add this in to resources.

---
server:
port:
8080
contextPath: /BootThymeleaf
---
spring:
profiles:
local, default
datasource:
bootThymeleaf:
url:
jdbc:h2:~/BootThymeleaf
username: SA
password:
driverClassName:
org.h2.Driver
defaultSchema:
maxPoolSize:
10
hibernate:
hbm2ddl.method:
create-drop
show_sql: true
format_sql: true
dialect: org.hibernate.dialect.H2Dialect
---

Make sure to follow indentations. This is yml.

Step — 5 — The Controller

import com.bootThymeleaf.models.Person;
import com.bootThymeleaf.services.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

/**
* Created by lasitha on 3/16/18.
*/
@Controller
public class PersonController {

/**
* The Person service.
*/
@Autowired
PersonService personService;

/**
* Add Person string.
*
*
@param model the model
*
@return the string
*/
@RequestMapping("/")
public String addPerson(Map<String, Object> model) {
Person person = new Person();
model.put("people", personService.getPeople());
model.put("person", person);
return "person";
}

/**
* Process form string.
*
*
@param person the Person
*
@param model the model
*
@return the string
*/
@RequestMapping(value = "/addPerson", method= RequestMethod.POST)
public String processForm(@ModelAttribute(value="person") Person person, Map<String, Object> model) {
try{
personService.savePerson(person);
model.put("statusMessage", "Person saved successfully.");
}catch (Exception ex){
model.put("statusMessage", "Person saved successfully.");
}
model.put("people", personService.getgetPeople());
model.put("person", new Person());
return "person";
}

/**
* Delete Person string.
*
*
@param id the id
*
@param model the model
*
@return the string
*/
@RequestMapping(value = "/deletePerson", method = RequestMethod.GET)
public String deletePerson(@RequestParam(name="id")Integer id, Map<String, Object> model) {
personService.deletePerson(id);
model.put("people", personService.getPeople());
model.put("person", new Person());
return "person";
}
}

Best practice — Create separate package (controllers?) and add this.

Step — 6 — The JPARepository Class.

import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;

/**
* The type Jpa configuration.
*/
@Configuration
@EnableJpaRepositories(basePackages = "com.bootThymeleaf.repositories",
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager")
@EnableTransactionManagement
public class JpaConfiguration {

@Autowired
private Environment environment;

@Value("${datasource.bootThymeleaf.maxPoolSize:10}")
private int maxPoolSize;

/**
* Data source properties data source properties.
*
*
@return the data source properties
*/
@Bean
@Primary
@ConfigurationProperties(prefix = "datasource.bootThymeleaf")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}

/**
* Data source data source.
*
*
@return the data source
*/
/*
* Configure HikariCP pooled DataSource.
*/
@Bean
public DataSource dataSource() {
DataSourceProperties dataSourceProperties = dataSourceProperties();
HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder
.create(dataSourceProperties.getClassLoader())
.driverClassName(dataSourceProperties.getDriverClassName())
.url(dataSourceProperties.getUrl())
.username(dataSourceProperties.getUsername())
.password(dataSourceProperties.getPassword())
.type(HikariDataSource.class)
.build();
dataSource.setMaximumPoolSize(maxPoolSize);
return dataSource;
}

/**
* Entity manager factory local container entity manager factory bean.
*
*
@return the local container entity manager factory bean
*
@throws NamingException the naming exception
*/
/*
* Entity Manager Factory setup.
*/
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setPackagesToScan(new String[]{"com.bootThymeleaf.models"});
factoryBean.setJpaVendorAdapter(jpaVendorAdapter());
factoryBean.setJpaProperties(jpaProperties());
return factoryBean;
}

/**
* Jpa vendor adapter jpa vendor adapter.
*
*
@return the jpa vendor adapter
*/
/*
* Provider specific adapter.
*/
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
return hibernateJpaVendorAdapter;
}

/*
* Here you can specify any provider specific properties.
*/
private Properties jpaProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("datasource.bootThymeleaf.hibernate.dialect"));
properties.put("hibernate.hbm2ddl.auto", environment.getRequiredProperty("datasource.bootThymeleaf.hibernate.hbm2ddl.method"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("datasource.bootThymeleaf.hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("datasource.bootThymeleaf.hibernate.format_sql"));
if (StringUtils.isNotEmpty(environment.getRequiredProperty("datasource.bootThymeleaf.defaultSchema"))) {
properties.put("hibernate.default_schema", environment.getRequiredProperty("datasource.bootThymeleaf.defaultSchema"));
}
return properties;
}

/**
* Transaction manager platform transaction manager.
*
*
@param emf the emf
*
@return the platform transaction manager
*/
@Bean
@Autowired
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(emf);
return txManager;
}

}

This class basically read the yml file and process the JPA Connection.

Step — 7 — Repository classes.

Learn JPA is not a hard task. this will contain all the queries related to particular Database Entity models.

import com.bootThymeleaf.models.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

/**
* Created by lasitha on 3/16/18.
*/
@Repository
public interface PersonRepository extends JpaRepository<Person, Integer>{
/**
* Find by id Person.
*
*
@param id the id
*
@return the Person
*/
Person findById(Integer id);

}

Refer JPA Repository class. we are giving package which all the repositories located inside the system. make sure you package comply with given repository package name.

Step — 8— Service classes.

import com.bootThymeleaf.models.Person;

import java.util.List;

/**
* Created by lasitha on 3/16/18.
*/
public interface PersonService {

/**
* Save
Person.
*
*
@param person the person
*/
void savePerson(Person person);

/**
* Get All People.
*
*
@return Value for property 'getPeople'.
*/
List<Person> getPeople();

/**
* Delete Person.
*
*
@param id the id
*/
void deletePerson(Integer id);
}

Step — 9 — Entity Class (POJO)

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

/**
* Created by lasitha on 3/15/18.
*/
@Entity
@Table(name = "person")
public class Person {

@Id
@GeneratedValue
private Integer id ;

@NotNull
private String fullName;

@NotNull
private String shortCode;

/**
* Getter for property 'id'.
*
*
@return Value for property 'id'.
*/
public Integer getId() {
return id;
}

/**
* Setter for property 'id'.
*
*
@param id Value to set for property 'id'.
*/
public void setId(Integer id) {
this.id = id;
}

/**
* Getter for property 'fullName'.
*
*
@return Value for property 'fullName'.
*/
public String getFullName() {
return fullName;
}

/**
* Setter for property 'fullName'.
*
*
@param fullName Value to set for property 'fullName'.
*/
public void setFullName(String fullName) {
this.fullName = fullName;
}

/**
* Getter for property 'shortCode'.
*
*
@return Value for property 'shortCode'.
*/
public String getShortCode() {
return shortCode;
}

/**
* Setter for property 'shortCode'.
*
*
@param shortCode Value to set for property 'shortCode'.
*/
public void setShortCode(String shortCode) {
this.shortCode = shortCode;
}
}
import com.bootThymeleaf.models.Person;
import com.bootThymeleaf.repositories.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* Created by lasitha on 3/16/18.
*/
@Service("personService")
@Transactional
public class PersonServiceImpl implements PersonService{

@Autowired
private PersonRepository personRepository;

/** {@inheritDoc} */
@Override
public void savePerson(Person person) {
personRepository.saveAndFlush(person);
}

/** {@inheritDoc} */
@Override
public List<Person> getPeople() {
return personRepository.findAll();
}

/** {@inheritDoc} */
@Override
public void deletePerson(Integer id) {
personRepository.delete(id);
}
}

Please Note — Refer Entity Manager Factory setup inside your JPARepository Class. Add your models comply with the package given there.

Last Step — The html File

If you already know the EL then thymeleaf will be a piece of cake for you.

<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Sample App for Spring boot, Thymeleaf and JPA</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

<link rel="stylesheet" type="text/css"
href="webjars/bootstrap/3.3.7/css/bootstrap.min.css"
/>

<link rel="stylesheet" th:href="@{/css/main.css}"
href="../../css/main.css"
/>
</head>
<body>
<div class="container">
<div class="starter-template">
<form action="#" th:action="@{/addPerson}" th:object="${person}" method="post">
<div class="form-group row">
<label for="code" class="col-sm-2 col-form-label">Short Code</label>
<div class="col-sm-10">
<input type="text" class="form-control-plaintext" id="code" th:field="*{shortCode}"/>
</div>
</div>
<div class="form-group row">
<label for="fullName" class="col-sm-2 col-form-label">Full Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="fullName" placeholder="Full Name"
th:field="*{fullName}"
/>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-block">Add Person</button>
</div>
</div>
</form>
</div>
</div>
<div class="container">
<table class="table table-dark">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Short Code</th>
<th scope="col">Full Name</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr scope="row" th:each="person : ${people}">
<td th:text="${person.id}">1</td>
<td><a href="#" th:text="${person.shortCode}">Title ...</a></td>
<td th:text="${person.fullName}">Text ...</td>
<td><a th:href="@{/deleteperson(id=${person.id})}" class="btn btn-danger">Delete</a>
</tr>
</tbody>
</table>
</div>

<script type="text/javascript"
src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"
></script>
</body>
</html>

Okay. Its completed. I will post the Unit Testing Tutorial and Freemarker Tutorial with angular soon. Please share your expeirience with a comment. If you found any issues please leave a comment i will do whatever i can to help you.

Thank you.

--

--