1. 前言
在当今的软件开发领域,Java 作为一门历史悠久且功能强大的编程语言,持续吸引着大量的开发者。而在 Java 的 Web 开发框架中,Spring Boot 以其简单、快速、便捷的特性,成为了许多开发者的首选。Spring Boot 致力于简化 Spring 应用的初始搭建以及开发过程,通过自动配置和起步依赖等特性,大大减少了项目搭建的复杂性,使得开发者能够更加专注于业务功能的实现。
对于 Web 应用来说,前后端分离是一个趋势,但即使如此,服务端模板引擎仍然是不可或缺的。Thymeleaf 作为一款现代服务器端 Java 模板引擎,以其优雅、易于理解和使用的特性,成为了众多开发者的选择。它支持 HTML5、JavaScript、CSS 等静态资源的直接引入,并且能够完美地与 Spring Boot 集成,为开发者提供了一种简洁、高效的方式来渲染页面。
在持久层框架的选择上,MyBatis 同样以其灵活、易用、强大的特性赢得了开发者的青睐。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集,它可以通过简单的 XML 或注解来配置和映射原生 SQL,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java 对象)映射成数据库中的记录。这种映射关系使得开发者能够更加方便地进行数据库操作,提高了开发效率。
本文旨在通过 10 分钟的时间,教会读者如何在 Spring Boot 项目中集成 Thymeleaf 和 MyBatis,并实现产品的增删改查功能。我们将从环境准备、数据库设计、项目搭建、代码实现等方面进行详细的讲解,使读者能够快速上手并掌握相关技术。无论你是初学者还是有一定经验的开发者,都可以通过本文学习到一些实用的技巧和方法,希望对你有所帮助。
2. Spring Boot
2.1 Spring Boot是什么?
百度百科中说:“Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。”
Spring Boot 是一个开源的Java开发框架,它使得创建基于Spring的应用变得更为简单。Spring Boot通过简化Spring框架的初始化配置过程,提供了一系列“开箱即用”的特性,如内嵌的HTTP服务器(如Tomcat)、自动配置、以及通过“starter”依赖来快速集成第三方库。这使得开发人员能够更快速地构建和部署Spring应用。
2.2 我们为什么使用Spring Boot
- 快速构建项目。
- 对主流开发框架的无配置集成。
- 独立运行,无须依赖外部Servlet容器。
- 提供运行时的应用监控。
- 极大地提高了开发、部署效率。
- 与云计算的天然集成。
2.3 Spring Boot特性
- 创建独立的Spring应用程序
- 直接嵌入Tomcat,Jetty或Undertow(无需部署WAR文件)
- 提供starter,简化构建配置
- 自动配置Spring,及第三方库
- 生产环境的指标,健康检查及外部化配置
- 开箱即用,没有代码生成,也无需XML配置
3. MyBatis
3.1 MyBatis 是什么?
百度百科中说:“MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。”
3.2 我们为什么使用Mybatis?
我们以前最流行的持久层框架为Hibernate,如今很多公司转而使用Mybatis,因为Mybatis具有如下优势:
- 简单易学:小且简单,没有任何第三方依赖,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。
sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。 - 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的字段ORM关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql
4. Thymeleaf
4.1 Thymeleaf是什么?
网上说:“Thymeleaf是一种用于Web和独立环境的现代服务器端的Java模板引擎。 Thymeleaf的主要目标是将优雅的自然模板带到开发工作流程中,并将HTML在浏览器中正确显示,并且可以作为静态原型,让开发团队能更容易地协作。Thymeleaf能够处理HTML,XML,JavaScript,CSS甚至纯文本。 ”
4.2 我们为什么使用Thymeleaf ?
Thymeleaf 是一个用于 Web 和独立环境的现代服务器端 Java 模板引擎。以下是使用 Thymeleaf 的一些主要原因:
- 自然模板:Thymeleaf 允许你使用 HTML 作为模板语言,只需添加特定的属性前缀即可进行动态内容替换。这使得模板更易于阅读和维护,因为开发人员和设计师都可以直接理解和编辑 HTML。
- Spring 集成:如果你正在使用 Spring 框架,Thymeleaf 是一个很好的选择。Spring Boot 默认集成了 Thymeleaf,使得在 Spring 项目中使用它变得非常简单。Thymeleaf 与 Spring MVC 的良好集成允许你轻松地使用数据模型(如 ModelMap 或 ModelAndView)来渲染页面。
- 语法简单:Thymeleaf 使用了简单的表达式语言(Expression Language),如
${...}
来访问变量,#{...}
来访问国际化消息等。这使得模板逻辑更清晰、更易于理解。 - 高性能:Thymeleaf 支持两种模式:HTML5 和 XML。它采用了一种高效的标记解析策略,使得在服务器端渲染页面时性能较高。
- 安全性:Thymeleaf 对常见的 Web 安全问题(如跨站脚本攻击 XSS)提供了内置的保护。它还可以防止在渲染过程中由于模板中的错误或缺失属性而导致的潜在安全问题。
- 国际化支持:Thymeleaf 提供了强大的国际化支持,允许你轻松地为不同地区的用户呈现不同的语言版本。你可以使用
#{...}
语法来访问消息文件中的消息,并根据用户的语言偏好进行切换。 - 可配置性:Thymeleaf 提供了丰富的配置选项,允许你根据项目的需求进行自定义。你可以配置模板解析器、表达式处理器、消息解析器等,以满足特定的业务需求。
- 与其他技术的兼容性:Thymeleaf 可以与其他 Java 技术(如 JSP、FreeMarker、Velocity 等)一起使用,使得在迁移或集成现有项目时更加灵活。
综上所述,Thymeleaf 因其自然模板、与 Spring 的良好集成、简单语法、高性能、安全性、国际化支持、可配置性和与其他技术的兼容性等优点而成为许多 Java Web 项目的首选模板引擎。
6. 案例
下面跟着作者基于Spring Boot、Mybatis,Thymeleaf,使用三层结构(视图层、业务逻辑层、持久层),一步步完成产品的增删改查功能案例吧。
5.1 环境准备
在我们开始案例之前,需要搭建开发环境,我们需要的环境列表,及安装链接如下:
- Jdk13
- Spring Boot2.2.5版本
- 开发工具Eclipse2019
- Mysql8.0.18
5.2 数据库准备
5.2.1 连接数据库
进入cmd,输入数据库连接语句,格式:mysql -u 用户名 -p,这里用户名为root,所以输入 mysql -u root -p,回车,输入密码,显示下面界面内容,说明数据库连接成功。
5.2.2 创建数据库
新建数据库作为我们本次实例的数据库,格式:create database 数据库名;
查询所有数据库可以用 show databases;
4.2.3 使用数据库
创建好数据库后,我们需要在数据库中创建产品表,在这之前我们需要切换到需要使用的数据库,切换语句:use 数据库名;
4.2.4 建表
在数据库中新建表Product,如果数据库中
4.3 基于Spring Starter Project 新建项目
- 打开eclipse,新建项目,选择Spring Starter Project,如下图:
- 输入项目名称等信息,如下图:
3. 选择依赖,然后单击Finish按钮,等待系统自动下载创建完成,你也可以把devtools加进来,就热部署了。
4. 建好后,pom.xml文件如下(其中devtools是我后加进去的):
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cherry</groupId>
<artifactId>ProductProject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ProductProject</name>
<description>Demo project for Spring Boot</description>
<properties>
<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>2.1.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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.4 多环境及国际化配置
我们在项目中通常会有本地开发环境,单体测试环境,结合测试环境,生产环境,有些已经上线的项目还会有预生产环境。
现在我们在代码中配置多环境:
- 将src/main/resources下的application.properties文件改为application.yml作为主配置文件;
- 新建applicatoin-local.yml文件,作为本地开发环境的配置文件;
- 新建applicatoin-dev.yml文件,作为单体测试环境配置文件;
- 新建applicatoin-test.yml文件,作为结合测试环境配置文件;
- 新建applicatoin-prod.yml文件,作为生产环境配置文件;
- 打开application.yml文件,加入下面代码,设置当前有效文件。
当前我们在本地开发,配置如下(如果是单体测试环境,则active后设置为dev,其他同理):
spring:
profiles:
active: local
- 在applicatoin-dev.yml文件中,加入如下配置内容:
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
datasource:
url: jdbc:mysql://localhost:3306/productproject?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: 131452sleilj
driver-class-name: com.mysql.cj.jdbc.Driver
messages:
basename: i18n/message
下面是配置说明:
-
spring.thymeleaf.cache: 前台页面缓存设置,默认为true,开发环境需要立即看到页面效果,所以这里设置为false,其他环境最好设置为true;
-
spring.thymeleaf.prefix: 指定模板文件位置,这里的classpath代表资源目录,即
-
spring.datasource.url:数据库连接url。
localhost:本机地址,如果数据库不在自己机器上,这里可以使用IP地址;
3306:数据库访问端口(3306是mysql默认访问端口);
productproject:数据库名;
useUnicode:是否使用Unicode编码,这里应该设置为true;
characterEncoding:使用字符集,国际化需要使用utf-8;
serverTimezone:时区,这里设置为北京时间;
useSSL:是否使用SSL,如果你本地没有配置SSL,设置为false,测试及生产环境建议设置为true -
spring.datasource.username:数据库访问用户名
-
spring.datasource.password:数据库访问密码
-
spring.datasource.driver-class-name:数据库驱动,如果数据库版本为6.0及以上,应该为com.mysql.cj.jdbc.Driver,如果数据库版本低于6.0,为com.mysql.jdbc.Driver
-
spring.messages.basename: 国际化属性文件路径,其默认在资源文件夹下。这里设置为i18n/message,表示在资源文件夹src/main/resources下新建名为i18n的文件夹,并且在此文件夹下新建下面文件:
message.properties:默认的多语言配置文件
message_en.properties:message_en_US.properties也可以,加US表示美国,不加代表所有使用英文的国家
message_zh.properties:message_en_CN.properties也可以,加CN表示中国大陆,不加表示所有中国地区
4.5 持久层
1. 创建产品的Entity,如下:
package com.cherry.ProductProject.entity;
public class Product {
private String code;
private String name;
private String summary;
private String description;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
2. 创建访问数据库的Mapper文件
这里使用的注解方式进行数据库的操作,使用xml文件也是可以的,我曾在Spring MVC的视频中用xml文件的方式访问Mybatis数据库,如果有兴趣,可以查看【如下视频】
package com.cherry.ProductProject.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.cherry.ProductProject.entity.Product;
@Mapper
public interface ProductMapper {
@Insert("insert into product values(#{code}, #{name}, #{summary}, #{description})")
public int insertProduct(Product product);
@Delete("delete from product where code=#{code}")
public int deleteProduct(String code);
@Update("update product set name=#{name}, summary=#{summary}, description=#{description} where code=#{code}")
public int updateProduct(Product product);
@Select("select * from product")
public List<Product> findAllProducts();
@Select("select * from product where code=#{code}")
public Product findProductByCode(String code);
}
4.6. 业务逻辑层
1. 新建Service的接口
package com.cherry.ProductProject.service;
import java.util.List;
import com.cherry.ProductProject.entity.Product;
public interface ProductService {
public int insertProduct(Product product); //新增产品
public int deleteProduct(String code); //移除产品
public int updateProduct(Product product); //更新某个产品
public List<Product> findAllProducts(); //查询所有产品
public Product findProductByCode(String code); //根据产品code,查询某个产品
}
2. 新建Service接口的实现类
package com.cherry.ProductProject.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cherry.ProductProject.entity.Product;
import com.cherry.ProductProject.mapper.ProductMapper;
import com.cherry.ProductProject.service.ProductService;
@Service
public class ProductServiceImpl implements ProductService{
@Autowired
private ProductMapper productMapper;//产品Mapper对象
/**
* 新增产品
* @param 产品对象
* @return 插入条数
* @author itzhiya
*/
@Override
public int insertProduct(Product product) {
// TODO Auto-generated method stub
return productMapper.insertProduct(product);
}
/**
* 移除产品
* @param 产品code
* @return 删除条数
* @author itzhiya
*/
@Override
public int deleteProduct(String code) {
// TODO Auto-generated method stub
return productMapper.deleteProduct(code);
}
/**
* 更新某个产品
* @param 要更新的产品信息
* @return 更新条数
* @author itzhiya
*/
@Override
public int updateProduct(Product product) {
// TODO Auto-generated method stub
return productMapper.updateProduct(product);
}
/**
* 查询所有产品
* @return 产品list
* @author itzhiya
*/
@Override
public List<Product> findAllProducts() {
// TODO Auto-generated method stub
return productMapper.findAllProducts();
}
/**
* 根据产品code,查询某个产品
* @param 产品code
* @return 查询到的产品
* @author itzhiya
*/
@Override
public Product findProductByCode(String code) {
// TODO Auto-generated method stub
return productMapper.findProductByCode(code);
}
}
4.7. 表现层及页面非空验证
Spring Boot内部集成了基本的验证,我们可以直接通过注解的方式完成验证,如下:
1. 创建页面输入表单需要的form,并加入验证
package com.cherry.ProductProject.form;
import javax.validation.constraints.NotEmpty;
public class ProductForm {
@NotEmpty(message="{code.valid}")
private String code;
@NotEmpty(message = "{name.valid}")
private String name;
@NotEmpty(message = "{summary.valid}")
private String summary;
@NotEmpty(message = "{description.valid}")
private String description;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
2. 新建controller文件,代码如下:
package com.cherry.ProductProject.controller;
import javax.validation.Valid;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.cherry.ProductProject.entity.Product;
import com.cherry.ProductProject.form.ProductForm;
import com.cherry.ProductProject.service.ProductService;
@Controller
@RequestMapping("product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 显示增加新产品页面
*
*/
@GetMapping("add")
public String show(ModelMap model) {
model.addAttribute("productForm", new ProductForm());
return "product/add";
}
/**
* 提交新产品,将新产品保存如数据库
* @param productForm 产品信息
* @param bindingResult 校验结果
* @param model
* @return 返回产品列表页
*/
@PostMapping("add")
public String add(@Valid @ModelAttribute ProductForm productForm, BindingResult bindingResult, ModelMap model) {
if(bindingResult.hasErrors()) {
model.addAttribute("productForm", productForm);
return "product/add";
}
Product product = new Product();
BeanUtils.copyProperties(productForm, product);
productService.insertProduct(product);
return "redirect:/product/list";
}
/**
* 显示产品列表页
* @param model
* @return 返回产品列表页面
*/
@GetMapping("list")
public String list(ModelMap model) {
model.addAttribute("products", productService.findAllProducts());
return "product/list";
}
/**
* 显示更新产品页
* @param code 需要更新的产品code
* @param model
* @return 返回更新产品页
*/
@GetMapping("update/{code}")
public String update(@PathVariable("code") String code, ModelMap model) {
Product product = productService.findProductByCode(code);
ProductForm productForm = new ProductForm();
BeanUtils.copyProperties(product, productForm);
model.addAttribute("productForm", productForm);
return "product/update";
}
/**
* 提交更新的产品信息
* @param productForm 更新的产品信息
* @param bindingResult 验证结果
* @param model
* @return 失败,返回更新产品页,成功返回产品列表页
*/
@PostMapping("update/submit")
public String updateSubmit(@Valid @ModelAttribute ProductForm productForm,
BindingResult bindingResult, ModelMap model) {
if(bindingResult.hasErrors()) {
model.addAttribute("productForm", productForm);
return "product/update";
}
Product product = new Product();
BeanUtils.copyProperties(productForm, product);
productService.updateProduct(product);
return "redirect:/product/list";
}
/**
* 移除产品
* @param code 需要移除的产品code
* @param model
* @return 返回产品列表页
*/
@GetMapping("remove/{code}")
public String remove(@PathVariable("code") String code, ModelMap model) {
productService.deleteProduct(code);
return "redirect:/product/list";
}
}
这里,return后面给定“redirect”表示系统执行url为“/product/list”的controller,没有“redirect”的表示系统直接显示templates目录下的product目录下的list.html文件内容。
3. 更新message文件
打开message.properties及message_en.properties,加入下面内容:
product.add.title=Add Product
product.code=Code
product.name=Name
product.summary=Summary
product.description=Description
submit.text=Submit
product.list.title=Product List
code.valid=code is required
name.valid=name is required
summary.valid=summary is required
description.valid=description is required
update.text=Update
remove.text=Remove
打开文件message_zh.properties,加入下面内容,里面直接输入中文即可,系统会自动转码:
product.add.title=\u65B0\u4EA7\u54C1
product.code=\u4EA7\u54C1Code
product.name=\u4EA7\u54C1\u540D\u79F0
product.summary=\u4EA7\u54C1\u7B80\u8FF0
product.description=\u4EA7\u54C1\u63CF\u8FF0
submit.text=\u63D0\u4EA4
product.list.title=\u4EA7\u54C1\u5217\u8868
code.valid=code\u4E0D\u80FD\u4E3A\u7A7A
name.valid=\u540D\u5B57\u4E0D\u80FD\u4E3A\u7A7A
summary.valid=\u7B80\u8FF0\u4E0D\u80FD\u4E3A\u7A7A
description.valid=\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A
update.text=\u66F4\u65B0
remove.text=\u79FB\u9664
4. 新建新增产品页
在resources下面的templates目录中,新建目录product,创建add.html页面,显示新增产品页面,代码如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{product.add.title}">Add Product</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1 th:text="#{product.add.title}">Add Product</h1>
<form th:action="@{/product/add}" method="post" th:object="${productForm}">
<div class="form-group">
<table>
<tr>
<td th:text="#{product.code}">Code</td>
<td><input type="text" th:field="*{code}"/><span th:if="${#fields.hasErrors('code')}" th:errors="*{code}"></span></td>
</tr>
<tr>
<td th:text="#{product.name}">Name</td>
<td><input type="text" th:field="*{name}"/><span th:errors="*{name}">Name is required</span></td>
</tr>
<tr>
<td th:text="#{product.summary}">Summary</td>
<td><textarea th:field="*{summary}"></textarea><span th:errors="*{summary}">Summary is required</span></td>
</tr>
<tr>
<td th:text="#{product.description}">Description</td>
<td><textarea rows="10" cols="40" th:field="*{description}"></textarea><span th:errors="*{description}">Des is required</span></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" th:value="#{submit.text}">
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
5. 新建产品列表页
在resources下面的templates目录下的product目录中,新建list.html页面,显示产品列表内容,代码如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{product.list.title}">Product List</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1 th:text="#{product.list.title}">Product List Page</h1>
<a th:href="@{/product/add}" th:text="#{product.add.title}">新建用户</a>
<table border="1">
<thead>
<tr>
<td th:text="#{product.code}">Code</td>
<td th:text="#{product.name}">Name</td>
<td th:text="#{product.summary}">Summary</td>
<td></td>
<td></td>
</tr>
<tr th:each="product:${products}">
<td th:text="${product.code}"></td>
<td th:text="${product.name}"></td>
<td th:text="${product.summary}"></td>
<td><a th:href="@{'/product/update/' + ${product.code}}" th:text="#{update.text}"></a></td>
<td><a th:href="@{'/product/remove/' + ${product.code}}" th:text="#{remove.text}"></a></td>
</tr>
</thead>
</table>
</body>
</html>
6. 新建产品更新页
在resources下面的templates目录下的product目录中,新建update.html页面, 用于显示产品更新页面, 代码如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{product.add.title}">Add Product</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1 th:text="#{product.add.title}">Add Product</h1>
<form th:action="@{/product/update/submit}" method="post" th:object="${productForm}">
<div class="form-group">
<table>
<tr>
<td th:text="#{product.code}">Code</td>
<td><input type="text" th:field="*{code}"/><span th:if="${#fields.hasErrors('code')}" th:errors="*{code}"></span></td>
</tr>
<tr>
<td th:text="#{product.name}">Name</td>
<td><input type="text" th:field="*{name}"/><span th:errors="*{name}">Name is required</span></td>
</tr>
<tr>
<td th:text="#{product.summary}">Summary</td>
<td><textarea th:field="*{summary}"></textarea><span th:errors="*{summary}">Summary is required</span></td>
</tr>
<tr>
<td th:text="#{product.description}">Description</td>
<td><textarea rows="10" cols="40" th:field="*{description}"></textarea><span th:errors="*{description}">Des is required</span></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" th:value="#{submit.text}">
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
7. 启动项目
右键单击启动类ProductProjectApplicatoin.java,选择【Run As】,选择【Spring Boot APP】
控制台显示如下内容,则表示启动成功,端口8080
8. 多语言显示
打开浏览器,输入http://localhost:8080/product/list
按下新建用户进行测试吧。
如果你想要看到英文效果,请修改浏览器显示语言,下面是修改firefox浏览器的语言:
选择菜单栏的【工具】-》【选项】,点击优先显示的语言的【选择…】按钮
选中【英语】,进行【上移】,选中【确认】按钮
重新刷新页面即可看到下面内容:
9. 验证信息国际化
这时我们会发现,页面非空验证的国际化在不同语言下可以显示不同语言,但是验证信息无法正常显示,这是因为系统默认验证信息文件在resources下的ValidationMessages.properties文件中,由于我们的国际化信息文件文件在i18n目录下,所以我们需要加一个配置类,改变默认的验证信息路径及文件名,代码如下:
package com.cherry.ProductProject.config;
import javax.validation.Validator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/**
* 改变message位置及名称
* @author J
*
*/
@Configuration
public class ValidationConfig {
@Bean
public Validator getValidator() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("i18n/message");
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
}