1.SpringBoot整合SpringMVC
之前的入门案例已经完成SpringMVC的自动配置,能够使用SpringMVC的功能,这里我们主要需要解决三个问题:
1. 修改端口
2. 静态资源
3. 拦截器配置
1.1 修改端口
编辑SpringBoot的全局配置文件application.properties:
server.port=80
或者application.yml:
server:
port: 80
1.2 访问静态资源
ResourceProperties的类,里面就定义了静态资源的默认查找路径:
默认的静态资源路径为:
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public
只要静态资源放在这些目录中任何一个,SpringMVC都会帮我们处理。我们习惯会把静态资源放在 classpath:/static/ 目录下。
1.3 设置拦截器
拦截器也是我们经常需要使用的,在SpringBoot中该如何配置呢?
1.3.1 定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
logger.debug("处理器执行前执行!");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
logger.debug("处理器执行后执行!");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
logger.debug("跳转后执行!");
}
}
这里测试用了日志打印,因为我们记录的log级别是debug,默认是显示info以上,我们需要进行配置:
#SpringBoot通过 logging.level.*=debug 来配置日志级别,*填写包名(yaml的写法也是类似的) #设置com.lijinghua包的日志级别为debug logging.level.com.lijinghua=debug
1.3.2 添加拦截器
通过实现 WebMvcConfigurer接口并添加 @Configuration 注解,来实现自定义部分SpringMVC配置:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* 通过@Bean注解,将我们定义的拦截器注册到Spring容器
*
* @return
*/
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
/**
* 重写接口中的addInterceptors方法,添加自定义拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 通过registry来注册拦截器,通过addPathPatterns来添加拦截路径
registry.addInterceptor(this.loginInterceptor()).addPathPatterns("/**");
}
}
注意:ant path路径匹配通配符
- ‘?’ 匹配任何单字符
- ‘*’ 匹配0或者任意数量的字符
- ‘/**’ 匹配0或者更多的目录
2. SpringBoot整合JDBC
2.1 数据库建表
CREATE TABLE `tb_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_name` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
`age` int DEFAULT NULL,
`sex` int DEFAULT NULL,
`birthday` date DEFAULT NULL,
`created` date DEFAULT NULL,
`updated` date DEFAULT NULL,
`note` varchar(2000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
这里使用MySQL数据库。
2.2 引入依赖
这里有两种方式,我们可以在新建项目或者模块的时候选择JDBC的相关依赖(这里选择使用MySQL数据库):
项目建好之后,发现自动帮我们导入了如下的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
当然,我们也可以手动将这些依赖添加到POM.xml中。
2.3 配置数据连接以及连接池
其实,在刚才引入jdbc启动器的时候,SpringBoot已经自动帮我们引入了一个连接池:
HikariCP应该是目前速度最快的连接池了,我们看看它与c3p0的对比:
因此,我们只需要在全局配置文件中指定连接池参数即可:
spring:
datasource:
#配置数据库四大参数
url: jdbc:mysql://localhost:3306/springboot
username: root
password: lijinghua
driverClassName: com.mysql.jdbc.Driver #SpringBoot自动推断,可以省略
#配置hikari连接池参数,这些都是有默认值的可以不配置
hikari:
idle-timeout: 60000
maximum-pool-size: 30
minimum-idle: 10
当然,你换用别的连接池也可以的,这里用HikariCP的另一个原因也是为了图个方便。
2.4 实体类
public class User implements Serializable {
private Long id;
// 用户名
//自动转换下换线到驼峰命名user_name -> userName
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
//toString、getter和setter
}
2.5 dao层
@Repository
public class JdbcDao {
@Autowired
private JdbcTemplate jdbcTemplate;//支持自动转换下换线到驼峰命名user_name -> userName
//BeanPropertyRowMapper这个类可以把同名的字段赋值给属性
public List<User> findAll() {
return jdbcTemplate.query("select * from tb_user", new BeanPropertyRowMapper<>(User.class));
}
}
2.6 测试
@SpringBootTest
class Springboot02JdbcApplicationTests {
@Autowired
private JdbcDao jdbcDao;
@Test
void contextLoads() {
List<User> list = jdbcDao.findAll();
list.forEach(user -> System.out.println(user));
}
}
3. SpringBoot整合Mybatis
3.1 数据库建表
参见2.1小节
3.2 引入依赖
这里和2.2小节的操作是一样的,只不过引入的依赖不同。
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
3.3 配置数据库连接、连接池以及Mybatis
和2.3小节相比,基本上没什么变化,多了Mybatis的相关配置:
spring:
datasource:
#配置数据库四大参数
url: jdbc:mysql://localhost:3306/springboot
username: root
password: lijinghua
driverClassName: com.mysql.cj.jdbc.Driver #SpringBoot自动推断,可以省略
#配置hikari连接池参数,这些都是有默认值的可以不配置
hikari:
idle-timeout: 60000
maximum-pool-size: 30
minimum-idle: 10
mybatis:
# mybatis 别名扫描
type-aliases-package: com.lijinghua.pojo
# mapper.xml文件位置,如果没有映射文件,请注释掉
mapper-locations: classpath:mapper/*.xml
3.4 实体类
@Data
public class User implements Serializable {
private Long id;
// 用户名
//使用Mybatis不可以自动转换下换线到驼峰命名user_name -> userName
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
}
3.5 Dao层接口
public interface UserMapper {
public abstract List<User> findAll();
}
3.6 映射文件
根据前面的配置文件,把映射文件放到resources目录的mapper包中。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lijinghua.mapper.UserMapper">
<select id="findAll" resultType="user">
select * from tb_user
</select>
</mapper>
3.7 生成Mapper的接口代理对象
Mapper的加载接口代理对象方式有2种:使用@Mapper注解和设置MapperScan。
3.7.1 使用@Mapper注解[不推荐]
@Mapper
public interface UserMapper {
//......
}
由于这里没有配置mapper接口扫描包,因此我们需要以注解的方式,给每一个Mapper接口添加 @Mapper 注解,才能被识别。
3.7.2 设置MapperScan[推荐]
@MapperScan(“dao所在的包”),自动搜索包中的接口,产生dao的代理对象。
@SpringBootApplication
@MapperScan("com.lijinghua.mapper")
public class Springboot03MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot03MybatisApplication.class, args);
}
}
3.8 测试
@SpringBootTest
class Springboot03MybatisApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<User> list = userMapper.findAll();
list.forEach(user -> System.out.println(user));
}
}
发现userName为null,说明使用Mybatis不可以自动转换下换线到驼峰命名user_name -> userName
4. SpringBoot整合Tk-Mybatis
4.1 通用Mapper概念
使用Mybatis时,最大的问题是,要写大量的重复SQL语句在xml文件中,除了特殊的业务逻辑SQL语句之外,还有大量结构类似的增删改查SQL。而且,当数据库表结构改动时,对应的所有SQL以及实体类都需要更改。这大量增加了程序员的负担。避免重复书写CRUD映射的框架有两个:
- 通用Mybatis(Tk-Mybatis)
- Mybatis-Plus(通用功能更加强大,后面讲解)
4.2 数据库建表
参见2.1小节
4.3 引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 通用mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
引入了Tk-Mybatis依赖之后,因为间接依赖的关系,可以不用引入Mybatis的依赖了。
4.4 配置数据库连接和连接池以及Mybatis
参见3.3小节
4.5 实体类
Tk-Mybatis 实体类使用的注解是jpa注解。
点击了解什么是JPA?
点击了解怎么用Spring Data JPA?
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "tb_user")
public class User implements Serializable {
@Id//映射主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//设置主键自增策略
private Long id;
// 用户名
//自动转换下换线到驼峰命名user_name -> userName
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
}
映射规则:
-
默认表名=类名,字段名=属性名;
-
表名可以使用 @Table(name = “tableName”) 进行指定;
-
@Id 映射主键;
-
@GeneratedValue 主键的生成策略;
-
@Column(name = “fieldName”) 指定;
指定映射字段,而不完成驼峰转换。
-
使用 @Transient 注解表示跟字段不进行映射。
4.6 Dao层接口
import com.lijinghua.pojo.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User> {
//自定义的方法需要编写自定义映射文件
public List<User> findByUser(User user);
}
dao层接口通过继承tk.mybatis.mapper.common.Mapper接口,可以使用它内置的一些增删改查方法,也不用写映射文件,它内部已经帮我们配置好了。当然一些复杂的方法我们可以自行编写。
那么Tk-Myabtis内置了哪些方法呢?
一旦继承了Mapper,继承的Mapper就拥有了Mapper所有的通用方法,部分如下:
-
Select方法
-
List select(T record);
根据实体中的属性值进行查询,查询条件使用等号
-
T selectByPrimaryKey(Object key);
根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
-
List selectAll();
查询全部结果,select(null)方法能达到同样的效果
-
T selectOne(T record);
根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
-
int selectCount(T record);
根据实体中的属性查询总数,查询条件使用等号
-
-
Insert方法
- int insert(T record);
保存一个实体,null的属性也会保存,不会使用数据库默认值
-
int insertSelective(T record);
保存一个实体,null的属性不会保存,会使用数据库默认值
-
Update 方法
-
int updateByPrimaryKey(T record);
根据主键更新实体全部字段,null值会被更新
-
int updateByPrimaryKeySelective(T record);
根据主键更新属性不为null的值
-
-
Delete 方法
-
int delete(T record);
根据实体属性作为条件进行删除,查询条件使用等号
-
int deleteByPrimaryKey(Object key);
根据主键字段进行删除,方法参数必须包含完整的主键属性
-
-
Example方法
-
List selectByExample(Object example);
根据Example条件进行查询
重点:这个查询支持通过 Example 类指定查询列,通过 selectProperties 方法指定查询列
-
int selectCountByExample(Object example);
根据Example条件进行查询总数
-
int updateByExample(@Param(“record”) T record, @Param(“example”) Object example);
根据Example条件更新实体 record 包含的全部属性,null值会被更新
-
int updateByExampleSelective(@Param(“record”) T record, @Param(“example”) Object example);
根据Example条件更新实体 record 包含的不是null的属性值
-
int deleteByExample(Object example);
根据Example条件删除数据
-
注意:
如果我们使用@MapperScan注解,一定要使用tk.mybatis.spring.annotation.MapperScan的,不要用Mybatis的。
使用Tk-Mybatis内置方法的时候可以完成自动的驼峰转换,自定义的不行。解决方法:我们可以开启Mybatis的自动驼峰转换。
mybatis: # mybatis 开启自动驼峰转换 configuration: map-underscore-to-camel-case: true
4.7 自定义映射文件
因为Tk-Mybatis已经提供了基础的dao层方法以及相对应的映射文件,我们这里只需要针对我们额外扩展的方法进行自定义映射文件的编写。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lijinghua.mapper.UserMapper">
<select id="findByUser" resultType="user">
SELECT
* FROM
tb_user
<where>
<if test="name != null">
name like '%${name}%'
</if>
<if test="note != null">
and note like '%${note}%'
</if>
</where>
</select>
</mapper>
5. SpringBoot整合Thymeleaf
5.1 Thymeleaf概念
Thymeleaf 是一个跟 FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下特点:
-
动静结合
Thymeleaf 在有网络和无网络的环境下皆可运行,无网络显示静态内容,有网络用后台得到数据替换静态内容
-
与SpringBoot完美整合
SpringBoot默认整合Thymeleaf
JSP支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,其次,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。
5.2 Thymeleaf入门案例
以第四节中SpringBoot整合Tk-Mybatis的例子为基础,下面给出一个实际案例来说明SpringBoot如何整合Thymeleaf。
5.2.1 引入依赖
SpringBoot整合Thymeleaf需要引入Thymeleaf的依赖,共有两种引入方式,第一种是在创建项目的时候直接设置添加Thymeleaf模板引擎依赖。
创建项目完毕后,发现已经添加了Thymeleaf的依赖(如下):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
也可以手动添加该依赖到POM.xml文件中。
SpringBoot会自动为Thymeleaf注册一个视图解析器:
与解析JSP的InternalViewResolver类似,Thymeleaf也会根据前缀和后缀来确定模板文件的位置:
- 默认前缀: classpath:/templates/
- 默认后缀: .html
一般我们无需进行修改,默认即可。
5.2.3 编写Service层
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> findAll(){
return userMapper.selectAll();
}
}
5.2.4 编写Controller层
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/users", method = RequestMethod.GET)
public String findAll(Model model){
List<User> users = userService.findAll();
model.addAttribute("users",users);
return "users";//跳转到classpath:/templates/users.html
}
}
5.2.5 编写前端页面
5.2.5.1 命名空间约束
我们要使用Thymeleaf,需要在html文件中导入命名空间的约束,方便提示。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
.....
</html>
5.2.5.2 前端页面内容
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
<style type="text/css">
table {border-collapse: collapse; font-size: 14px; width: 80%; margin: auto}
table, th, td {border: 1px solid darkslategray;padding: 10px}
</style>
</head>
<body>
<div style="text-align: center">
<span style="color: darkslategray; font-size: 30px">欢迎光临!</span>
<hr/>
<table class="list">
<tr>
<th>id</th>
<th>姓名</th>
<th>用户名</th>
<th>年龄</th>
<th>性别</th>
<th>生日</th>
<th>备注</th>
<th>操作</th>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.id}">1</td>
<td th:text="${user.name}">张三</td>
<td th:text="${user.userName}">zhangsan</td>
<td th:text="${user.age}">20</td>
<td th:text="${user.sex} == 1 ? '男': '女'">男</td>
<td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd')}">1980-02-30</td>
<td th:text="${user.note}">1</td>
<td>
<a href="#">删除</a>
<a href="#">修改</a>
<a href="#">审核</a>
</td>
</tr>
</table>
</div>
</body>
</html>
我们看到这里使用了以下语法:
-
${} :这个类似与el表达式,但其实是ognl的语法,比el表达式更加强大
-
th- 指令: th- 是利用了Html5中的自定义属性来实现的。
如果不支持H5,可以用 data-th- 来代替
-
th:each :类似于 c:foreach 遍历集合,但是语法更加简洁
-
th:text :声明标签中的文本
例如 < td th-text=’${user.id}’>1< /td> ,
-
如果user.id有值,会覆盖默认的1;
-
如果没有值,则会显示td中默认的1。
这正是thymeleaf能够动静结合的原因,模板解析失败不影响页面的显示效果,因为会显示默认值
-
-
这里的Thymeleaf语法在5.3小节会再展开说。
5.2.6 测试
5.3 模板缓存
Thymeleaf会在第一次对模板解析之后进行缓存,极大的提高了并发处理能力。
但是这给我们开发带来了不便,修改页面后并不会立刻看到效果,我们开发阶段可以关掉缓存使用:
# 开发阶段关闭thymeleaf的模板缓存
spring.thymeleaf.cache=false
在Idea中,我们需要在修改页面后按快捷键: Ctrl + Shift + F9 对项目进行rebuild才可以。
5.4 Thymeleaf详解
5.4.1 表达式
- 变量表达式
- 选择或星号表达式
- URL表达式
5.4.1.1 变量表达式
变量表达式即OGNL表达式或Spring EL表达式(在Spring中用来获取model attribute的数据)。
例如,显示Session中User的name属性:
${session.user.name}
将以HTML标签的一个属性来表示:
<h5>表达式</h5>
<span>${text}</span>
<span th:text="${text}">你好 thymleaf</span>
5.4.1.2 选择(星号)表达式
选择表达式很像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(map)来执行,如: *{customer.name} 。
被指定的object由th:object属性定义,例如:
<tr th:each="user : ${users}" th:object="${user}">
<td th:text="${user.id}">1</td>
<td th:text="*{name}">张三</td>
<td th:text="*{userName}">zhangsan</td>
....
通过这种方式,可以简化变量表达式。
5.4.1.3 URL表达式
URL表达式指的是把一个有用的上下文或回话信息添加到URL,这个过程经常被叫做URL重写。例如: @{/order/list}
URL还可以设置参数:
- @{/order/details(id=${orderId}, name=*{name})}
- 相对路径:@{…/documents/report}
让我们来看个实例应用场景:
<form th:action="@{/createOrder}"></form>
<a th:href="@{/main}"></a>
现在我们可以对5.2小节的入门案例中的审核、修改、删除进行进一步编写:
-
URL表达式
<a th:href="@{/delete(id=${user.id}, userName=*{userName})}">删除</a>
-
文本替换
<!--以竖线“|”开始,以竖线“|”结束 --> <a th:href="|/update/${user.id}|">修改</a>
-
字符串拼接
<a th:href="'/approve/' + ${user.id}">审核</a>
5.4.2 表达式常见用法
5.4.2.1 字面(Literals)
- 文本文字(Text literals): ‘one text’, ‘Another one!’,…
- 数字文本(Number literals): 0, 34, 3.0, 12.3,…
- 布尔文本(Boolean literals): true, false
- 空(Null literal): null
- 文字标记(Literal tokens): one, sometext, main,…
5.4.2.2 文本操作(Text operations)
- 字符串连接(String concatenation): +
- 文本替换(Literal substitutions): |The name is ${name}|
5.4.2.3 算术运算(Arithmetic operations)
- 二元运算符(Binary operators): +, -, *, /, %
- 减号(单目运算符)Minus sign (unary operator): -
5.4.2.4 布尔操作(Boolean operations)
- 二元运算符(Binary operators): and, or
- 布尔否定(一元运算符)Boolean negation (unary operator): !, not
5.4.2.5 比较和等价(Comparisons and equality)
- 比较(Comparators): >, <, >=, <= (gt, lt, ge, le)
- 等值运算符(Equality operators): ==, != (eq, ne)
5.4.2.6 条件运算符(Conditional operators)
- If-then: (if) ? (then)
- If-then-else: (if) ? (then) : (else)
- Default: (value) ?: (defaultvalue)
注意:所有的表达式用法都得写到th标签里面才可以使用。
5.4.3 常用th标签
关键字 | 功能介绍 | 案例 |
---|---|---|
th:text | 文本替换 | < p th:text="${collect.description}">description< /p > |
th:id | 替换id | < input th:id="‘xxx’ + ${collect.id}"/ > |
th:utext | 支持html的文本替换 | < p th:utext="${htmlcontent}">conten< /p > |
th:object | 替换对象 | < div th:object="${session.user}"> |
th:value | 属性赋值 | < input th:value="${user.name}" /> |
th:with | 变量赋值运 算 | < div th:with=“isEven=${prodStat.count}%2==0”>< /div> |
th:style | 设置样式 | <th:style="‘display:’ + @{(${sitrue} ? ‘none’ : ‘inline-block’)} + ‘’"> |
th:onclick | 点击事件 | th:οnclick="‘getCollect()’" |
th:each | 属性赋值 | |
th:if | 判断条件 | < a th:if="${userId == collect.userId}" > |
th:unless | 和th:if判断相反 | < a th:href="@{/login}" th:unless=${session.user != null}">Login < /a> |
th:href | 链接地址 | < a th:href="@{/login}" th:unless=${session.user != null}">Login < /a> |
th:switch | 多路选择 配合th:case 使用 | < div th:switch="${user.role}"> |
th:case | th:switch的一个分支 | < p th:case="‘admin’">User is an administrator< /p> |
th:fragment | 布局标签,定义一个代码片段,方便其它地方引用 | < div th:fragment=“alert”> |
th:include | 布局标签,替换内容到引入的文件 | < head th:include=“layout :: htmlhead” th:with=“title=‘xx’”>< /head> |
th:replace | 布局标签,替换整个标签到引入的文件 | < div th:replace=“fragments/header :: title”>< /div> |
th:selected | selected选择框 选中 | <th:selected="( {configObj.dd})"> |
th:src | 图片类地址引入 | < img class=“img-responsive” alt=“App Logo” th:src="@{/img/logo.png}" /> |
th:inline | 定义js脚本可以使用变量 | < script type=“text/javascript” th:inline=“javascript” /> |
th:action | 表单提交的地址 | < form action=“subscribe.html” th:action="@{/subscribe}" /> |
th:remove | 删除某个属性 | < tr th:remove=“all”> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。 |
th:attr | 设置标签属性,多个属性可以用逗号分隔 | 比如 < th:attr=“src=@{/image/aa.jpg},title=#{logo}”>,此标签不太优雅,一般用的比较少。 |
这里这列举了一些常用的,更详细的参见Thymeleaf的在线官方文档。
5.4.4 基本用法
5.4.4.1 赋值、字符串拼接
<a th:href="|/update/${user.id}|">修改</a>
<a th:href="'/approve/' + ${user.id}">审核</a>
5.4.4.2 条件判断if和unless
Thymeleaf中使用th:if和th:unless属性进行条件判断,下面的例子中, < a> 标签只有在 th:if 中条件成立时才显示, th:unless和th:if恰好相反,只有表达式中的条件不成立,才会显示其内容:
<h5>if指令</h5>
<a th:if="${users.size() > 0}">查询结果存在</a><br>
<a th:if="${users.size() <= 0}">查询结果不存在</a><br>
<a th:unless="${session.user != null}" href="#">登录</a><br>
也可以使用 (if) ? (then) : (else) 这种语法来判断显示的内容。
5.4.4.3 for循环
<tr th:each="user, status : ${users}" th:object="${user}" th:class="${status.even} ? 'grey'">
<td th:text="${status.even}"></td>
<td th:text="${user.id}">1</td>
<td th:text="*{name}">张三</td>
<td th:text="*{userName}">zhangsan</td>
<td th:text="${user.age}">20</td>
<td th:text="${user.sex} == 1 ? '男' : '女'">男</td>
<td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd')}">1980-02-30</td>
<td th:text="${user.note}">1</td>
<td>
<a th:href="@{/delete(id=${user.id}, userName=*{userName})}">删除</a>
<a th:href="|/update/${user.id}|">修改</a>
<a th:href="'/approve/' + ${user.id}">审核</a>
</td>
</tr>
status称作状态变量(也可以不叫status,叫什么都行,可以自定义命名,只要写在那个位置上),属性有:
- index:当前迭代对象的index(从0开始计算)
- count: 当前迭代对象的index (从1开始计算)
- size:被迭代对象的大小
- current:当前迭代变量
- even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)
- first:布尔值,当前循环是否是第一个
- last:布尔值,当前循环是否是最后一个
5.4.4.4 内联文本
内联文本:[[…]]内联文本的表示方式,使用时,必须先用th:inline=”text/javascript/none”激活,默认是“text”,”none“是禁用内联。
th:inline可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text的代码少,不然不利于原型显示。
-
在thymeleaf指令中显示
<h6 th:text="${text}">静态内容</h6>
-
使用内联文本显示model attribute
<h5>内联文本</h5> <div> <h6 th:inline="text">[[${users}]]</h6> <h6 th:inline="none">[[${users}]]</h6> <h6>[[${users}]]</h6> </div>
效果如下:
-
内联js
<h5>内联js</h5> <script th:inline="javascript"> /*<![CDATA[*/ var text = '[[${users}]]'; alert(text); /*]]>*/ </script>
效果如下:
5.4.4.5 内置变量(工具类对象)、
- dates : java.util.Date的功能方法类。
- calendars : 类似 dates*,面向*java.util.Calendar
- numbers : 格式化数字的功能方法类
- strings : 字符串对象的功能类,contains、startWiths、prepending/appending等等。
- objects: 对objects的功能类操作。
- bools: 对布尔值求值的功能方法。
- arrays:对数组的功能类方法。
- lists: 对lists功能类方法
- sets
- maps
- ……
5.4.4.6 内置基本对象
- ctx
- vars
- locale
- request
- response
- session
- servletContext
- servletContext
5.4.5 使用Thymeleaf布局
使用thymeleaf布局非常的方便,在/resources/templates/目录下创建test.html,内容如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy(title)">
© 2021 Thymeleaf布局测试<br>
<span th:text="${title}">title footer</span>
</footer>
</body>
</html>
有点像一个函数,传递了个名字进去(title)。
然后我们可以在任意地方引入:
<h5>thymeleaf布局</h5>
<div th:insert="test :: copy('页面1')"></div>
<div th:replace="test :: copy('页面2')"></div>
<div th:include="test :: copy('页面3')"></div>
页面展示测试效果:
前端生成的源代码效果也是不一样的:
-
insert:把footer整个内容添加到div(当前标签)里面;
-
replace:替换div(当前标签),只保留footer整个内容。
-
include:把footer里的全部内容,放到div(当前标签)里面。
6.SpringBoot整合Mabatis Plus
6.1 Mybatis Plus概念
Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,避免了我们重复CRUD语句。
6.2 入门案例
6.2.1 引入依赖
<!--Mybatis Plus的启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
6.2.2 配置数据库连接、连接池以及日志
# DataSource Config
spring:
datasource:
#配置数据库四大参数
url: jdbc:mysql://localhost:3306/springboot
username: root
password: lijinghua
driverClassName: com.mysql.cj.jdbc.Driver #SpringBoot自动推断,可以省略
#配置hikari连接池参数,这些都是有默认值的可以不配置
hikari:
idle-timeout: 60000
maximum-pool-size: 30
minimum-idle: 10
mybatis-plus:
# Mybatis Plus的别名扫描
type-aliases-package: com.lijinghua.pojo
# mapper.xml文件位置,如果没有映射文件,请注释掉
mapper-locations: classpath:mapper/*.xml
#开启自动驼峰转换
configuration:
map-underscore-to-camel-case: true
# Logger Config
logging:
level:
com.lijinghua: debug
6.2.3 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//实体类映射表名,不加上的话就是默认去找实体类名称的表,这里不写的话就会去找User这个表
public class User implements Serializable {
private Long id;
// 用户名
//自动转换下换线到驼峰命名user_name -> userName
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
}
和Tk-Mybatis一样,它内置的方法都是支持自动驼峰命名转换的。
6.2.4 dao层接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
}
需要继承com.baomidou.mybatisplus.core.mapper.BaseMapper接口,这样我们就可以使用Mybatis Plus中内置的方法实现增删改查了。
6.2.5 启动类
@SpringBootApplication
@MapperScan("com.lijinghua.mapper")
public class Springboot06MybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot06MybatisplusApplication.class, args);
}
}
也可以用@Mapper
6.2.6 测试
@SpringBootTest
class Springboot06MybatisplusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<User> users = userMapper.selectList(null);
users.forEach(user -> System.out.println(user));
}
}
测试结果:
6.3 Mybatis Plus常用注解
MyBatis Plus提供了一些注解供我们在实体类和表信息出现不对应的时候使用。通过使用注解完成逻辑上匹配。
例如6.2.3小节编写实体类的时候,就用过 @TableName 注解。
注解名称 | 说明 |
---|---|
@TableName | 实体类的类名和数据库表名不一致 |
@TableId | 实体类的主键名称和表中主键名称不一致 |
@TableField | 实体类中的成员名称和表中字段名称不一致 |
如果表名、字段名都一致是不需要使用的。
6.3.1 @TableId设置主键策略
public enum IdType {
AUTO(0),
NONE(1),
INPUT(2),
ASSIGN_ID(3),
ASSIGN_UUID(4),
/** @deprecated */
@Deprecated
ID_WORKER(3),
/** @deprecated */
@Deprecated
ID_WORKER_STR(3),
/** @deprecated */
@Deprecated
UUID(4);
//.....
}
默认主键策略是 ASSIGN_ID (会根据雪花算法算出来);MySQL自增主键策略可设置为 AUTO。
6.3.2 @TableField设置映射规则
这个注解主要有两个作用:
-
设置实体类属性与数据库表中字段的映射规则
//例如,表中密码对应的字段是password,实体类的属性名叫pwd,可以通过这个注解让他俩建立映射关系 @TableField("password") private String pwd;
如果实体类属性与数据库表中字段名称一致,那么无需使用。
-
排除实体类中非表属性
如果我们的实体类中除了有与数据库表中字段对应的属性以外的别的属性,那我们需要排除这些非表的属性,否则运行会出错。//例如表中没有pwd这个字段 @TableField(exist = false) private String pwd;
6.4 Mybatis Plus内置的、常用的增删改查
下面给出常见的增删改查方法的使用,更详细的参考
6.4.1 增加insert
@Test
public void insert(){
User user = new User();
user.setName("test");
user.setAge(23);
int insert = userMapper.insert(user);
System.out.println(insert);
}
6.4.2 删除delete
@Test
public void delete(){
//主键删除
// int i = userMapper.deleteById(1422569027296890886l);
// System.out.println(i);
//批量删除,方式1
// QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();//条件容器
// userQueryWrapper.like("name","s");//模糊查询
// userMapper.delete(userQueryWrapper);
//批量删除,方式2
// QueryWrapper<User> query = Wrappers.query();//都是new出一个QueryWrapper对象
// query.like("name","s");//模糊查询
// userMapper.delete(query);
//批量删除,方式3
QueryWrapper<User> query = Wrappers.query();//都是new出一个QueryWrapper对象
query.lambda().like(User::getName,"s");//使用了lambda表达式,方法引用
userMapper.delete(query);
//批量删除,方式4,根据主键id
// List<Long> ids = new ArrayList<>();
// ids.add(1422569027296890890l);
// ids.add(1422569027296890891l);
// userMapper.deleteBatchIds(ids);
}
6.4.3 修改update
@Test
public void update() {
//基本修改
// User user1 = new User();
// user1.setId(1l);
// user1.setNote("testUpdate01");
// userMapper.updateById(user1);
// 批量修改,方式1
// UpdateWrapper<User> userUpdateWrapper = Wrappers.<User>update();//同理也可以直接new UpdateWrapper的对象
// userUpdateWrapper.lambda().set(User::getNote,"testUpdate02").like(User::getName,"te");//同理也可以不用lambda
// userMapper.update(null,userUpdateWrapper);//userUpdateWrapper已经set过了,这里的实体就传null就行
//批量修改,方式2
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.like("note","te");
User user2 = new User();
user2.setNote("testUpdate03");
userMapper.update(user2, Wrappers.<User>update().like("note","te"));
}
6.4.4 查询select
@Test
void select() {
// QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();//条件容器
// userQueryWrapper.like("name", "s");//模糊查询
//(1)查询全部
// List<User> users = userMapper.selectList(null);
//(2)条件查询
// List<User> users = userMapper.selectList(userQueryWrapper);
// users.forEach(user -> System.out.println(user));
//(3)查询单个
// User user = userMapper.selectOne(Wrappers.<User>query().eq("id",1));
// System.out.println(user);
//(4)投影查询
userMapper.selectList(Wrappers.<User>query().select("id","name")).forEach(user -> System.out.println(user));
}
6.5 Mybatis Plus分页
6.5.1 内置分页
6.5.1.1 编写分页配置类
@Configuration
public class MybatisPlusConfig {
/**
* mp分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
// 开启 count 的 join 优化,只针对 left join !!!
return new PaginationInterceptor().setCountSqlParser(new JsqlParserCountOptimize(true));
}
}
下面说说要优化的理由。
在一对一join操作时,也存在优化可能,例如:
select u.id,ua.account from user u left join user_account ua on u.id=ua.uid
#本来生成的count语句像这样
select count(1) from (select u.id,ua.account from user u left join user_account ua on u.id=ua.uid)
这时候分页查count时,其实可以去掉left join直查user,因为user与user_account是1对1关系,而且去掉后查询效率更高。
#查count:
select count(1) from user u
#查记录:
select u.id,ua.account from user u left join user_account ua on u.id=ua.uid limit 0,50
分页查询都是先查询count,然后再分页查询记录。
6.5.1.2 测试
@Test
public void selectByPage(){
System.out.println("------ baseMapper 自带分页 ------");
Page<User> page = new Page<>(1, 5);//分页参数
IPage<User> pageResult = userMapper.selectPage(page, new QueryWrapper<User>());//后面可以加条件
System.out.println("总条数 ------> " + pageResult.getTotal());
System.out.println("当前页数 ------> " + pageResult.getCurrent());
System.out.println("当前每页显示数 ------> " + pageResult.getSize());
pageResult.getRecords().forEach(System.out :: println);
}
6.5.2 自定义XML分页
这种方式和原来自己基于Mybatis手动写dao层方法一样,在这里实现分页功能。
只不过这里可以不去写limit,因为Mybatis Plus可以基于拦截器帮我们去做。拦截器配置参考6.5.1.1小节
6.5.2.1 全局配置文件添加Mybatis Plus的配置
mybatis-plus:
# Mybatis Plus的别名扫描
type-aliases-package: com.lijinghua.pojo
# mapper.xml文件位置,如果没有映射文件,请注释掉
mapper-locations: classpath:mapper/*.xml
#开启自动驼峰转换
configuration:
map-underscore-to-camel-case: true
6.5.2.2 编写dao层接口方法
在UserMapper接口中添加如下方法:
/**
* 如果映射的接口方法有2个参数需要@Param定义参数名,定义参数名后,映射文件中使用p.属性 c.属性,具体访问
* *
*
* @param page
* @param conditioin
* @return
*/
public IPage<User> selectUserByPage(@Param("p") IPage<User> page, @Param("c") User conditioin);
6.5.2.3 编写自定义映射文件
编写自定义映射文件放到指定扫描的位置,这里是/mapper目录下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lijinghua.mapper.UserMapper">
<sql id="selectSql">
SELECT * FROM tb_user
</sql>
<select id="selectUserByPage" resultType="user">
<include refid="selectSql"></include>
<where>
<if test="c.age !=null">
age = #{c.age}
</if>
<if test="c.name !=null">
and name like '%${c.name}%'
</if>
</where>
</select>
</mapper>
6.5.2.4 测试
@Test
public void selectByPage2(){
System.out.println("------ baseMapper 自定义xml分页 ------");
//分页参数
Page<User> page = new Page<>(1, 5);
//条件参数
User user = new User();
user.setName("tes");
IPage<User> pageResult = userMapper.selectUserByPage(page, user);
System.out.println("总条数 ------> " + pageResult.getTotal());
System.out.println("当前页数 ------> " + pageResult.getCurrent());
System.out.println("当前每页显示数 ------> " + pageResult.getSize());
pageResult.getRecords().forEach(System.out :: println);
}
6.5.3 pageHelper分页
6.5.3.1 引入pageHelper依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>
6.5.3.2 编写分页配置类
@Configuration
public class MybatisPlusConfig {
/**
* mp内置分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
// 开启 count 的 join 优化,只针对 left join !!!
return new PaginationInterceptor().setCountSqlParser(new JsqlParserCountOptimize(true));
}
/**
* 两个分页插件都配置,不会冲突
* pagehelper的分页插件
*/
@Bean
public PageInterceptor pageInterceptor() {
return new PageInterceptor();
}
}
这里两种分页方式的配置不会冲突,这里可以只是用pageHelper的分页。
6.5.3.3 测试
//Mybatis Plus 分页方式3:pageHelper分页
@Test
public void selectByPage3(){
System.out.println("------ baseMapper pageHelper分页 ------");
//条件参数
User user = new User();
user.setName("tes");
//设置分页,调用分页插件,不调用就查询所有,紧挨查询语句
PageHelper.startPage(1, 5);
List<User> pageResult = userMapper.selectList(Wrappers.<User>query().lambda().like(User::getName,"tes"));
PageInfo<User> userPageInfo = new PageInfo<>(pageResult);
System.out.println("总行数=" + userPageInfo.getTotal());
System.out.println("当前页=" + userPageInfo.getPageNum());
System.out.println("每页行数=" + userPageInfo.getPageSize());
System.out.println("总页数=" + userPageInfo.getPages());
System.out.println("起始行数=" + userPageInfo.getStartRow());
System.out.println("是第一页=" + userPageInfo.isIsFirstPage());
System.out.println("是最后页=" + userPageInfo.isIsLastPage());
System.out.println("还有下一页=" + userPageInfo.isHasNextPage());
System.out.println("还有上一页=" + userPageInfo.isHasPreviousPage());
System.out.println("页码列表" + Arrays.toString(userPageInfo.getNavigatepageNums()));
}