1.介绍
SpringBoot是为了简化Spring应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性 让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的 搭建出一个 WEB 工程
未接触SpringBoot之前,搭建一个普通的WEB工程往往需要花费30分钟左右,如果遇到点奇葩的问题耽搁的 时间会更长一点,但自从用了SpringBoot后,真正体会到什么叫分分钟搭建一个WEB,让我拥有更多的时间 跟我的小伙伴们唠嗑了。使用SpringBoot后发现一切是如此的简单(还记得被JAR包,xml支配的恐惧吗,如 今都可以说 good bye) 在传统的一些框架中(SSH、SSM、Servlet/JSP) 虽然功能很强大但是配置起来太麻烦了,相比python或者 php ,Java就略显臃肿,主要问题集中在两点:
1. 复杂的配置文件: 项目之间各种配置文件铺天盖地,在开发的过程中占用了大量的时间,并且需要做这些配置文件进行维护, 整个项目感觉特别复杂,并且Java代码和配置文件之间的切换开发,给程序人员带来很大的负担。
1. 各种依赖比较混乱: 主要问题就是版本冲突,不同技术的版本需要我们知道哪些版本存在冲突问题,一旦使用错误就需要重新再 去下载库进行依赖。
而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
2. 构建SpringBoot项目
运行项目
前后台交互
自定义Banner
SpringBoot启动的时候我们可以看到如下内容,这一块其实是可以自定义的哦,而且在 2.X 版本中,它支持 的格式从文本扩展到**banner.txt、banner.jpg、banner.gif、banner.jpeg等等,只需要在resouces**目 录下添加指定命名的文件即可
查看SpringBoot默认提供的Bean
手动注入bean
//注入一个bean
@Bean
public Student student(){ return new Student("张三"); }
关于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依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
功能 2:默认环境配置(绝对兼容) 默认使用Java8,可添加以下配置修改版本 默认使用UTF-8编码,可添加以下配置修改编码 等...
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
</properties>
功能 3:资源过滤
识别默认配置文件
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
过滤打包内容(打包的时候把 src/main/resources 下所有文件都打包到包中)
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
3. YAML文件
1、什么是 YAML ?
- YAML(YAML Ain't Markup Language),一种数据序列化格式。
- 这种格式的配置文件近些年逐渐进入市场,并开始占据主导地位。
2、优点
- 较 xml 类型和 properties 类型相比,更容易阅读,结构也更加的清晰。
- 容易与脚本语言交互。
- 以数据为核心,重数据轻格式,轻格式并不是说可以不遵守格式,只是格式更加随性一些。
3、扩展名
- 扩展名有两种,无论是那个扩展名,都是 YAML 格式的配置文件,拥有 YAML 的属性。
- .yml ,这种扩展名更加主流一些
- yam.l
语法规则
1. 大小写敏感
2. 使用缩进表示层级关系
3. 缩进时不允许使用Tab键,只允许使用空格。
4. 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
5. 可以使用 "-小写字母" 或 "_小写字母"来 代替 "大写字母",如 userName 与 user-name ,user_name 含 义是一样的
6. key: value 格式书写 key 后面跟着冒号,再后面跟着一个空格,然后是值 ${basedir}/src/main/resources true **/application*.yml **/application*.yaml **/application*.properties 1 2 3 4 5 6 7 8 9 *:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA 1 2 3 4 5 6 7 8
7. 注释使用#.
数据类型
YAML 支持的数据结构有三种
1. 普通的值(数字,字符串,布尔) 2. 对象、Map (属性和值) (键值对) 3. 数组 (List、Set)
4. SpringBoot日志配置
Spring Boot内部采用的是Commons Logging进行日志记录,但在底层为**Java Util Logging+Log4J2、 Logback**等日志框架提供了默认配置 。 Java 虽然有很多可用的日志框架,但请不要担心,一般来说,使用 SpringBoot 默认的 Logback 就可以了。
日志级别拓展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默认为我们输出的日志级别为INFO、WARN、ERROR,如需要输出更多日志的时候,可以通过 以下方式开启
命令模式配置: java -jar app.jar --debug=true , 这种命令会被SpringBoot解析,且优先级最高
资源文件配置:application.yml配置debug=true即可。该配置只对 嵌入式容器、Spring、Hibernate生效, 我们自己的项目想要输出DEBUG需要额外配置(配置规则:logging.level.=)
logging:
level:
org.springframework: info
com.xja: info #建议:info 或者 warn
文件保存
默认情况下,SpringBoot仅将日志输出到控制台,不会写入到日志文件中去。如果除了控制台输出之外还想 写日志文件,则需要在application.properties设置logging.file或logging.path属性。 logging: level: org.springframework: info com.xja: info
#建议:info 或者 warn 1 2 3 4 logging.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:限制日志保留天数
logging:
level:
org.springframework: info
com.xja: info #建议:info 或者 warn
file:
#path: D://日志
name: D://日志/boot2.txt
# 如果颜色消失,配置这个
spring:
output:
ansi:
enabled: always
关于五颜六色的插件:Grep Console
5. 单元测试
添加依赖
添加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>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
编写单元测试类
在项目中添加测试依赖启动器后,可以编写Spring Boot项目中相关方法对应的单元测试。 如果是使用Spring Initializr方式搭建的Spring Boot项目,会在src.test.java测试目录下自动创建与项目主程 序启动类对应的单元测试类。
package edu.xja;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class CargoApplicationTests {
@Test
void contextLoads() {
}
@Test
void c(){
System.out.println("sss");
}
}
6. JdbcTemplate
关于SpringData介绍
Spring Framework对数据库的操作在JDBC上面做了深层次的封装,通过依赖注入功能,可以将 DataSource 注册到JdbcTemplate之中,使我们可以轻易的完成对象关系映射,并有助于规避常见的错误,在 SpringBoot中我们可以很轻松的使用它。
特点:
速度快,对比其它的ORM框架而言,JDBC的方式无异于是最快的
配置简单,Spring自家出品,几乎没有额外配置
学习成本低,毕竟JDBC是基础知识,JdbcTemplate更像是一个DBUtils
6.1 整合JdbcTemplate
在 pom.xml 中添加对 JdbcTemplate 的依赖
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
在 application.properties 中添加如下配置。值得注意的是,SpringBoot默认会自动配置 DataSource , 它将优先采用 HikariCP 连接池,如果没有该依赖的情况则选取 tomcat-jdbc ,如果前两者都不可用最后选 取 Commons DBCP2 。通过**spring.datasource.type属性可以指定其它种类的连接池**
# 数据源
spring:
datasource:
url: jdbc:mysql:///boot?useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password:
6.2 案例测试
实体类
package edu.xja.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Category {
private Integer id;
private String name;
}
dao
package edu.xja.dao;
import edu.xja.pojo.Category;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class CategoryDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* Category表添加
* @param category
* @return
*/
public int add02(Category category) {
String sql="insert into category value(?,?)";
int i=jdbcTemplate.update(sql,category.getId(),category.getName());
return i;
}
/**
* Category表删除
* @param i
* @return
*/
public int del02(int i) {
String sql="delete from category where id = ?";
int i1=jdbcTemplate.update(sql,i);
return i1;
}
/**
* Category表修改
* @param name
* @param id
* @return
*/
public int update02(String name, int id) {
String sql="update category set name=? where id=?";
int i=jdbcTemplate.update(sql,name,id);
return i;
}
/**
* Category表查询所有
* @return
*/
public List<Category> queryCategory() {
String sql="select * from category";
List<Category> list=jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Category.class));
return list;
}
}
service
package edu.xja.service;
import edu.xja.pojo.Cargo;
import edu.xja.pojo.Category;
import java.util.Date;
import java.util.List;
public interface CargoService {
/**
*货物表增加
* @param cargo
* @return
*
*/
int insert(Cargo cargo);
/**
*货物表删除
* @param i
* @return
*
*/
int del(int i);
/**
*货物表修改
* @param name
* @param id
* @return
*
*/
int update(String name, int id);
/**
*货物表查询
* @return
*
*/
List<Cargo> query();
/**
* 根据name模糊查询
* @param name
* @return
*/
List<Cargo> queryName(String name);
/**
* 根据时间模糊查询
* @param date1
* @return
*/
List<Cargo> queryTime(Date date1);
}
test
package edu.xja;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.sun.deploy.security.ruleset.RuleSetParser;
import edu.xja.pojo.Cargo;
import edu.xja.pojo.Category;
import edu.xja.service.CargoService;
import edu.xja.service.CategoryService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
@SpringBootTest
public class CargoTest {
@Autowired
private CategoryService categoryService;
@Test
/**
* Category表添加
* @param category
* @return
*/
public void add02() {
Category category = new Category(4, "格力");
int i = categoryService.add(category);
System.out.println(i);
}
@Test
/**
* Category表删除
* @param i
* @return
*/
public void del02() {
int i = categoryService.del02(4);
System.out.println(i);
}
@Test
/**
* Category表修改
* @param name
* @param id
* @return
*/
public void update02() {
int i = categoryService.update02("金属", 4);
System.out.println(i);
}
@Test
/**
* Category表查询所有
* @return
*/
public void queryCategory() {
List<Category> list = categoryService.queryCategory();
list.forEach(System.out::println);
}
}
7. 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。代码几乎没有任何区别,就是在模板语法上有区别。
7.3.1 变量
我们先新建一个实体类:User
public class User {
String name;
int age;
User friend;// 对象类型属性
}
然后在模型中添加数据
@GetMapping("show2")
public String show2(Model model){
User user = new User();
user.setAge(21);
user.setName("Jack Chen");
user.setFriend(new User("李小龙", 30));
model.addAttribute("user", user);
return "show2";
}
语法说明: Thymeleaf通过 ${} 来获取model中的变量,注意这不是el表达式,而是ognl表达式,但是语法非常像。
示例: 我们在页面获取user数据:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户页面</title>
</head>
<body>
<div th:text="${user.name}"></div>
</body>
</html>
7.3.2 自定义变量
我们获取用户的所有信息,分别展示。
当数据量比较多的时候,频繁的写 user. 就会非常麻烦。 因此,Thymeleaf提供了自定义变量来解决:
示例:
<h2 th:object="${user}">
<p>Name: <span th:text="*{name}">Jack</span>.</p>
<p>Age: <span th:text="*{age}">21</span>.</p>
<p>friend: <span th:text="*{friend.name}">Rose</span>.</p>
</h2>
7.3.3 方法
7.3.4 字面值
字符串字面值 使用一对 ' 引用的内容就是字符串字面值了:
数字字面值 数字不需要任何特殊语法, 写的什么就是什么,而且可以直接进行算术运算
布尔字面值 布尔类型的字面值是true或false:
7.3.5 拼接
字符串字面值需要用 '' ,拼接起来非常麻烦,Thymeleaf对此进行了简化,使用一对 | 即可:
<span th:text="|欢迎您:${user.name}|"></span>
7.3.6 运算
算术运算 支持的算术运算符: + - * / %
比较运算 支持的比较运算: > , < , >= and , < 不能直接使用,因为xml会解析为标签,要使用别名。 注意 == and != 不仅可以比较数值,类似于equals的功能。 可以使用的别名: gt (>), lt (=), le (<=), not (!). Also eq (==), neq/ne (!=).
条件运算 三元运算 三元运算符的三个部分:conditon ? then : else condition:条件 then:条件成立的结果 else:不成立的结果 其中的每一个部分都可以是Thymeleaf中的任意表达式。
默认值 有的时候,我们取一个值可能为空,这个时候需要做非空判断,可以使用 表达式 ?: 默认值 简 写:当前面的表达式值为null时,就会使用后面的默认值。 注意: ?: 之间没有空格。
7.3.7 循环
循环也是非常频繁使用的需求,我们使用 th:each 指令来完成: 假如有用户的集合:users在Context中
<tr th:each="user : ${users}">
<td th:text="${user.name}">Onions</td>
<td th:text="${user.age}">2.41</td>
</tr>
${users} 是要遍历的集合,
可以是以下类型: Iterable,实现了Iterable接口的类
Enumeration,枚举 Interator,
迭代器 Map,遍历得到的是Map.Entry Array
,数组及其它一切符合数组结果的对象,实现了Iterable接口的类 Enumeration,枚举 Interator,迭代器 Map,遍历得到的是Map.Entry Array,数组及其它一切符合数组结果的对象 在迭代的同时,我们也可以获取迭代的状态对象:
<tr th:each="user,stat : ${users}">
<td th:text="${user.name}">Onions</td>
<td th:text="${user.age}">2.41</td>
</tr>
stat对象包含以下属性:index,从0开始的角标 count,元素的个数,从1开始 size,总元素个数 current,当前遍历到的元素 even/odd,返回是否为奇偶,boolean值 first/last,返回是否为第一或最后,boolean值
7.3.8 逻辑判断
有了 if和else ,我们能实现一切功能^_^。 Thymeleaf中使用 th:if 或者 th:unless ,两者的意思恰好相反
如果表达式的值为true,则标签会渲染到页面,否则不进行渲染。 以下情况被认定为true:
表达式值为true 表达式值为非0数值 表达式值为非0字符 表达式值为字符串,但不是 "false" , "no" , "off" 表达式不是布尔、字符串、数字、字符中的任何一种
其它情况包括null都被认定为false
7.3.9 样式及脚本引入
<script type="text/javascript" th:src="@{/js/My97DatePicker/WdatePicker.js}"></script>
<script type="text/javascript" th:src="@{/js/My97DatePicker/WdatePicker.js}"></script>
8. 分页设计
package edu.xja.utils;
import lombok.Data;
import java.util.List;
@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 List<T> list;
public PageInfo(int pageNum, int total, int pageSize) {
this.pageNum = pageNum;
this.total = total;
this.pageSize = pageSize;
this.prePage = this.pageNum > 1 ? this.pageNum - 1 : 1;
/* 总条数 / 每页多少条 */
this.pages = (this.total / this.pageSize) + (this.total % this.pageSize != 0?1:0);
/* 下一页 */
this.nextPage = this.pageNum >= this.pages ? this.pages : this.pageNum + 1;
/* 起始索引 (当前页 -1 ) * pageSize */
this.startIndex = (this.pageNum - 1) * pageSize;
}
}
html
<script>
$(function () {
$("#pageSize").change(function () {
let v = $(this).val();
location = "/cargo/list?pageSize="+ v;
})
})
</script>
总条数:<span th:text="${pageInfo.total}"></span>,
<span th:text="${pageInfo.pageNum}"></span>/<span th:text="${pageInfo.pages}"></span>
<a href="/cargo/list?pageNum=1">首页</a>
<a th:href="|/cargo/list?pageNum=${pageInfo.prePage}&pageSize=${pageInfo.pageSize}|">上一页</a>
<a th:href="|/cargo/list?pageNum=${pageInfo.nextPage}&pageSize=${pageInfo.pageSize}|">下一页</a>
<a th:href="|/cargo/list?pageNum=${pageInfo.pages}&pageSize=${pageInfo.pageSize}|">尾页</a>
<select id="pageSize">
<option value="2" th:selected="${pageInfo.pageSize}==2">2</option>
<option value="5" th:selected="${pageInfo.pageSize}==5">5</option>
<option value="10" th:selected="${pageInfo.pageSize}==10">10</option>
<option value="20" th:selected="${pageInfo.pageSize}==20">20</option>
</select>