本章内容
1. 概念
2. 构建SpringBoot项目
3. YAML
4. SpringBoot日志配置
5. SpringBoot单元测试
6. Thymeleaf模板
7. JdbcTemplate
8. 分页
9. 上传下载
10. SpringBoot打包、部署
概念
介绍
官网地址: https://spring.io/
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过
程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。Spring Boot致力于
在蓬勃发展的快速应用开发领域成为领导者。
2013年,Pivotal团队开始研发SpringBoot。
2014年4月,发布全新开源的轻量级框架的第一个SpringBoot版本。
特点
为什么要学习SpringBoot?
SpringBoot是为了简化Spring应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性
让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的
搭建出一个 WEB 工程
未接触SpringBoot之前,搭建一个普通的WEB工程往往需要花费30分钟左右,如果遇到点奇葩的问题耽搁的
时间会更长一点,但自从用了SpringBoot后,真正体会到什么叫分分钟搭建一个WEB,让我拥有更多的时间
跟我的小伙伴们唠嗑了。使用SpringBoot后发现一切是如此的简单(还记得被JAR包,xml支配的恐惧吗,如
今都可以说 good bye)
在传统的一些框架中(SSH、SSM、Servlet/JSP) 虽然功能很强大但是配置起来太麻烦了,相比python或者
php ,Java就略显臃肿,主要问题集中在两点:
复杂的配置文件:
项目之间各种配置文件铺天盖地,在开发的过程中占用了大量的时间,并且需要做这些配置文件进行维护,
整个项目感觉特别复杂,并且Java代码和配置文件之间的切换开发,给程序人员带来很大的负担。
各种依赖比较混乱:
主要问题就是版本冲突,不同技术的版本需要我们知道哪些版本存在冲突问题,一旦使用错误就需要重新再
去下载库进行依赖。
而Spring Boot简化了基于Spring的应用开发,只需要一个”run”就创建了一个独立的、生产级别的Spring应
用,Spring boot为Spring平台及第三方提供了开箱即用的设置(默认设置的包就是启动器starter),这样我们
就可以简单的开始。Spring Boot主张,用最少的配置做更多的事。如果我们创建一个Java应用,并使用java
-jar启动,就能得到一个生产级别的web工程。
特点:
创建独立的 Spring 应用程序
直接嵌入Tomcat、Jetty或Undertow(无需部署WAR文件)
提供自以为是的“入门”依赖项以简化您的构建配置
尽可能自动配置 Spring 和 3rd 方库
提供生产就绪功能,例如指标、运行状况检查和外部化配置
完全不需要代码生成,也不需要 XML 配置
SpringBoot = Spring+SpringMVC
构建SpringBoot项目
start.spring.io
![](https://i-blog.csdnimg.cn/blog_migrate/d141e6f68ad57f4da3cb09d9b4b4bdc8.png)
在IDER中构建SpringBoot项目
创建项目
![](https://i-blog.csdnimg.cn/blog_migrate/3a4b225d7cc75dc30bc0393347b2ad90.png)
选择依赖
![](https://i-blog.csdnimg.cn/blog_migrate/0a5482b82fc7743704567d5a6a484402.png)
创建完成结构
![](https://i-blog.csdnimg.cn/blog_migrate/0a682b82685c509c0b27125bd6bf5b18.png)
运行项目
点击选择启动项目工程
![](https://i-blog.csdnimg.cn/blog_migrate/d5d1ca7c3d63160aaa5ada3bda770533.png)
观察启动日志
![](https://i-blog.csdnimg.cn/blog_migrate/4b06945d61728f05bd1341f598365870.png)
前后台交互
创建控制层
![](https://i-blog.csdnimg.cn/blog_migrate/6144836e971e46d9a9bfb9ad73edad61.png)
重启项目并访问
http://localhost:8080/example/show
输出:
![](https://i-blog.csdnimg.cn/blog_migrate/99aa54355e89b14435399477e76812a7.png)
至此 前后台交互完成!!
目录结构
工程结构
![](https://i-blog.csdnimg.cn/blog_migrate/9a75afaaf9d38fe61fdd0e3581ecc470.png)
pom文件
<?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.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 项目信息 -->
<groupId>com.its</groupId>
<artifactId>HelloSpringBoot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>HelloSpringBoot</name>
<description>HelloSpringBoot</description>
<!-- 属性配置 -->
<properties>
<java.version>1.8</java.version>
</properties>
<!-- 依赖信息 -->
<dependencies>
<!-- SpringBoot web工程依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
自定义Banner
SpringBoot启动的时候我们可以看到如下内容,这一块其实是可以自定义的哦,而且在 2.X 版本中,它支持
的格式从文本扩展到**banner.txt、banner.jpg、banner.gif、banner.jpeg等等,只需要在resouces**目
录下添加指定命名的文件即可
![](https://i-blog.csdnimg.cn/blog_migrate/9bf2be67480c384165807f65369ada99.png)
重启运行
![](https://i-blog.csdnimg.cn/blog_migrate/d1e16bcb8fe3c7803500e1fa1fd2e502.png)
查看SpringBoot默认提供的Bean
![](https://i-blog.csdnimg.cn/blog_migrate/840f3cb546e5d7bface1f3027ce87d62.png)
手动注入bean
![](https://i-blog.csdnimg.cn/blog_migrate/7b6e1ded5011c57435e2e215d5caec8e.png)
运行查看是否注入
控制层可以注解读取
初窥配置文件
从启动日志中可以发现,SpringBoot默认的端口是 8080 ,那么如果端口被占用了怎么办呢?不要慌,问题
不大,配置文件分分钟解决你的困扰…
![](https://i-blog.csdnimg.cn/blog_migrate/d91d8ebb7ce4239f74d37646c03de12b.png)
关于starter
stater 参赛人、发令员
Spring Boot 中的starter 只是把我们某一模块,比如web 开发时所需要的所有JAR 包打包好给我们而已。不
过它的厉害之处在于,能自动把配置文件搞好,不用我们手动配置。
spring-boot-starter-web
spring-boot-starter-parent
这是 Spring Boot 的父级依赖,这样当前的项目就是 Spring Boot 项目了。spring-boot-starter-parent 是一
个特殊的 starter。
功能 1:默认依赖配置(绝对兼容)
它用来提供相关的 Maven 默认依赖。使用它之后,常用的包依赖可以省去 version 标签,当我们搭建web
应用的时候,
可以像下面这样添加spring-boot-starter-web依赖:
![](https://i-blog.csdnimg.cn/blog_migrate/793fccbc542051dff700ef18d9c4a7eb.png)
但是这种允许默认不填写版本号的依赖也必须是boot中默认给定的,如果没有默认给定,还是需要手动填写
的。
功能 2:默认环境配置(绝对兼容)
默认使用Java8,可添加以下配置修改版本
默认使用UTF-8编码,可添加以下配置修改编码
等...
![](https://i-blog.csdnimg.cn/blog_migrate/6bd1a744f4477dd62e39ad51f56d65e2.png)
功能 3:资源过滤
识别默认配置文件
![](https://i-blog.csdnimg.cn/blog_migrate/5f211e8d7fa8584ae2caca7e29fd6289.png)
过滤打包内容(打包的时候把 src/main/resources 下所有文件都打包到包中)
![](https://i-blog.csdnimg.cn/blog_migrate/8d08d8c3bde1603b59151b7a180b06cf.png)
YAML文件
在我们翻阅stater的过程中,也应该发现配置文件除了可是使用application*.properties类型,还可以使用后
缀为.yml或.yaml类型,这种类型相比properties类型支持了集合对象等数据,但是前提也必须是
application*才能被Spring扫描到。
YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语
言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这
种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
配置环境基础信息,创建application.yaml
语法规则
1. 大小写敏感
2. 使用缩进表示层级关系
3. 缩进时不允许使用Tab键,只允许使用空格。
4. 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
5. 可以使用 "-小写字母" 或 "_小写字母"来 代替 "大写字母",如 userName 与 user-name ,user_name 含
义是一样的
6. key: value 格式书写 key 后面跟着冒号,再后面跟着一个空格,然后是值
7. 注释使用#
数据类型
YAML 支持的数据结构有三种
1. 普通的值(数字,字符串,布尔)
2. 对象、Map (属性和值) (键值对)
3. 数组 (List、Set)
测试代码
#测试普通的值,注意字符串不用加单引号或者双引号
name: 张三
age: 12
#测试对象,对象还是k: v的方式 -- 注意缩进(不支持tab,使用空格)
stu:
name: 李四
age: 55
student:
studentName: 李四
#测试数组,使用- 值表示数组中的一个元素
hobbies:
- 打篮球
- 羽毛球
- 踢足球
#组合形式 对象加数组
animal:
fly:
- 喜鹊
- 大雁
run:
- 犀牛
- 河马
#组合形式 数组加对象
clazz:
- {name: 翠花,age: 18}
- {name: 李四,age: 20}
读取配置文件
@Controller
@RequestMapping("/userController")
public class UserController {
@Value("${name}")
private String name;
@Value("${age}")
private String age;
@Value("${stu.name}")
private String stu;
@Value("${hobbies[0]}")
private String hobbies;
@Value("${animal.fly[0]}")
private String ob;
@Value("${clazz[1].name}")
private String ac;
@RequestMapping("/show")
public void show(){
System.out.println("基本类型:"+name+"-"+age);
System.out.println("对象中的值:"+stu);
System.out.println("数组中的值:"+hobbies);
System.out.println("对象中的数组:"+ob);
System.out.println("数组中的对象:"+ac);
}
}
关于直接获取数组类型
读取yml中配置的一个数组,通过@Vaule一直获取不到,通过一番资料的查询,才彻底清楚了@Vaule的使
用情况。
在Spring中读取配置文件的快捷方法常见的有两种,一个是通过@Vaule注解进行单一字段的注入,另外一种
方法就是通过@ConfigurationProperties注解来进行批量注入。
![](https://i-blog.csdnimg.cn/blog_migrate/36b83baffe11a326ffc013decd8e305b.png)
student:
name: 张三
age: 88
hobbies: #对象中包含数组
- 抽烟
- 喝酒
- 烫头
声明配置类
/**
* 配置类 对象的创建交给IOC
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "student")
public class StudentEntityDataConfig {
private String name;
private int age;
private String[] hobbies;
}
使用方法
@Autowired
private StudentEntityDataConfig studentEntityDataConfig;
SpringBoot日志配置
Spring Boot内部采用的是Commons Logging进行日志记录,但在底层为**Java Util Logging+Log4J2、
Logback**等日志框架提供了默认配置 。
Java 虽然有很多可用的日志框架,但请不要担心,一般来说,使用 SpringBoot 默认的 Logback 就可以了。
日志格式
![](https://i-blog.csdnimg.cn/blog_migrate/1374cff8f663cdf4d0696a7ccc92d20e.png)
输出如下元素:
Logback是没有FATAL级别的日志,它将被映射到ERROR
1. 时间日期:精确到毫秒,可以用于排序
2. 日志级别:ERROR、WARN、INFO、DEBUG、TRACE
3. 进程ID
4. 分隔符:采用---来标识日志开始部分
5. 线程名:方括号括起来
6. Logger名:通常使用源代码的类名
7. 日志内容:我们输出的消息
日志级别拓展log4j
log4j定义了很全的日志级别,分别是:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、ALL,一
共8个级别的log,它们的优先级顺序为:OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE >
ALL。
但是除去一些不常用的日志级别(如OFF、FATAL、TRACE、ALL)。其实,我们一般经常使用ERROR、
WARN、INFO、DEBUG这四种级别。而Log4j也建议我们使用四个级别,优先级从高到低分别是ERROR、
WARN、INFO、DEBUG。通过在这里定义的级别,可以控制到应用程序中相应级别的日志信息的开关。比
如在这里定义了INFO级别,
则应用程序中所有DEBUG级别的日志信息将不被打印出来。而优先级高的将被打印出来。项目上生产环境时
候一定得把debug的日志级别重新调为warn或者更高,避免产生大量日志。
1. OFF:最高等级的,用于关闭所有日志记录。
2. FATAL:指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别
你可以直接停止程序了。
3. ERROR:指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出
太多的日志,可以使用这个级别。
4. WARN:表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。
5. INFO:消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这
个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
6. DEBUG:指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信
息。
7. TRACE:跟踪日志,日志消息的粒度太细,很低的日志级别,一般不会使用。
8. ALL:最低等级的,用于打开所有日志记录。
日志输出
![](https://i-blog.csdnimg.cn/blog_migrate/af8d08049fb2ef5531a00c627e1f5c20.png)
文件保存
默认情况下,SpringBoot仅将日志输出到控制台,不会写入到日志文件中去。如果除了控制台输出之外还想
写日志文件,则需要在application.properties设置logging.file或logging.path属性。
logging:
level:
org.springframework: info
com.xja: info #建议:info 或者 warn
4logging.file:将日志写入到指定的文件中,默认为相对路径,可以设置成绝对路径
logging.path:将名为spring.log写入到指定的文件夹中,如(/var/log)
日志文件在达到10MB时进行切割,产生一个新的日志文件(如:spring.1.log、spring.2.log),新的日志依
旧输出到spring.log中去,默认情况下会记录ERROR、WARN、INFO级别消息。
logging.file.max-size:限制日志文件大小
logging.file.max-history:限制日志保留天数
![](https://i-blog.csdnimg.cn/blog_migrate/1ce57ba8dae93257f44b3fba6aac07f1.png)
单元测试
![](https://i-blog.csdnimg.cn/blog_migrate/ad3f3ca120d201f16aa4a5cf9af36d03.png)
在实际开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确。
Spring Boot对项目的单元测试提供了很好的支持,在使用时,需要提前在项目的pom.xml文件中添加
spring-boot-starter-test测试依赖启动器,可以通过相关注解实现单元测试。这里,以之前创建的
chapter01项目为例对Spring Boot项目的单元测试进行使用演示。
添加依赖
添加spring-boot-starter-test测试依赖启动器
在项目的pom.xml文件中添加spring-boot-starter-test测试依赖启动器,示例代码如下。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
上述代码中,展示了Spring Boot框架集成单元测试提供的依赖启动器,其范围默认为test。需要说明的是,
如果是使用Spring Initializr方式搭建的Spring Boot项目,会自动加入spring-boot-starter-test测试依赖启动
器,无需开发者再手动添加。
编写单元测试类
在项目中添加测试依赖启动器后,可以编写Spring Boot项目中相关方法对应的单元测试。
如果是使用Spring Initializr方式搭建的Spring Boot项目,会在src.test.java测试目录下自动创建与项目主程
序启动类对应的单元测试类。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 使用Spring Initializr方式自动创建的主程序启动类对应的单元测试类
*/
@RunWith(SpringRunner.class) // 测试启动器,并加载Spring Boot测试注解
@SpringBootTest // 标记为Spring Boot单元测试类,并加载项目的ApplicationContext上下文环境
public class Chapter01ApplicationTests {
// 自动创建的单元测试方法示例
@Test
public void contextLoads() {
}
}
JdbcTemplate
![](https://i-blog.csdnimg.cn/blog_migrate/640e864b9a874d00623eea6dc8143074.png)
Spring Framework对数据库的操作在JDBC上面做了深层次的封装,通过依赖注入功能,可以将 DataSource
注册到JdbcTemplate之中,使我们可以轻易的完成对象关系映射,并有助于规避常见的错误,在
SpringBoot中我们可以很轻松的使用它。
特点:
速度快,对比其它的ORM框架而言,JDBC的方式无异于是最快的
配置简单,Spring自家出品,几乎没有额外配置
学习成本低,毕竟JDBC是基础知识,JdbcTemplate更像是一个DBUtils
整合JdbcTemplate
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
在 application.properties 中添加如下配置。值得注意的是,SpringBoot默认会自动配置 DataSource ,
它将优先采用 HikariCP 连接池,如果没有该依赖的情况则选取 tomcat-jdbc ,如果前两者都不可用最后选
取 Commons DBCP2 。通过**spring.datasource.type属性可以指定其它种类的连接池**
![](https://i-blog.csdnimg.cn/blog_migrate/e0395f0e4e33f2a4d5614c60c60b945c.png)
案例测试
这里使用的是单元测试进行的练习
创建两张表
-- ----------------------------
-- Table structure for cargo
-- ----------------------------
DROP TABLE IF EXISTS `cargo`;
CREATE TABLE `cargo` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`unit` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`number` int(0) DEFAULT NULL,
`price` double(10, 2) DEFAULT NULL,
`vendor` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`category_id` int(0) DEFAULT NULL,
`entry_time` datetime(0) DEFAULT NULL,
`operator` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for category
-- ----------------------------
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
实体类
//cargo实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Cargo {
private Integer id;
private String name;
private String unit;
private Integer number;
private Double price;
private String vendor;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date entryTime;
private String operator;
private Integer categoryId;
private Category category;
//查询的最大时间
private Date bigDate;
//查询的最小时间
private Date littleDate;
public Cargo(Integer id, String name, String unit, Integer number, Double price, String vendor, Date entryTime, String operator, Integer categoryId) {
this.id = id;
this.name = name;
this.unit = unit;
this.number = number;
this.price = price;
this.vendor = vendor;
this.entryTime = entryTime;
this.operator = operator;
this.categoryId = categoryId;
}
}
//category实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Category {
private Integer id;
private String name;
}
dao接口
public interface CargoDao {
/**
* 查询所有(根据名称模糊查询和时间查询)
* @return
*/
List<Cargo> queryCargoList(Cargo cargo);
/**
* 根据id查询
* @param id
* @return
*/
Cargo queryCargoById(Integer id);
/**
* 添加
* @param cargo
* @return
*/
Integer saveCargo(Cargo cargo);
/**
* 修改
* @param cargo
* @return
*/
Integer updateCargo(Cargo cargo);
/**
* 删除
* @param id
* @return
*/
Integer deleteCargo(Integer id);
}
public interface CategoryDao {
/**
* 查询所有
* @return
*/
List<Category> queryCategoryList();
/**
* 根据id查询
* @param id
* @return
*/
Category queryCategoryById(Integer id);
/**
* 添加
* @param category
* @return
*/
Integer saveCategory(Category category);
/**
* 修改
* @param category
* @return
*/
Integer updateCategory(Category category);
/**
* 删除
* @param id
* @return
*/
Integer deleteCategory(Integer id);
}
dao接口的实现类
@Repository
public class CargoDaoImpl implements CargoDao {
@Autowired
private JdbcTemplate jdbc;
/**
* 查询所有(根据名称模糊查询和时间查询)
*
* @param cargo
* @return
*/
@Override
public List<Cargo> queryCargoList(Cargo cargo) {
String sql = "select * from cargo ";
List<Cargo> list = null;
if (cargo.getName() != null && cargo.getBigDate() != null && cargo.getLittleDate() != null) {
sql += "where name like ? and entry_time > ? and entry_time < ?";
list = jdbc.query(sql, new BeanPropertyRowMapper<>(Cargo.class), "%" + cargo.getName() + "%", cargo.getBigDate() ,cargo.getLittleDate());
} else if (cargo.getBigDate() != null && cargo.getLittleDate() != null) {
sql += "where entry_time > ? and entry_time < ? ";
list = jdbc.query(sql, new BeanPropertyRowMapper<>(Cargo.class), cargo.getBigDate() ,cargo.getLittleDate());
} else if (cargo.getBigDate() != null) {
sql += "where entry_time > ?";
list = jdbc.query(sql, new BeanPropertyRowMapper<>(Cargo.class), cargo.getBigDate());
}else if (cargo.getLittleDate() != null){
sql += "where entry_time < ? ";
list = jdbc.query(sql, new BeanPropertyRowMapper<>(Cargo.class), cargo.getLittleDate());
}else if (cargo.getName() != null) {
sql += "where name like ? ";
list = jdbc.query(sql, new BeanPropertyRowMapper<>(Cargo.class), "%" + cargo.getName() + "%");
} else {
list = jdbc.query(sql, new BeanPropertyRowMapper<>(Cargo.class));
}
return list;
}
/**
* 根据id查询
*
* @param id
* @return
*/
@Override
public Cargo queryCargoById(Integer id) {
String sql = "select * from cargo where id = ?";
Cargo cargo = jdbc.queryForObject(sql, new BeanPropertyRowMapper<>(Cargo.class), id);
return cargo;
}
/**
* 添加
*
* @param cargo
* @return
*/
@Override
public Integer saveCargo(Cargo cargo) {
String sql = "insert into cargo (name,unit,number,price,vendor,entry_time,operator,category_id) value(?,?,?,?,?,?,?,?)";
int update = jdbc.update(sql, cargo.getName(), cargo.getUnit(), cargo.getNumber(), cargo.getPrice(), cargo.getVendor(), cargo.getEntryTime(), cargo.getOperator(), cargo.getCategoryId());
return update;
}
/**
* 修改
*
* @param cargo
* @return
*/
@Override
public Integer updateCargo(Cargo cargo) {
String sql = "update cargo set name = ?,unit = ?,number = ?,price = ?,vendor = ?,entry_time = ?,operator = ?,category_id = ? where id = ?";
int update = jdbc.update(sql, cargo.getName(), cargo.getUnit(), cargo.getNumber(), cargo.getPrice(), cargo.getVendor(), cargo.getEntryTime(), cargo.getOperator(), cargo.getCategoryId(), cargo.getId());
return update;
}
/**
* 删除
*
* @param id
* @return
*/
@Override
public Integer deleteCargo(Integer id) {
String sql = "delete from cargo where id = ?";
int update = jdbc.update(sql, id);
return update;
}
}
@Repository
public class CategoryDaoImpl implements CategoryDao {
@Autowired
private JdbcTemplate jdbc;
/**
* 查询所有
*
* @return
*/
@Override
public List<Category> queryCategoryList() {
String sql = "select * from category";
List<Category> list = jdbc.query(sql, new BeanPropertyRowMapper<>(Category.class));
return list;
}
/**
* 根据id查询
*
* @param id
* @return
*/
@Override
public Category queryCategoryById(Integer id) {
String sql = "select * from category where id = ?";
Category category = jdbc.queryForObject(sql, new BeanPropertyRowMapper<>(Category.class), id);
return category;
}
/**
* 添加
*
* @param category
* @return
*/
@Override
public Integer saveCategory(Category category) {
String sql = "insert into category (name)" +
"value(?)";
int i = jdbc.update(sql, category.getName());
return i;
}
/**
* 修改
*
* @param category
* @return
*/
@Override
public Integer updateCategory(Category category) {
String sql = "update category set name = ? where id = ?";
int i = jdbc.update(sql, category.getName(),category.getId());
return i;
}
/**
* 删除
*
* @param id
* @return
*/
@Override
public Integer deleteCategory(Integer id) {
String sql = "delete from category where id = ?";
int i = jdbc.update(sql, id);
return i;
}
}
service接口
public interface CargoService {
/**
* 查询所有(根据名称模糊查询和时间查询)
* @return
*/
List<Cargo> queryCargoList(Cargo cargo);
/**
* 根据id查询
* @param id
* @return
*/
Cargo queryCargoById(Integer id);
/**
* 添加
* @param cargo
* @return
*/
Integer saveCargo(Cargo cargo);
/**
* 修改
* @param cargo
* @return
*/
Integer updateCargo(Cargo cargo);
/**
* 删除
* @param id
* @return
*/
Integer deleteCargo(Integer id);
}
public interface CategoryService {
/**
* 查询所有
* @return
*/
List<Category> queryCategoryList();
/**
* 根据id查询
* @param id
* @return
*/
Category queryCategoryById(Integer id);
/**
* 添加
* @param category
* @return
*/
Integer saveCategory(Category category);
/**
* 修改
* @param category
* @return
*/
Integer updateCategory(Category category);
/**
* 删除
* @param id
* @return
*/
Integer deleteCategory(Integer id);
}
service接口的实现类
@Service
@Transactional
public class CargoServiceImpl implements CargoService {
@Autowired
private CargoDao cargoDao;
@Autowired
private CategoryDao categoryDao;
/**
* 查询所有(根据名称模糊查询和时间查询)
*
* @param cargo
* @return
*/
@Override
public List<Cargo> queryCargoList(Cargo cargo) {
List<Cargo> cargos = cargoDao.queryCargoList(cargo);
cargos.forEach(x -> x.setCategory(categoryDao.queryCategoryById(x.getCategoryId())));
return cargos;
}
/**
* 根据id查询
*
* @param id
* @return
*/
@Override
public Cargo queryCargoById(Integer id) {
Cargo cargo = cargoDao.queryCargoById(id);
cargo.setCategory(categoryDao.queryCategoryById(cargo.getCategoryId()));
return cargo;
}
/**
* 添加
*
* @param cargo
* @return
*/
@Override
public Integer saveCargo(Cargo cargo) {
return cargoDao.saveCargo(cargo);
}
/**
* 修改
*
* @param cargo
* @return
*/
@Override
public Integer updateCargo(Cargo cargo) {
return cargoDao.updateCargo(cargo);
}
/**
* 删除
*
* @param id
* @return
*/
@Override
public Integer deleteCargo(Integer id) {
return cargoDao.deleteCargo(id);
}
}
@Service
@Transactional
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryDao categoryDao;
/**
* 查询所有
*
* @return
*/
@Override
public List<Category> queryCategoryList() {
return categoryDao.queryCategoryList();
}
/**
* 根据id查询
*
* @param id
* @return
*/
@Override
public Category queryCategoryById(Integer id) {
return categoryDao.queryCategoryById(id);
}
/**
* 添加
*
* @param category
* @return
*/
@Override
public Integer saveCategory(Category category) {
return categoryDao.saveCategory(category);
}
/**
* 修改
*
* @param category
* @return
*/
@Override
public Integer updateCategory(Category category) {
return categoryDao.updateCategory(category);
}
/**
* 删除
*
* @param id
* @return
*/
@Override
public Integer deleteCategory(Integer id) {
return categoryDao.deleteCategory(id);
}
}
单元测试
@SpringBootTest
public class CargoServiceImplTest {
@Autowired
private CargoService cargoService;
/**
* 查询所有(根据名称模糊查询和时间查询)
* @return
*/
@Test
public void testQueryCargoList() throws ParseException {
Cargo cargo = new Cargo();
cargo.setName("6");
cargo.setBigDate(DateTurnStrigUtil.dateParse("2023-04-08 19:44:50"));
cargo.setLittleDate(DateTurnStrigUtil.dateParse("2023-04-08 19:44:50"));
List<Cargo> list = cargoService.queryCargoList(cargo);
list.forEach(System.out::println);
}
/**
* 根据id查询
* @return
*/
@Test
public void testQueryCargoById() {
Cargo cargo = cargoService.queryCargoById(1);
System.out.println("cargo = " + cargo);
}
/**
* 添加
* @return
*/
@Test
public void testSaveCargo() {
Cargo cargo = new Cargo(1,"77","77",77,77.0,"77",new Date(),"77",1);
Integer integer = cargoService.saveCargo(cargo);
System.out.println("integer = " + integer);
}
/**
* 修改
* @return
*/
@Test
public void testUpdateCargo() {
Cargo cargo = new Cargo(7,"88","8",88,88.8,"88",new Date(),"88",1);
Integer integer = cargoService.updateCargo(cargo);
System.out.println("integer = " + integer);
}
/**
* 删除
* @return
*/
@Test
public void testDeleteCargo() {
Integer integer = cargoService.deleteCargo(7);
System.out.println("integer = " + integer);
}
}
@SpringBootTest
public class CategoryServiceImplTest {
@Autowired
private CategoryService categoryService;
/**
* 查询所有
*
* @return
*/
@Test
public void testQueryCategoryList() {
List<Category> list = categoryService.queryCategoryList();
list.forEach(System.out::println);
}
/**
* 根据id查询
*
* @return
*/
@Test
public void testQueryCategoryById() {
Category category = categoryService.queryCategoryById(6);
System.out.println("category = " + category);
}
/**
* 添加
*
* @return
*/
@Test
public void testSaveCategory() {
Integer ee = categoryService.saveCategory(new Category(1, "ee"));
System.out.println("ee = " + ee);
}
/**
* 修改
*
* @return
*/
@Test
public void testUpdateCategory() {
Integer pp = categoryService.updateCategory(new Category(7, "pp"));
System.out.println("pp = " + pp);
}
/**
* 删除
*
* @return
*/
@Test
public void testDeleteCategory() {
Integer integer = categoryService.deleteCategory(8);
System.out.println("integer = " + integer);
}
}
Thymeleaf模板
介绍
Thymeleaf是用来开发Web和独立环境项目的现代服务器端Java模板引擎。
Thymeleaf的主要目标是为您的开发工作流程带来优雅的自然模板 - HTML。可以在直接浏览器中正确显示,
并且可以作为静态原型,从而在开发团队中实现更强大的协作。
借助Spring Framework的模块,可以根据自己的喜好进行自由选择,可插拔功能组件,Thymeleaf是现代
HTML5 JVM Web开发的理想选择 - 尽管它可以做的更多。
Spring官方支持的服务的渲染模板中,并不包含jsp。而是Thymeleaf和Freemarker等,而Thymeleaf与
SpringMVC的视图技术,及SpringBoot的自动化配置集成非常完美,几乎没有任何成本,你只用关注
Thymeleaf的语法即可。
特点:
动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态
效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html
标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,
所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静
态内容,使页面动态显示。
开箱即用:它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免
每天套模板、该jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
多方言支持:Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的
实现表单绑定、属性编辑器、国际化等功能。
·与SpringBoot完美整合,SpringBoot提供了Thymeleaf的默认配置,并且为Thymeleaf设置了视图解
析器,我们可以像以前操作jsp一样来操作Thymeleaf。代码几乎没有任何区别,就是在模板语法上有区
别。
基本设置
创建环境
勾选web和Thymeleaf的依赖:
默认配置
不需要做任何配置,启动器已经帮我们把Thymeleaf的视图器配置完成
![](https://i-blog.csdnimg.cn/blog_migrate/03c28838057a2ca90987ad4de944c587.png)
而且,还配置了模板文件(html)的位置,与jsp类似的前缀+ 视图名 + 后缀风格:
![](https://i-blog.csdnimg.cn/blog_migrate/59688e9b6360942b6ee3027b37dc3365.png)
默认前缀: classpath:/templates/
默认后缀: .html
所以如果我们返回视图: users ,会指向到 classpath:/templates/users.html
Thymeleaf默认会开启页面缓存,提高页面并发能力。但会导致我们修改页面不会立即被展现,因此我们关
闭缓存:
![](https://i-blog.csdnimg.cn/blog_migrate/0dd3bdc4d304dfb271796370ce71fbcd.png)
另外,修改完毕页面,需要使用快捷键: Ctrl + Shift + F9 来刷新工程。
快速开始
我们准备一个controller,控制视图跳转:
![](https://i-blog.csdnimg.cn/blog_migrate/4e0dd6148d870e692663da715fce12ad.png)
新建一个html模板:
![](https://i-blog.csdnimg.cn/blog_migrate/f2a2717891d8baba70412d450a0bd438.png)
注意,把html 的名称空间,改成: xmlns:th="http://www.thymeleaf.org" 会有语法提示
启动项目,访问页面:
![](https://i-blog.csdnimg.cn/blog_migrate/0d51d091115e88fb4ac2b19f3d96352d.png)
语法
Thymeleaf的主要作用是把model中的数据渲染到html中,因此其语法主要是如何解析model中的数据。从
以下方面来学习:
变量
方法
条件判断
循环
运算
逻辑运算
布尔运算
比较运算
条件运算
其它
变量
我们先新建一个实体类:User
![](https://i-blog.csdnimg.cn/blog_migrate/9fa7a80914c621992ce1d0aaa07b7562.png)
然后在模型中添加数据
![](https://i-blog.csdnimg.cn/blog_migrate/ff1b53c8fe5983cea621f9c59ec9bc20.png)
语法说明:
Thymeleaf通过 ${} 来获取model中的变量,注意这不是el表达式,而是ognl表达式,但是语法非常像。
示例:
我们在页面获取user数据:
![](https://i-blog.csdnimg.cn/blog_migrate/e5aba6eccecc5dc7793a0034945c472c.png)
效果:
感觉跟el表达式几乎是一样的。不过区别在于,我们的表达式写在一个名为: th:text 的标签属性中,这个
叫做 指令
动静结合
指令:
Thymeleaf崇尚 自然模板 ,意思就是模板是纯正的html代码,脱离模板引擎,在纯静态环境也可以直接运
行。现在如果我们直接在html中编写 ${} 这样的表达式,显然在静态环境下就会出错,这不符合Thymeleaf
的理念。
Thymeleaf中所有的表达式都需要写在 指令 中,指令是HTML5中的自定义属性,在Thymeleaf中所有指令都
是以 th: 开头。因为表达式 ${user.name} 是写在自定义属性中,因此在静态环境下,表达式的内容会被当
做是普通字符串,浏览器会自动忽略这些指令,这样就不会报错了!
现在,我们不经过SpringMVC,而是直接用浏览器打开页面看看:
![](https://i-blog.csdnimg.cn/blog_migrate/2d76b3892843f9ed9f7514fd7a449f5f.png)
静态页面中, th 指令不被识别,但是浏览器也不会报错,把它当做一个普通属性处理。这样 span 的默
认值 请登录 就会展现在页面
如果是在Thymeleaf环境下, th 指令就会被识别和解析,而 th:text 的含义就是替换所在标签中的文
本内容,于是 user.name 的值就替代了 span 中默认的请登录
指令的设计,正是Thymeleaf的高明之处,也是它优于其它模板引擎的原因。动静结合的设计,使得无论是
前端开发人员还是后端开发人员可以完美契合。
向下兼容
但是要注意,如果浏览器不支持Html5怎么办?
如果不支持这种 th: 的命名空间写法,那么可以把 th:text 换成 data-th-text ,Thymeleaf也可以兼容。
escape
另外, th:text 指令出于安全考虑,会把表达式读取到的值进行处理,防止html的注入。
例如, <p>你好</p> 将会被格式化输出为 $lt;p$gt;你好$lt;/p$lt; 。
如果想要不进行格式化输出,而是要输出原始内容,则使用 th:utext 来代替.
ognl表达式的语法糖
刚才获取变量值,我们使用的是经典的 对象.属性名 方式。但有些情况下,我们的属性名可能本身也是变量,
怎么办?
ognl提供了类似js的语法方式:
例如: ${user.name} 可以写作 ${user['name']}
自定义变量
场景
看下面的案例:
![](https://i-blog.csdnimg.cn/blog_migrate/c611eaf5d138a765113224065e6c93c7.png)
我们获取用户的所有信息,分别展示。
当数据量比较多的时候,频繁的写 user. 就会非常麻烦。
因此,Thymeleaf提供了自定义变量来解决:
示例:
![](https://i-blog.csdnimg.cn/blog_migrate/b5f54031e0874ff3dd6868577350c322.png)
首先在 h2 上 用 th:object="${user}" 获取user的值,并且保存
然后,在 h2 内部的任意元素上,可以通过 *{属性名} 的方式,来获取user中的属性,这样就省去了大量的
user. 前缀了
方法
![](https://i-blog.csdnimg.cn/blog_migrate/14246bf4e55a44d9e0b76e0267835d67.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a2ba38599d1d2ac38829f72019f51e58.png)
Thymeleaf提供的全局对象:
![](https://i-blog.csdnimg.cn/blog_migrate/20f30edf6ce06731ae0372a2fcff7f58.png)
举例
我们在环境变量中添加日期类型对象
![](https://i-blog.csdnimg.cn/blog_migrate/df635021f0e96b857e86416f6132f9ce.png)
在页面中处理
![](https://i-blog.csdnimg.cn/blog_migrate/49b0017426fd048d781bf0a9373a21b5.png)
字面值
有的时候,我们需要在指令中填写基本类型如:字符串、数值、布尔等,并不希望被Thymeleaf解析为变
量,这个时候称为字面值。
字符串字面值
使用一对 ' 引用的内容就是字符串字面值了:
![](https://i-blog.csdnimg.cn/blog_migrate/8ff7256e3ed56c46915f97a9908824b4.png)
th:text 中的thymeleaf并不会被认为是变量,而是一个字符串
数字字面值
数字不需要任何特殊语法, 写的什么就是什么,而且可以直接进行算术运算
![](https://i-blog.csdnimg.cn/blog_migrate/d00663dbbe0fba1eb9c52b4f65c49d88.png)
布尔字面值
布尔类型的字面值是true或false
![](https://i-blog.csdnimg.cn/blog_migrate/3b3fe90b864c9c282e3da853a9c83d4e.png)
这里引用了一个 th:if 指令,跟vue中的 v-if 类似
拼接
我们经常会用到普通字符串与表达式拼接的情况:
![](https://i-blog.csdnimg.cn/blog_migrate/fd1d425e4bb9017178601dca953486a9.png)
字符串字面值需要用 '' ,拼接起来非常麻烦,Thymeleaf对此进行了简化,使用一对 | 即可:
![](https://i-blog.csdnimg.cn/blog_migrate/fcd57426afd65f04208cf1509d695328.png)
与上面是完全等效的,这样就省去了字符串字面值的书写。
运算
算术运算
支持的算术运算符: + - * / %
![](https://i-blog.csdnimg.cn/blog_migrate/fda9705ea33371328183d7282b5e875e.png)
比较运算
支持的比较运算: > , < , >= and <= ,但是 > , < 不能直接使用,因为xml会解析为标签,要使用别名。
注意 == and != 不仅可以比较数值,类似于equals的功能。
可以使用的别名: gt (>), lt (<), ge (>=), le (<=), not (!). Also eq (==), neq/ne (!=).
条件运算
三元运算
![](https://i-blog.csdnimg.cn/blog_migrate/c0049fd7912533ed3a17fcf7c6c76ef7.png)
三元运算符的三个部分:conditon ? then : else
condition:条件
then:条件成立的结果
else:不成立的结果
其中的每一个部分都可以是Thymeleaf中的任意表达式。
默认值
有的时候,我们取一个值可能为空,这个时候需要做非空判断,可以使用 表达式 ?: 默认值 简
写:
![](https://i-blog.csdnimg.cn/blog_migrate/b8da538bd6ee6370cc30da9140d1123e.png)
当前面的表达式值为null时,就会使用后面的默认值。
注意: ?: 之间没有空格。
循环
循环也是非常频繁使用的需求,我们使用 th:each 指令来完成:
假如有用户的集合:users在Context中。
![](https://i-blog.csdnimg.cn/blog_migrate/3795da570d4b95fe148c68d2dbae11bd.png)
${users} 是要遍历的集合,可以是以下类型:
Iterable,实现了Iterable接口的类
Enumeration,枚举
Interator,迭代器
Map,遍历得到的是Map.Entry
Array,数组及其它一切符合数组结果的对象
在迭代的同时,我们也可以获取迭代的状态对象:
![](https://i-blog.csdnimg.cn/blog_migrate/d9ba8d0a84606b2acaf92de74e3c427f.png)
stat对象包含以下属性:
index,从0开始的角标
count,元素的个数,从1开始
size,总元素个数
current,当前遍历到的元素
even/odd,返回是否为奇偶,boolean值
first/last,返回是否为第一或最后,boolean值
逻辑判断
有了 if和else ,我们能实现一切功能^_^。
Thymeleaf中使用 th:if 或者 th:unless ,两者的意思恰好相反。
![](https://i-blog.csdnimg.cn/blog_migrate/d7cf3cdc000a54acf90355dbdec0a3f8.png)
如果表达式的值为true,则标签会渲染到页面,否则不进行渲染。
以下情况被认定为true:
表达式值为true
表达式值为非0数值
表达式值为非0字符
表达式值为字符串,但不是 "false" , "no" , "off"
表达式不是布尔、字符串、数字、字符中的任何一种
其它情况包括null都被认定为false
样式及脚本引入
<!--原生的-->
<link th:href="@{/css/index.css}">
<script type="text/javascript" src="/js/jquery-3.6.0.js"></script>
<!--推荐使用thymeleaf语法进行引入-->
<script type="text/javascript" th:src="@{/js/jquery-3.6.0.js}"></script>
分页设计
手写工具类
@Data
public class PageInfo<T> {
//当前页
private int pageNum;
//上一页
private int prePage;
//下一页
private int nextPage;
//总页数
private int pages;
//总条数
private int total;
//每页多少条
private int pageSize;
//起始索引
private int startIndex;
//查询对象
private T t;
//返回的集合
private List<T> list;
public PageInfo(int pageNum,int pageSize,int total) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.total = total;
this.pages = this.total%this.pageSize >0? this.total/this.pageSize+1:this.total/this.pageSize;
this.prePage = this.pageNum >=2? this.pageNum-1:1;
this.nextPage = this.pageNum == this.pages? this.pageNum:this.pageNum+1;
this.startIndex = (this.pageNum-1) * this.pageSize;
}
}