SpringBoot基础

本章内容

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

在IDER中构建SpringBoot项目

创建项目

选择依赖

创建完成结构

运行项目

点击选择启动项目工程

观察启动日志

前后台交互

创建控制层

重启项目并访问

http://localhost:8080/example/show

输出:

至此 前后台交互完成!!

目录结构

工程结构

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**目

录下添加指定命名的文件即可

重启运行

查看SpringBoot默认提供的Bean

手动注入bean

运行查看是否注入

控制层可以注解读取

初窥配置文件

从启动日志中可以发现,SpringBoot默认的端口是 8080 ,那么如果端口被占用了怎么办呢?不要慌,问题

不大,配置文件分分钟解决你的困扰…

关于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依赖:

但是这种允许默认不填写版本号的依赖也必须是boot中默认给定的,如果没有默认给定,还是需要手动填写

的。

功能 2:默认环境配置(绝对兼容)

默认使用Java8,可添加以下配置修改版本

默认使用UTF-8编码,可添加以下配置修改编码

等...

功能 3:资源过滤

识别默认配置文件

过滤打包内容(打包的时候把 src/main/resources 下所有文件都打包到包中)

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注解来进行批量注入。

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 就可以了。

日志格式

输出如下元素:

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:最低等级的,用于打开所有日志记录。

日志输出

文件保存

默认情况下,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:限制日志保留天数

单元测试

在实际开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确。

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

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属性可以指定其它种类的连接池**

案例测试

这里使用的是单元测试进行的练习

创建两张表

-- ----------------------------
-- 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的视图器配置完成

而且,还配置了模板文件(html)的位置,与jsp类似的前缀+ 视图名 + 后缀风格:

默认前缀: classpath:/templates/

默认后缀: .html

所以如果我们返回视图: users ,会指向到 classpath:/templates/users.html

Thymeleaf默认会开启页面缓存,提高页面并发能力。但会导致我们修改页面不会立即被展现,因此我们关

闭缓存:

另外,修改完毕页面,需要使用快捷键: Ctrl + Shift + F9 来刷新工程。

快速开始

我们准备一个controller,控制视图跳转:

新建一个html模板:

注意,把html 的名称空间,改成: xmlns:th="http://www.thymeleaf.org" 会有语法提示

启动项目,访问页面:

语法

Thymeleaf的主要作用是把model中的数据渲染到html中,因此其语法主要是如何解析model中的数据。从

以下方面来学习:

变量

方法

条件判断

循环

运算

逻辑运算

布尔运算

比较运算

条件运算

其它

变量

我们先新建一个实体类:User

然后在模型中添加数据

语法说明:

Thymeleaf通过 ${} 来获取model中的变量,注意这不是el表达式,而是ognl表达式,但是语法非常像。

示例:

我们在页面获取user数据:

效果:

感觉跟el表达式几乎是一样的。不过区别在于,我们的表达式写在一个名为: th:text 的标签属性中,这个

叫做 指令

动静结合

指令:

Thymeleaf崇尚 自然模板 ,意思就是模板是纯正的html代码,脱离模板引擎,在纯静态环境也可以直接运

行。现在如果我们直接在html中编写 ${} 这样的表达式,显然在静态环境下就会出错,这不符合Thymeleaf

的理念。

Thymeleaf中所有的表达式都需要写在 指令 中,指令是HTML5中的自定义属性,在Thymeleaf中所有指令都

是以 th: 开头。因为表达式 ${user.name} 是写在自定义属性中,因此在静态环境下,表达式的内容会被当

做是普通字符串,浏览器会自动忽略这些指令,这样就不会报错了!

现在,我们不经过SpringMVC,而是直接用浏览器打开页面看看:

静态页面中, 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']}

自定义变量

场景

看下面的案例:

我们获取用户的所有信息,分别展示。

当数据量比较多的时候,频繁的写 user. 就会非常麻烦。

因此,Thymeleaf提供了自定义变量来解决:

示例:

首先在 h2 上 用 th:object="${user}" 获取user的值,并且保存

然后,在 h2 内部的任意元素上,可以通过 *{属性名} 的方式,来获取user中的属性,这样就省去了大量的

user. 前缀了

方法

Thymeleaf提供的全局对象:

举例

我们在环境变量中添加日期类型对象

在页面中处理

字面值

有的时候,我们需要在指令中填写基本类型如:字符串、数值、布尔等,并不希望被Thymeleaf解析为变

量,这个时候称为字面值。

字符串字面值

使用一对 ' 引用的内容就是字符串字面值了:

th:text 中的thymeleaf并不会被认为是变量,而是一个字符串

数字字面值

数字不需要任何特殊语法, 写的什么就是什么,而且可以直接进行算术运算

布尔字面值

布尔类型的字面值是true或false

这里引用了一个 th:if 指令,跟vue中的 v-if 类似

拼接

我们经常会用到普通字符串与表达式拼接的情况:

字符串字面值需要用 '' ,拼接起来非常麻烦,Thymeleaf对此进行了简化,使用一对 | 即可:

与上面是完全等效的,这样就省去了字符串字面值的书写。

运算

算术运算

支持的算术运算符: + - * / %

比较运算

支持的比较运算: > , < , >= and <= ,但是 > , < 不能直接使用,因为xml会解析为标签,要使用别名。

注意 == and != 不仅可以比较数值,类似于equals的功能。

可以使用的别名: gt (>), lt (<), ge (>=), le (<=), not (!). Also eq (==), neq/ne (!=).

条件运算

三元运算

三元运算符的三个部分:conditon ? then : else

condition:条件

then:条件成立的结果

else:不成立的结果

其中的每一个部分都可以是Thymeleaf中的任意表达式。

默认值

有的时候,我们取一个值可能为空,这个时候需要做非空判断,可以使用 表达式 ?: 默认值 简

写:

当前面的表达式值为null时,就会使用后面的默认值。

注意: ?: 之间没有空格。

循环

循环也是非常频繁使用的需求,我们使用 th:each 指令来完成:

假如有用户的集合:users在Context中。

${users} 是要遍历的集合,可以是以下类型:

Iterable,实现了Iterable接口的类

Enumeration,枚举

Interator,迭代器

Map,遍历得到的是Map.Entry

Array,数组及其它一切符合数组结果的对象

在迭代的同时,我们也可以获取迭代的状态对象:

stat对象包含以下属性:

index,从0开始的角标

count,元素的个数,从1开始

size,总元素个数

current,当前遍历到的元素

even/odd,返回是否为奇偶,boolean值

first/last,返回是否为第一或最后,boolean值

逻辑判断

有了 if和else ,我们能实现一切功能^_^。

Thymeleaf中使用 th:if 或者 th:unless ,两者的意思恰好相反。

如果表达式的值为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;
    }
}

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值