Spring Boot + MyBatis + Thymeleaf implements a simple message board application
This project mainly introduces the use of Spring Boot + MyBatis + Thymeleaf + Bootstrap to implement a simple CRUD message board application. Advanced people can skip directly.
Source code for this tutorial: https://github.com/qingwenwei/spring-boot-crud-example
Features
post, post list
edit post
Build the project with Spring Initializr
Spring Initializr is a web-based tool for quickly building Spring Boot projects.
- Login to https://start.spring.io
- Select the required dependencies.
- Click Generate Project to download the project zip file.
import project
This tutorial uses eclipse as IDE.
- Unzip the compressed package.
- Import the project, as shown below.
- Select the project root directory (where the pom.xml file is located).
Configure MySQL database
root
Create a user with username and password in the local MySQL database root
.
Create an crudDemoDB
empty database (schema), using UTF8 encoding format to be more friendly to Chinese character storage:
CREATE DATABASE crudDemoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
configuration file
maven dependency management configuration file: pom.xml
<?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>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Configuration file for Spring Boot application: src/main/resources/application.properties
# database
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/crudDemoDB?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.initialization-mode=always
# MyBatis
mybatis.type-aliases-package=com.example.demo.model
mybatis.config-locations=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
# thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML
MyBatis
Underresources
New Directory mybatis
, mybatis
under New Directory mapper
. Also, add the following two files.
MyBatis configuration file: src/main/resources/mybatis/mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer" />
<typeAlias alias="Long" type="java.lang.Long" />
<typeAlias alias="String" type="java.lang.String" />
<typeAlias alias="HashMap" type="java.util.HashMap" />
<typeAlias alias="Date" type="java.util.Date" />
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
<typeAlias alias="ArrayList" type="java.util.ArrayList" />
<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
</configuration>
Mapper file: src/main/resources/mybatis/mapper/PostMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.dao.PostDao">
<resultMap id="PostResultMap" type="com.example.demo.model.Post">
<id property="id" column="post_id"/>
<result property="title" column="post_title"/>
<result property="content" column="post_content"/>
<result property="creationDate" column="post_creation_date"/>
</resultMap>
<insert id="save">
INSERT INTO `T_POST` (title, content, creationDate) VALUES (#{title}, #{content}, #{creationDate})
</insert>
<select id="findAll" resultMap="PostResultMap">
SELECT
p.id as post_id,
p.title as post_title,
p.content as post_content,
p.creationDate as post_creation_date
FROM T_POST p
ORDER BY p.creationDate DESC
</select>
<select id="find" resultMap="PostResultMap">
SELECT
p.id as post_id,
p.title as post_title,
p.content as post_content,
p.creationDate as post_creation_date
FROM T_POST p
WHERE p.id = #{postId}
</select>
<update id="update" parameterType="com.example.demo.model.Post">
UPDATE T_POST SET
title = #{title},
content = #{content}
WHERE id = #{id}
</update>
<delete id="delete" parameterType="Long">
DELETE FROM T_POST WHERE id = #{postId}
</delete>
</mapper>
DAO layers
The methods of the Dao interface need to correspond to the Mapper above.
src / main / java / com / example / demo / dao / PostDao.java
package com.example.demo.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.model.Post;
@Mapper
public interface PostDao {
void save(Post post);
void delete(Long postId);
void update(Post post);
Post find(Long postId);
List<Post> findAll();
}
entity class
src/main/java/com/example/demo/model/Post.java
package com.example.demo.model;
import java.io.Serializable;
import java.sql.Timestamp;
public class Post implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String title;
private String content;
private Timestamp creationDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Timestamp getCreationDate() {
return creationDate;
}
public void setCreationDate(Timestamp creationDate) {
this.creationDate = creationDate;
}
@Override
public String toString() {
return "Post [id=" + id + ", title=" + title + ", content=" + content + ", creationDate=" + creationDate + "]";
}
}
business logic layer
interface design
src/main/java/com/example/demo/service/PostService.java
package com.example.demo.service;
import java.util.Map;
import com.example.demo.model.Post;
public interface PostService {
void createPost(Post post);
void deletePost(Long postId);
void updatePost(Post post);
Map<String, Object> findPost(Long postId);
Map<String, Object> findAllPosts();
}
Implementation
src/main/java/com/example/demo/service/impl/PostServiceImpl.java
package com.example.demo.service.impl;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.dao.PostDao;
import com.example.demo.model.Post;
import com.example.demo.service.PostService;
@Service("postService")
@Transactional
public class PostServiceImpl implements PostService{
@Autowired
private PostDao postDao;
@Override
public void createPost(Post post) {
post.setCreationDate(new Timestamp(System.currentTimeMillis()));
this.postDao.save(post);
}
@Override
public void deletePost(Long postId) {
this.postDao.delete(postId);
}
@Override
public void updatePost(Post post) {
this.postDao.update(post);
}
@Override
public Map<String, Object> findPost(Long postId) {
Map<String, Object> attributes = new HashMap<>();
Post post = this.postDao.find(postId);
attributes.put("post", post);
return attributes;
}
@Override
public Map<String, Object> findAllPosts() {
Map<String, Object> attributes = new HashMap<>();
List<Post> allPosts = this.postDao.findAll();
// all posts
attributes.put("posts", allPosts);
// provide a new data transfer object
attributes.put("postDto", new Post());
return attributes;
}
}
controller
src/main/java/com/example/demo/controller/PostController.java
package com.example.demo.controller;
import java.util.Map;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.example.demo.model.Post;
import com.example.demo.service.PostService;
@Controller
public class PostController {
public static final Logger logger = LoggerFactory.getLogger(PostController.class);
@Autowired
private PostService postService;
/*
* Create a new post
*/
@RequestMapping(value = "/posts", method = RequestMethod.POST)
public String createPost(Model model, @Valid @ModelAttribute("postDto") Post post) {
logger.info("Creating post >> " + post);
this.postService.createPost(post);
return "redirect:/posts";
}
/*
* Delete a post
*/
@RequestMapping(value = "/posts/{postId}", method = RequestMethod.DELETE)
public String deletePost(@PathVariable("postId") Long postId) {
logger.info("Deleting post id:" + postId);
this.postService.deletePost(postId);
return "redirect:/posts";
}
/*
* Update a post
*/
@RequestMapping(value = "/posts", method = RequestMethod.PUT)
public String updatePost(@Valid @ModelAttribute("postDto") Post post) {
logger.info("Updating post >> " + post);
this.postService.updatePost(post);
return "redirect:/posts";
}
/*
* List all posts
*/
@RequestMapping(value = "/posts", method = RequestMethod.GET)
public String listAllPosts(Model model) {
logger.info("Litsing all posts...");
Map<String, Object> attributes = this.postService.findAllPosts();
model.addAllAttributes(attributes);
return "post-index";
}
/*
* Display post details
*/
@RequestMapping(value = "/posts/{postId}", method = RequestMethod.GET)
public String displayPostDetails(Model model, @PathVariable("postId") Long postId) {
logger.info("Displaying post details >> postId: " + postId);
Map<String, Object> attributes = this.postService.findPost(postId);
model.addAllAttributes(attributes);
return "post-details";
}
}
Spring Boot entry class
src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
SQL script
At runtime, Spring Boot checks resources
whether there are two SQL script files named schema.sql
and in the directory data.sql
, and executes these two files after the database connection pool is established.
src/main/resources/schema.sql
DROP TABLE IF EXISTS `T_POST`;
CREATE TABLE `T_POST`(
`id` BIGINT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`content` TEXT,
`creationDate` DATETIME,
PRIMARY KEY (`id`)
);
src/main/resources/data.sql
INSERT INTO `T_POST` (title, content, creationDate) VALUES ('测试标题1', '测试正文1', '2018-01-23');
INSERT INTO `T_POST` (title, content, creationDate) VALUES ('测试标题2', '测试正文2', '2018-01-25');
front page
The front-end page code is not posted here one by one. Interested friends can download the project code to have a look.
post list page
src/main/resources/templates/post-index.html
post content
src/main/resources/templates/post-details.html
Import bootstrap and jQuery via CDN
src/main/resources/templates/head.html
source code
Source code for this tutorial: https://github.com/qingwenwei/spring-boot-crud-example