IntelliJ+Kotlin/Java for SpringBoot Note: ThymeLeaf, H2 Database, PostgreSQL, PMVC-Test

Homan Huang
9 min readSep 24, 2020

--

IntelliJ is a fine IDE. Especially, the Android Studio is built on it. Right here, I show you that we can use IntelliJ with Kotlin to edit our backend such as SpringBoot Server instead of Java. Do you like this combination? Cool, isn’t it?! This will make our works shorter and easy to read. I show my notes when I build a project with SpringBoot with ThymeLeaf, with the Hibernate H2 and PostgreSQL database. You can find your trouble section in the menu.

— === MeNu === —

👍1. IntelliJ — Kotlin
➰2. Spring Initializr
🌐3. First Words: Hello Foods
👁‍🗨4. Controller => Template => Web
🍂5. Config Thymeleaf Templates
📝6. Note of ThymeLeaf
🖋🖋🖋 → Form Action
🖋🖋🖋 → Error of Backing Bean
📄7. Logging: Logback
⭐8. Hibernate — H2 Embedded Database
🛡🛡🛡 → Entity
🛡🛡🛡 → Repository
🐘9. Static Database (PostgreSQL)
🏷🏷🏷 → Docker
🏷🏷🏷 → Import Schema & Data
🔬10. MVC Test

👍1. IntelliJ — Kotlin

< === Menu

Download and install IntelliJ Community Edition:

How to create a Kotlin project in IJ is simple.

IJ: File => New => Project

Let’s choose the Kotlin.

Project: NewKotlin

pom.xml: for Kotlin and Java

<properties>
<java.version>11</java.version>
<kotlin.version>1.4.10</kotlin.version>
</properties>
<dependencies>
...
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
...
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
</plugin>

🚄2. Spring Initializr

< === Menu

We can generate the SpringBoot project at, https://start.spring.io/

It’s the fastest way to build your project with your dependencies.

Of course, I will use Kotlin. Let’s choose the dependencies.

Hit generate, your web browser will download the zip file to your download folder. Now, please unzip that file to your IdeaProjects directory.

So you can open the spring boot project in IJ.

Upper is the Maven version. I like to use Gradle because I am an Android developer.

Gradle dependencies:

implementation ("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
developmentOnly("org.springframework.boot:spring-boot-devtools")

implementation("org.springframework.boot:spring-boot-starter-log4j2")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}

🌐3. First Words: Hello Foods

< === Menu

Hello-World is too common in the coding world. Let’s try Hello-Foods.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SpringBoot Page</title>
</head>
<body>
<H1> Hello Foods! </H1>
</body>
</html>

Run the application:

localhost:8080

At HTTP://localhost:8080, you will found the result.

👁‍🗨4. Controller => Template => Web

< === Menu

Let’s try our second Hello-Foods by Spring controller.

Template

Copy&edit the content from index.html to helloFoods.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Second SpringBoot Page</title>
</head>
<body>
<H1>Hello</H1><H2>Foods!</H2><H3>in my controller!</H3>
</body>
</html>

Controller

In Kotlin, your package folder. Let’s add a new package called “control”. In the control, you can add a new Kotlin class called MainController.kt.

@Controller
class MainController {
@RequestMapping("helloFoods") // endpoint
fun helloFoods(): ModelAndView = ModelAndView("helloFoods")
}

HTTP://localhost:8080/helloWorld

Let’s turn to the auto-restart function.

When you update the HTML file, the server will not update automatically. You have to restart the app. In IJ, you can turn on the auto-restart function to update the data.

Setting => Compiler => Build project automatically(checked)

Press: Ctrl+Shift+Alt+/

Choose Registry.

“compiler.automake.allow.when.app.running” = checked.

Now, run the app. I want to make a mistake with the controller.

fun helloFoods():
ModelAndView = ModelAndView("helloFoods2") //html

Refresh the web page.

The server responds with error code 500. After that, I fix the error and change HTML content,

<H1>Hello</H1><H2>Foods!</H2><H3>in my controller!</H3>
<h4>New change!</h4>

This time, without the restart, let’s refresh the webpage.

You’ll see that the automated update is working.

🍂5. Config Thymeleaf Templates

< === Menu

Package + config

Optional for your server:

Let’s create a “config” under your package. Add the configuration: You can define your template path, formatter for an object/bean, endpoint →HTML setup.

@Configuration
class WebConfig : WebMvcConfigurer {
@Bean
@Description("Thymeleaf template resolver serving HTML 5")
fun templateResolver(): ClassLoaderTemplateResolver {
val templateResolver = ClassLoaderTemplateResolver()
templateResolver.prefix = "mytemplates/"
templateResolver.isCacheable = false
templateResolver.suffix = ".html"
templateResolver.setTemplateMode("HTML5")
templateResolver.characterEncoding = "UTF-8"
return templateResolver
}

@Bean
@Description("Thymeleaf template engine with Spring integration")
fun templateEngine(): SpringTemplateEngine {
val templateEngine = SpringTemplateEngine()
templateEngine.setTemplateResolver(templateResolver())
return templateEngine
}

@Bean
@Description("Thymeleaf view resolver")
fun viewResolver(): ViewResolver {
val viewResolver = ThymeleafViewResolver()
viewResolver.templateEngine = templateEngine()
viewResolver.characterEncoding = "UTF-8"
return viewResolver
}

override fun addViewControllers(registry: ViewControllerRegistry) {
registry.addViewController("/").setViewName("index")
}
}

> templateResolver.prefix = “mytemplates/” <
tells the server to change from “templates” to “mytemplates”. You can add all new Thymeleaf HTML template into this folder.

📝6. Note of ThymeLeaf

< === Menu

HTML header:

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns:th="http://www.thymeleaf.org" >

“${ attr }”: attribute → object, matched by model.

HTML: I just eat a <span th:text="${ fruit }" />.
SBoot: model.addAttribute("fruit", "apple");

Spring →ModelAndView: Matching ${attributes} in the Thymeleaf template

model: mapOf(“attr” to object)

val model = mapOf(
"bean" to MyBean(), // bean object
"cargo" to cargoRepository.findAll(), // from DB
"prieces" to 1..100, // list
"barcodeIni" to 'A'..'P' // list
)

return ModelAndView(
"cargoBooking", // HTML page
model
)

Don’t you think that is the faster way to create model by mapOf?

Form Action: shopping.html

  • th:action →Go to shoppingCart endpoint to deal with data.
  • th:object → The object(merchandise) carries the form data.
<form action="#" th:action="@{/shoppingCart}" th:object="${merchandise}" method="POST">

Controller: From shopping endpointshoppingCart endpoint.

@RequestMapping("/shopping") // endpoint
fun shopping(): ModelAndView {
return ModelAndView(
"shopping", // html page
"merchandise", // attribute
MerchandiseBean() // object
)
}
@PostMapping("/shoppingCart") // endpoint
fun addToCart( bean: MerchandiseBean ): ModelAndView {...}

List object: The fruits of object-mBean lists data in the option box and returns variable, selectedFruit.

Return value: th:field= “*{ attr }”

<select th:field="*{selectedFruit}">
<option th:each="n : ${mBean.fruits}" th:value="${n}" th:text="${n}" />
</select>

The n represents one item of the list by th:each.

If condition = th:Ifth:unless

<td>
<span th:if="${customer.gender} == 'M'" th:text="Male" />
<span th:unless="${customer.gender} == 'M'" th:text="Female" />
</td>

Switch and case condition

<td th:switch="${customer.gender}">
<span th:case="'M'" th:text="Male" />
<span th:case="'F'" th:text="Female" />
</td>

Error:

<ul>
<li th:each="err : ${#fields.errors('*')}" th:text="${err}" />
</ul>

Conversion: ${{ value }}

<tr th:each="cargo: ${ cargos }">
<td th:text="${{ cargo.detail }}" />
</tr>

Load formatter in Configuration( /config/MyConfig.kt):

@Configuration
class MyConfig: WebMvcConfigurer {
...
@Description("Cargo Detail Conversion")
override fun addFormatters(registry: FormatterRegistry) {
registry.addFormatter(CargoDetailFormater())
}
}

Error: Bean property ‘YourVar’ is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?

The YourVar of backing bean has a different variable name with the one in Thymeleaf; for example: fruitNanefruitName

Controller:
class BackingBean {
var fruitNane: Fruit? = null
...
}
HTML:
<form ... th:object="${bean}" method="POST">
<input type="hidden" th:field="*{fruitName}"/>

📄7. Logging: Logback

< === Menu

Gradle:

configurations {
all {
exclude(group="org.springframework.boot", module="spring-boot-starter-logging")
}
}

Logger

The logger has already existed in the Spring Boot framework. Let’s load it in the class, such as MainController:

var logger: Logger = LoggerFactory.getLogger(MainController::class.java)

Now you can use it to do logger.info, logger.debug, logger.error, logger.warn and logger.trace. For example,

fun getSeat(): ModelAndView {
logger.debug("Checking the seat availability.")
...
}
fun checkSeatInfo(...): ModelAndView {
logger.debug("Searching result of seat.")
...
}

To show only your package information, you need to change application.properties,

logging.level.root=INFO
logging.level.YourPackage=TRACE

Let’s check the console:

⭐8. Hibernate — H2 Embedded Database

< === Menu

Gradle plugin:

plugins {
id("org.springframework.boot") version "2.3.4.RELEASE"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
//kotlin plugins
kotlin
("jvm") version "1.4.10"
kotlin("plugin.jpa") version "1.4.10"
kotlin("plugin.spring") version "1.4.10"
kotlin("plugin.noarg") version "1.4.10"
kotlin("plugin.allopen") version "1.4.10"

}

Gradle dependencies:

implementation ("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")

pom.xml:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>

application.properties: For example

#logging
logging.level.org.springframework.jdbc.datasource.init.ScriptUtils=debug
#h2 database
spring.datasource.url=jdbc:h2:file:~/foods
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=homan
spring.datasource.password=huang
spring.jpa.properties.hibernate.hbm2dll.auto=update

Run: HTTP://localhost:8080/h2-console

Match with your application.properties:

Entity

@Entity
data class Fruit(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long,
...)

No column name as “ROW”

Or you have:

org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "CREATE TABLE ... ROW[*] CHAR(255) NOT NULL, PRIMARY KEY (ID))"; expected "identifier"

LiveTemplate for Kotlin: ID

@Id @Column(name="$id$") 
@GeneratedValue(strategy = GenerationType.IDENTITY)
val $id2$: Long,$END$

LiveTemplate for Java: ID

@Id @Column(name="$id$")
@GeneratedValue(strategy = GenerationType.AUTO)
private long $id2$; $END$

LiveTemplate for Kotlin: set Column

@Column(name="$name$")
val $name2$: $type$,
$END$

LiveTemplate for Java: set Column

@Column(name="$name$")
private $type$ $name2$;
$END$

Repostiory

JpaRespository ←PagingAndSortingRepository←CrudRepository

Due to their inheritance nature, JpaRepository will have all the behaviors of CrudRepository and PagingAndSortingRepository. So if you don’t need the repository to have the functions provided by JpaRepository and PagingAndSortingRepository, use CrudRepository.

Simple, one line, this code links Fruit with id.

@Repository
interface FruitRepository: JpaRepository<Fruit, Long>

Now re-run your application and connect to H2-console to search for the empty table for each entity.

  • getOne: Find one by id. | findAll: Select * from the table
  • save/saveAll: Save or update data to the cache until you flush them to the database. | saveAndFlush: save one straight to the database.
  • delete/deleteById: Delete one from the table. | deleteAll/deleteInBatch/deleteAllInBatch: Delete a list from the table.
  • count: count the rows
  • equals/exists/existsById: Boolean

🐘9. Static Database (PostgreSQL)

< === Menu

Install PostgreSQL

Download:

Install from Docker container: create, display, and stop

> docker run --name postgres-docker-name -e POSTGRES_PASSWORD=YourPass -e POSTGRES_DB=YourDatabase -d postgres> docker ps --format '{{.Names}}'
...
> docker stop postgres-docker-name

Import schema & data:

Run:

SpringBoot →pom.xml

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>

SpringBoot →Gradle:

//runtimeOnly("com.h2database:h2")
implementation
("org.postgresql:postgresql")

Create database:

application.properties: Connect to PostgreSQL

spring.jpa.database=POSTGRESQL
spring.datasource.platform=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/YOUR-DB
spring.datasource.username=postgres
spring.datasource.password=your-password

🔬10. MVC Test

< === Menu

pom.xml

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>

Gradle:

testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}

Spring Test Annotation:

@RunWith(SpringJUnit4ClassRunner.class)
@WebMvcTest(YourWebController.class)

MVC(Java):

@Autowired
private MockMvc mockMvc;

Mockito(Java): given( mocked_service_call ) willReturn ( your_result )

@MockBean
private ShoppingService mService;
...
given(mService.getShopList(date)).willReturn(myShopList);

Test endpoint status and string compare(Java):

mockMvc.perform( get("/shoppingCart?customerId=383833") )
.andExpect(status().isOk())
.andExpect(content().string(containsString("Apple: Jazz")));

--

--

Homan Huang
Homan Huang

Written by Homan Huang

Computer Science BS from SFSU. I studied and worked on Android system since 2017. If you are interesting in my past works, please go to my LinkedIn.

No responses yet