本文以单表数据的增删改查业务为背景,介绍 Spring Boot 整合视图层Bootstrap框架、Thymeleaf 模板以及 Spring Data JPA等框架的实际应用。
1、技术选型:
- Spring Boot
- Thymeleaf
- Bootstrap
- Spring Data JPA
- MySQL
2、需求描述:实现图书信息单表数据的增删改查任务。
3、项目演示效果如下图所示:
1. 项目初始化
第一步:在 IDEA 中新建空白的 SpringBoot 工程,勾选如下几项:
注:这些勾选的依赖都会在生成空白项目时,自动导入到 pom.xml 文件中。
第二步:在 pom.xml
文件中手动添加整合 Bootstrap 的相关依赖
<!-- bootstrap、jquery前端框架等 -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
第三步:在 MySQL 数据库中创建一张书籍数据表(book),并录入若干条测试数据。
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`price` decimal(10, 2) NULL DEFAULT NULL,
`publishDate` date NULL DEFAULT NULL,
`publishName` varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorInfo` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
创建数据表对应的实体类 Student
package com.trainingl.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity(name = "book") //实体类映射数据表
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Column
private Double price;
@Column
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date publishDate;
@Column
private String publishName;
@Column
private String authorInfo;
}
2. 业务代码
2.1 Spring Boot 配置文件
在全局配置文件 resources/application.yml 中配置数据源信息、视图解析器以及端口号等相关配置等
server:
port: 8080
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML5
encoding: UTF-8
datasource:
url: jdbc:mysql://localhost:3306/book?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
说明:最后一行表示 Spring Data JPA 的命名策略,这里取消了字段驼峰式命名时,字段转化为下划线连接的方式。
2.2.1 数据持化层
在路径 com > trainingl > repository 下创建接口 BookRepository;
Spring Data JPA 不是对 JPA 规范的具体实现,本身是一个抽象层,底层也是通过 Hibernate 实现的。开发者使用 Spring Data JPA 持久化框架时,并不需要自己编写 SQL 语句去实现,而是将数据表和实体类映射起来后,直接继承
JpaRepository
即可,该类内置了与增删改查业务相关的一些方法可供调用。
package com.trainingl.repository;
import com.trainingl.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
//第一个泛型表示实体类的类型,第二个泛型表示主键类型
public interface BookRepository extends JpaRepository<Book, Long> {
public Book getById(Long id);
}
说明:第一个泛型表示实体类的类型,第二个泛型表示主键类型。
2.2.2 业务层
创建业务层接口 BookService
package com.trainingl.service;
import com.trainingl.entity.Book;
import java.util.List;
public interface BookService {
public List<Book> findAll();
public Book findById(Long id);
public void save(Book book);
public void update(Book book);
public void deleteById(Long id);
}
业务层实现 BookServiceImpl
package com.trainingl.service.Impl;
import com.trainingl.entity.Book;
import com.trainingl.repository.BookRepository;
import com.trainingl.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookRepository bookRepository;
@Override
public List<Book> findAll() {
return bookRepository.findAll();
}
@Override
public Book findById(Long id) {
return bookRepository.getById(id);
}
@Override
public void save(Book book) {
bookRepository.save(book);
}
@Override
public void update(Book book) {
bookRepository.save(book);
}
@Override
public void deleteById(Long id) {
bookRepository.deleteById(id);
}
}
2.2.3 控制层
创建 BookController 控制器,主要负责接收客户端浏览器的请求与响应。这里接受的请求都是 POST 表单和 <a>...<a/>
标签触发的 GET 请求,至于 Ajax 异步请求在后面的实例学习中会进一步介绍。
package com.trainingl.controller;
import com.trainingl.entity.Book;
import com.trainingl.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/book")
public class BookController {
@Autowired
private BookService bookService;
@GetMapping("/list")
public ModelAndView booklist(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("index");
modelAndView.addObject("books",bookService.findAll());
return modelAndView;
}
//根据id查找数据库的记录
@GetMapping("/findById/{id}")
public ModelAndView findById(@PathVariable Long id){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("editor");
Book book = bookService.findById(id);
modelAndView.addObject("book",book);
return modelAndView;
}
@PostMapping("/save")
public String save(Book book){
bookService.save(book);
return "redirect:/book/list";
}
//根据id删除数据库的记录
@GetMapping("/delete/{id}")
public String deleteById(@PathVariable Long id){
bookService.deleteById(id);
return "redirect:/book/list";
}
//根据id修改数据记录
@PostMapping("/update")
public String updateById(Book book){
bookService.update(book);
return "redirect:/book/list";
}
}
2.2.4 视图层
Bootstrap 是最受欢迎的 HTML、CSS 和 JS 框架,⽤于开发响应式布局、移动设备优先的WEB 项⽬。简洁、直观、强悍的前端开发框架,html、css、javascript ⼯具集,让 web 开发更速、简单。这里我使用 Bootstrap + Thymeleaf 模板完成视图层网页的搭建与数据渲染操作。
1、数据列表首页 resources > templates > index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<html xmlns:th="http://www.thymeleaf.org"></html>
<head>
<meta charset="UTF-8">
<script src="/webjars/jquery/3.1.1/jquery.min.js"></script>
<script src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
<title>数据列表</title>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" datatoggle="collapse" data-target="#bs-example-navbar-collapse-1" ariaexpanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">图书管理系统</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">图书信息<span class="sr-only">
(current)</span></a></li>
<li><a href="#">新书推荐</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
role="button" aria-haspopup="true" aria-expanded="false">更多操作 <span
class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="搜素更多图书...">
</div>
<button type="submit" class="btn btn-default">搜索</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Training.L</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
role="button" aria-haspopup="true" aria-expanded="false">用户管理 <span
class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">修改密码</a></li>
<li><a href="#">添加地址</a></li>
<li><a href="#">订单记录</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退出登录</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- 布局 3:7 -->
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="list-group">
<a href="#" class="list-group-item active">
首页
</a>
<a href="#" class="list-group-item">图书列表</a>
<a href="#" class="list-group-item">出版社列表</a>
<a href="#" class="list-group-item">作者信息列表</a>
<a href="#" class="list-group-item">关于我们</a>
</div>
</div>
<div class="col-md-9">
<button class="btn btn-success" data-toggle="modal" id="btn">添加新书</button>
<br><br>
<div class="panel panel-default">
<div class="panel-heading">书籍信息列表</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>编号</th>
<th>书籍名称</th>
<th>价格</th>
<th>出版日期</th>
<th>图书出版社</th>
<th>作者信息</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="book:${books}">
<td th:text="${book.id}"></td>
<td th:text="${book.title}"></td>
<td th:text="${book.price}"></td>
<td th:text="${#dates.format(book.publishDate,'yyyy-MM-dd')}"></td>
<td th:text="${book.publishName}"></td>
<td th:text="${book.authorInfo}"></td>
<td>
<a th:href="@{/book/findById/{cid}(cid=${book.id})}" class="btn btn-primary">编辑</a>
<a th:href="@{/book/delete/{id}(id=${book.id})}" class="btn btn-danger">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal fade" tabindex="-1" role="dialog" id="myModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">添加新书籍</h4>
</div>
<div class="modal-body">
<form th:action="@{/book/save}" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label for="title" class="col-md-2 control-label">图书标题
</label>
<div class="col-md-8">
<input type="text" id="title" name="title" class="form-control" placeholder="请输入图书标题...">
</div>
</div>
<div class="form-group">
<label for="price" class="col-md-2 control-label">图书价格
</label>
<div class="col-md-8">
<input type="text" id="price" name="price" class="form-control" placeholder="请输入图书价格...">
</div>
</div>
<div class="form-group">
<label for="date" class="col-md-2 control-label">出版日期
</label>
<div class="col-md-8">
<input type="date" id="date" name="publishDate" class="form-control" placeholder="请输入出版日期...">
</div>
</div>
<div class="form-group">
<label for="publish" class="col-md-2 control-label">出版社名
</label>
<div class="col-md-8">
<input type="text" id="publish" name="publishName" class="form-control" placeholder="请输入出版社...">
</div>
</div>
<div class="form-group">
<label for="author" class="col-md-2 control-label">图书作者
</label>
<div class="col-md-8">
<input type="text" id="author" name="authorInfo" class="form-control" placeholder="请输入图书作者...">
</div>
</div>
<button type="reset" class="btn btn-primary">重置</button>
<button type="submit" class="btn btn-success">提交</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-success" id="submit_btn">保存提交</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
//绑定按钮的点击事件
$("#btn").click(function(){
// 手动打开模态框
$("#myModal").modal('show');
});
//手动关闭模态框
$("#submit_btn").click(function(){
$('#myModal').modal('hide');
});
</script>
</body>
</html>
2、更新信息页面 resources > templates > editor.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<html xmlns:th="http://www.thymeleaf.org"></html>
<head>
<meta charset="UTF-8">
<script src="/webjars/jquery/3.1.1/jquery.min.js"></script>
<script src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
<title>修改图书信息</title>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" datatoggle="collapse" data-target="#bs-example-navbar-collapse-1" ariaexpanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">图书管理系统</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">图书信息<span class="sr-only">
(current)</span></a></li>
<li><a href="#">新书推荐</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
role="button" aria-haspopup="true" aria-expanded="false">更多操作 <span
class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="搜素更多图书...">
</div>
<button type="submit" class="btn btn-default">搜索</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Training.L</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
role="button" aria-haspopup="true" aria-expanded="false">用户管理 <span
class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">修改密码</a></li>
<li><a href="#">添加地址</a></li>
<li><a href="#">订单记录</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退出登录</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- 布局 3:7 -->
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="list-group">
<a href="#" class="list-group-item active">
首页
</a>
<a href="#" class="list-group-item">图书列表</a>
<a href="#" class="list-group-item">出版社列表</a>
<a href="#" class="list-group-item">作者信息列表</a>
<a href="#" class="list-group-item">关于我们</a>
</div>
</div>
<div class="col-md-9">
<h1 class="text-center text-muted">更新图书信息</h1>
<hr>
<form th:action="@{/book/update}" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label for="ID" class="col-sm-2 control-label">图书编号:</label>
<div class="col-sm-10">
<input type="text" name="id" id="ID" class="form-control"
th:value="${book.id}" readonly>
</div>
</div>
<div class="form-group">
<label for="title" class="col-sm-2 control-label">书籍名称:</label>
<div class="col-sm-10">
<input type="text" name="title" id="title" class="form-control"
th:value="${book.title}">
</div>
</div>
<div class="form-group">
<label for="price" class="col-sm-2 control-label">书籍价格:</label>
<div class="col-sm-10">
<input type="text" name="price" id="price" class="form-control"
th:value="${book.price}">
</div>
</div>
<div class="form-group">
<label for="date" class="col-sm-2 control-label">出版日期:</label>
<div class="col-sm-10">
<input type="date" name="publishDate" id="date" class="form-control"
th:value="${#dates.format(book.publishDate,'yyyy-MM-dd')}">
</div>
</div>
<div class="form-group">
<label for="publish" class="col-sm-2 control-label">图书出版社:</label>
<div class="col-sm-10">
<input type="text" name="publishName" id="publish" class="form-control"
th:value="${book.publishName}">
</div>
</div>
<div class="form-group">
<label for="author" class="col-sm-2 control-label">作者列表:</label>
<div class="col-sm-10">
<input type="text" name="authorInfo" id="author" class="form-control"
th:value="${book.authorInfo}">
</div>
</div>
<hr>
<input type="submit" value="提交修改" class="btn btn-primary btn-block">
</form>
</div>
</div>
</div>
</body>
</html>
总结:本文使用 Spring Boot 可以非常方便地集成视图层、持久化层的相关框架,本实例基于常见的单表 CRUD 业务,采用了 Bootstrap 前端框架搭建页面结构,并通过 Thymeleaf 模板渲染数据,后端的数据库交互采用了 Spring 体系中的 Spring Data JPA,完成了对单表增删改查的场景实现。尽管还存在很多细节部分需要完善,但初步具备了基本的项目架构。