目录
4.1.1 @SpringBootConfiguration
4.1.3 @EnableAutoConfiguration
8.1.2 编写一个controller,返回一些用户数据,放入模型中,等会在页面渲染
一、SpringBoot概念
在这一部分,我们主要了解以下3个问题:
· 什么是SpringBoot
· 为什么要学习SpringBoot
· SpringBoot的特点
1.1 什么是SpringBoot
springboot是spring快速开发脚手架,通过约定大于配置的方式,快速构建和启动spring项目
比如在springmvc的时候,xml文件中需要配置DispatcherServlet,视图解析器,静态资源访问等等,springboot都会约定好,就不用再xml中配置了。
1.2 为什么要学习SpringBoot
spring的缺点:
1,复杂的配置
项目各种配置是开发时的损耗, 写配置挤占了写应用程序逻辑的时间。
2,混乱的依赖管理。
项目的依赖管理非常的繁琐。决定项目里要用哪些库就已经够让人头痛的了,还要知道这些库的哪个版本 和其他库不会有冲突,这是一个棘手的问题。并且,一旦选错了依赖的版本,随之而来的就是各种的不兼容 的bug。 spring boot 可以解决上面2个问题
1.3 SpringBoot的特点
Spring Boot 特点:
快速开发spring应用的框架
内嵌tomcat和jetty容器,不需要单独安装容器,jar包直接发布一个web应用
简化maven配置,parent这种方式,一站式引入需要的各种依赖
基于注解的零配置思想
和各种流行框架,spring web mvc,mybatis,spring cloud无缝整合
1.4 总结
spring boot 是spring快速开发脚手架,通过约定大于配置,优化了混乱的依赖管理,和复杂的配置,让我们用java -jar方式,运行启动java web项目
二、入门案例
需求:创建HelloController,在页面中打印hello spring boot...
2.1 创建工程
2.1.1 创建一个空工程
2.1.2 工程名为project_test:
2.1.3 设置jdk版本为1.8
2.1.4 新建一个module
2.1.5 填写项目坐标
2.2 添加依赖
SpringBoot提供了一个名为spring-boot-starter-parent的构件,里面已经对各种常用依赖(并非全部)的版本进行了管理,我们的项目需要以这个项目为父工程,这样我们就不用操心依赖的版本问题了,需要什么依赖,直接引入坐标即可!
2.2.1 添加父工程坐标
-
<parent>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-parent
</artifactId>
-
<version>2.0.0.RELEASE
</version>
-
</parent>
2.2.2 添加web启动器
为了让SpringBoot帮我们完成各种自动配置,我们必须引入SpringBoot提供的自动配置依赖,我们称为 启动器 。因为我们是web项目,这里我们引入web启动器:
-
<dependencies>
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-web
</artifactId>
-
</dependency>
-
</dependencies>
需要注意的是,我们并没有在这里指定版本信息。因为SpringBoot的父工程已经对版本进行了管理了。这个时候,我们会发现项目中多出了大量的依赖:
这些都是SpringBoot根据spring-boot-starter-web这个依赖自动引入的,而且所有的版本都已经管理好,不会出现冲突。
2.2.3 管理jdk版本
默认情况下,maven工程的jdk版本是1.5,而我们开发使用的是1.8,因此这里我们需要修改jdk版本,只需要简单的添加以下属性即可:
-
<properties>
-
<java.version>1.8
</java.version>
-
</properties>
2.2.4 完整的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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
<modelVersion>4.0.0
</modelVersion>
-
-
<groupId>com.lxy
</groupId>
-
<artifactId>spring-boot-demo-test
</artifactId>
-
<version>1.0-SNAPSHOT
</version>
-
-
<properties>
-
<java.version>1.8
</java.version>
-
</properties>
-
-
<parent>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-parent
</artifactId>
-
<version>2.0.0.RELEASE
</version>
-
</parent>
-
-
<dependencies>
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-web
</artifactId>
-
</dependency>
-
</dependencies>
-
</project>
2.3 启动类
Spring Boot项目通过main函数即可启动,我们需要创建一个启动类:
然后编写main函数:
-
package com.lxy;
-
-
import org.springframework.boot.SpringApplication;
-
import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-
@SpringBootApplication
-
public
class
Application {
-
public
static
void
main
(String[] args) {
-
SpringApplication.run(Application.class,args);
-
}
-
}
2.4 编写controller
接下来,我们就可以像以前那样开发SpringMVC的项目了!
我们编写一个controller:
-
package com.lxy.controller;
-
-
import org.springframework.web.bind.annotation.GetMapping;
-
import org.springframework.web.bind.annotation.RestController;
-
-
@RestController
-
public
class
HelloController {
-
-
@GetMapping("/hello")
-
public String
hello
(){
-
return
"hello, spring boot!";
-
}
-
}
2.5 启动测试
接下来,我们运行main函数,查看控制台:
并且可以看到监听的端口信息:
1)监听的端口是8080
2)SpringMVC的映射路径是:/
3) /hello 路径已经映射到了 HelloController 中的 hello() 方法
打开页面访问:http://localhost:8080/hello
测试成功了!
三、全注解配置和属性注入
在入门案例中,我们没有任何的配置,就可以实现一个SpringMVC的项目了,快速、高效!
但是有同学会有疑问,如果没有任何的xml,那么我们如果要配置一个Bean该怎么办?比如我们要配置一个数据库连接池,以前会这么玩:
-
<!-- 配置连接池 -->
-
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
-
init-method=
"init"
destroy-method=
"close">
-
<property name="url" value="${jdbc.url}" />
-
<property name="username" value="${jdbc.username}" />
-
<property name="password" value="${jdbc.password}" />
-
</bean>
现在该怎么做呢?
3.1 回顾历史
事实上,在Spring3.0开始,Spring官方就已经开始推荐使用java配置来代替传统的xml配置了,我们不妨来回顾一下Spring的历史:
1)Spring1.0时代在此时因为jdk1.5刚刚出来,注解开发并未盛行,因此一切Spring配置都是xml格式,想象一下所有的bean都用xml配置,细思极恐啊,心疼那个时候的程序员2秒.
2)Spring2.0时代Spring引入了注解开发,但是因为并不完善,因此并未完全替代xml,此时的程序员往往是把xml与注解进行结合,貌似我们之前都是这种方式。
3)Spring3.0及以后3.0以后Spring的注解已经非常完善了,因此Spring推荐大家使用完全的java配置来代替以前的xml,不过似乎在国内并未推广盛行。然后当SpringBoot来临,人们才慢慢认识到java配置的优雅。
3.2 spring全注解配置
spring全注解配置主要靠java类和一些注解,比较常用的注解有:
@Configuration :声明一个类作为配置类,代替xml文件
@Bean :声明在方法上,将方法的返回值加入Bean容器,代替 <bean> 标签
@value :属性注入
@PropertySource :指定外部属性文件,
我们接下来用java配置来尝试实现连接池配置:
首先引入Druid连接池依赖:
-
<dependency>
-
<groupId>com.alibaba
</groupId>
-
<artifactId>druid
</artifactId>
-
<version>1.1.10
</version>
-
</dependency>
创建一个jdbc.properties文件,编写jdbc属性(可以拷贝):
-
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
-
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
-
jdbc.usernam=root
-
jdbc.passwor=linda198721
然后编写代码: 创建一个JdbcConfig类
-
package com.lxy.config;
-
-
import com.alibaba.druid.pool.DruidDataSource;
-
import org.springframework.beans.factory.annotation.Value;
-
import org.springframework.context.annotation.Bean;
-
import org.springframework.context.annotation.Configuration;
-
import org.springframework.context.annotation.PropertySource;
-
-
import javax.sql.DataSource;
-
-
@Configuration
-
@PropertySource("classpath:jdbc.properties")
-
public
class
JdbcConfig {
-
@Value("${jdbc.url}")
-
String url;
-
@Value("${jdbc.driverClassName}")
-
String driverClassName;
-
@Value("${jdbc.username}")
-
String username;
-
@Value("${jdbc.password}")
-
String password;
-
-
@Bean
-
public DataSource
dataSource
() {
-
DruidDataSource
dataSource
=
new
DruidDataSource();
-
dataSource.setUrl(url);
-
dataSource.setDriverClassName(driverClassName);
-
dataSource.setUsername(username);
-
dataSource.setPassword(password);
-
return dataSource;
-
}
-
}
解读:
@Configuration :声明我们 JdbcConfig 是一个配置类
@PropertySource :指定属性文件的路径是: classpath:jdbc.properties
通过 @Value 为属性注入值
通过@Bean将 dataSource() 方法声明为一个注册Bean的方法,Spring会自动调用该方法,将方法的返回值加入Spring容器中。默认的对象名id=方法名,可以通过@Bean("自定义名字"),来指定新的对象名
然后我们就可以在任意位置通过 @Autowired 注入DataSource了!
我们在 HelloController 中测试:
-
package com.lxy.controller;
-
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.web.bind.annotation.GetMapping;
-
import org.springframework.web.bind.annotation.RestController;
-
-
import javax.sql.DataSource;
-
-
@RestController
-
public
class
HelloController {
-
-
@Autowired
-
private DataSource dataSource;
-
-
@GetMapping("/hello")
-
public String
hello
(){
-
return
"hello, spring boot!"+dataSource;
-
}
-
}
然后Debug运行并查看:
属性注入成功了!
3.3 SpringBoot的属性注入
在上面的案例中,我们实验了java配置方式。不过属性注入使用的是@Value注解。这种方式虽然可行,但是不够强大,因为它只能注入基本类型值。
在SpringBoot中,提供了一种新的属性注入方式,支持各种java基本数据类型及复杂类型的注入。
1)我们新建一个类,用来进行属性注入:
-
package com.lxy.config;
-
-
import org.springframework.boot.context.properties.ConfigurationProperties;
-
-
@ConfigurationProperties(prefix = "jdbc")
-
public
class
JdbcProperties {
-
private String url;
-
private String driverClassName;
-
private String username;
-
private String password;
-
-
public String
getUrl
() {
-
return url;
-
}
-
-
public
void
setUrl
(String url) {
-
this.url = url;
-
}
-
-
public String
getDriverClassName
() {
-
return driverClassName;
-
}
-
-
public
void
setDriverClassName
(String driverClassName) {
-
this.driverClassName = driverClassName;
-
}
-
-
public String
getUsername
() {
-
return username;
-
}
-
-
public
void
setUsername
(String username) {
-
this.username = username;
-
}
-
-
public String
getPassword
() {
-
return password;
-
}
-
-
public
void
setPassword
(String password) {
-
this.password = password;
-
}
-
}
(1)在类上通过@ConfigurationProperties注解声明当前类为属性读取类
(2)prefix="jdbc" 读取属性文件中,前缀为jdbc的值。
(3)在类上定义各个属性,名称必须与属性文件中 jdbc. 后面部分一致
(4)需要注意的是,这里我们并没有指定属性文件的地址,所以我们需要把jdbc.properties名称改为application.properties,这是SpringBoot默认读取的属性文件名:
2)在JdbcConfig中使用这个属性:
-
package com.lxy.config;
-
-
import com.alibaba.druid.pool.DruidDataSource;
-
import org.springframework.beans.factory.annotation.Value;
-
import org.springframework.boot.context.properties.EnableConfigurationProperties;
-
import org.springframework.context.annotation.Bean;
-
import org.springframework.context.annotation.Configuration;
-
import org.springframework.context.annotation.PropertySource;
-
-
import javax.sql.DataSource;
-
-
@Configuration
-
@EnableConfigurationProperties(JdbcProperties.class)
-
public
class
JdbcConfig {
-
-
@Bean
-
public DataSource
dataSource
(JdbcProperties jdbc) {
-
DruidDataSource
dataSource
=
new
DruidDataSource();
-
dataSource.setUrl(jdbc.getUrl());
-
dataSource.setDriverClassName(jdbc.getDriverClassName());
-
dataSource.setUsername(jdbc.getUsername());
-
dataSource.setPassword(jdbc.getPassword());
-
return dataSource;
-
}
-
}
通过 @EnableConfigurationProperties(JdbcProperties.class) 来声明要使用 JdbcProperties 这个类的对象 .
3)可以通过以下方式注入JdbcProperties:
@Autowired注入:
-
@Autowired
-
private JdbcProperties prop;
构造函数注入
-
private JdbcProperties prop;
-
public
JdbcConfig
(Jdbcproperties prop){
-
this.prop = prop;
-
}
声明有@Bean的方法参数注入
-
@Bean
-
public Datasource
dataSource
(JdbcProperties prop){
-
// ...
-
}
本例中,我们采用第三种方式。
4)测试结果
5)这种方法的优势
大家会觉得这种方式似乎更麻烦了,事实上这种方式有更强大的功能,也是SpringBoot推荐的注入方式。两者对比关系:
优势:
Relaxed binding:松散绑定
(1)不严格要求属性文件中的属性名与成员变量名一致。支持驼峰,中划线,下划线等等转换,甚至支持对象引导。比如:user.friend.name:代表的是user对象中的friend属性中的name属性,显然friend也是对象。@value注解就难以完成这样的注入方式。
(2)meta-data support:元数据支持,帮助IDE生成属性提示(写开源框架会用到)。
3.4 更优雅的注入
事实上,如果一段属性只有一个Bean需要使用,我们无需将其注入到一个类(JdbcProperties)中。而是直接在需要的地方声明即可:
(可删除之前创建的JdbcProperties类)在JdbcConfig中修改
-
package com.lxy.config;
-
-
import com.alibaba.druid.pool.DruidDataSource;
-
import org.springframework.boot.context.properties.ConfigurationProperties;
-
import org.springframework.context.annotation.Bean;
-
import org.springframework.context.annotation.Configuration;
-
-
import javax.sql.DataSource;
-
-
@Configuration
-
public
class
JdbcConfig {
-
-
@Bean
-
// 声明要注入的属性前缀,SpringBoot会自动把相关属性通过set方法注入到DataSource中
-
@ConfigurationProperties(prefix = "jdbc")
-
public DataSource
dataSource
() {
-
DruidDataSource
dataSource
=
new
DruidDataSource();
-
return dataSource;
-
}
-
}
我们直接把 @ConfigurationProperties(prefix = "jdbc") 声明在需要使用的 @Bean 的方法上,然后SpringBoot就会自动调用这个Bean(此处是DataSource)的set方法,然后完成注入。使用的前提是:该类必须有对应属性的set方法!
四、自动配置原理
通过刚才的案例看到,一个整合了SpringMVC的WEB工程开发,变的无比简单,那些繁杂的配置都消失不见了,这是如何做到的?
这些都是从springboot启动器开始的:
我们重点关注@SpringBootApplication注解
4.1 @SpringBootApplication
点击进入,查看源码:
这里重点的注解有3个:
-
@SpringBootConfiguration
-
@EnableAutoConfiguration
-
@ComponentScan
4.1.1 @SpringBootConfiguration
我们继续点击查看源码:
通过这段我们可以看出,在这个注解上面,又有一个 @Configuration 注解。这个注解的作用就是声明当前类是一个配置类,然后Spring会自动扫描到添加了 @Configuration 的类,并且读取其中的配置信息。
4.1.2 @ComponentScan
我们跟进源码:
并没有看到什么特殊的地方。我们查看注释:
大概的意思:
配置组件扫描的指令。提供了类似与 <context:component-scan> 标签的作用
通过basePackageClasses或者basePackages属性来指定要扫描的包。如果没有指定这些属性,那么将从声明这个注解的类所在的包开始,扫描包及子包
而我们的@SpringBootApplication注解声明的类就是main函数所在的启动类,因此扫描的包是该类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中。
4.1.3 @EnableAutoConfiguration
关于这个注解,官网上有一段说明:
简单翻译一下:
4.2 默认配置原理
@EnableAutoConfiguration会开启SpringBoot的自动配置,并且根据你引入的依赖来生效对应的默认配置,springboot如何做到的?
其实在我们的项目中,已经引入了一个依赖:spring-boot-autoconfigure,其中定义了大量自动配置类:
非常多,几乎涵盖了现在主流的开源框架,例如:
redis
jms
amqp
jdbc
jackson
mongodb
jpa
solr
elasticsearch等等。。。。。
我们来看一个我们熟悉的,例如SpringMVC,查看mvc 的自动配置类:
打开WebMvcAutoConfiguration:
我们看到这个类上的4个注解:
@Configuration :声明这个类是一个配置类
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
这里的条件是OnClass,也就是满足以下类存在:Servlet、DispatcherServlet、WebMvcConfigurer,其中Servlet只要引入了tomcat依赖自然会有,后两个需要引入SpringMVC才会有。这里就是判断你是否引入了相关依赖,引入依赖后该条件成立,当前类的配置才会生效!
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
这个条件与上面不同,OnMissingBean,是说环境中没有指定的Bean这个才生效。其实这就是自定义配置的入口,也就是说,如果我们自己配置了一个WebMVCConfigurationSupport的类,那么这个默认配置就会失效!
接着,我们查看该类中定义了什么:
视图解析器:
处理器适配器(HandlerAdapter):
还有很多,这里就不一一截图了。
4.3 总结
SpringBoot为我们提供了默认配置,而默认配置生效的条件一般有两个:
引入了相关依赖
没有自定义配置类
五、整合SpringMVC
刚才案例已经能实现mvc自动配置,这里我们主要解决以下3个问题
修改端口
静态资源
拦截器配置
5.1 修改端口
查看SpringBoot的全局属性可知,端口通过以下方式配置:
-
# 映射端口
-
server.port=80
重启服务后测试:
5.2 访问静态资源
ResourceProperties的类,里面就定义了静态资源的默认查找路径:
默认的静态资源路径为:
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public
只要静态资源放在这些目录中任何一个,SpringMVC都会帮我们处理。
我们习惯会把静态资源放在 classpath:/static/ 目录下。我们创建目录,并且添加一些静态资源:
重启项目后测试:
5.3 添加拦截器
拦截器也是我们经常需要使用的,在SpringBoot中该如何配置呢?
首先我们定义一个拦截器:
-
package com.lxy.interceptor;
-
-
import org.slf4j.Logger;
-
import org.slf4j.LoggerFactory;
-
import org.springframework.web.servlet.HandlerInterceptor;
-
import org.springframework.web.servlet.ModelAndView;
-
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
-
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(
"跳转后执行!");
-
}
-
-
}
通过实现 WebMvcConfigurer 并添加 @Configuration 注解来实现自定义部分SpringMvc配置:
-
package com.lxy.config;
-
-
import com.lxy.interceptor.LoginInterceptor;
-
import org.springframework.context.annotation.Bean;
-
import org.springframework.context.annotation.Configuration;
-
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
-
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
-
@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或者更多的目录 (代表根目录下的所有路径都被拦截)
结构如下:
接下来运行并查看日志:
你会发现日志中什么都没有,因为我们记录的log级别是debug,默认是显示info以上,我们需要进行配置。SpringBoot通过 logging.level.*=debug 来配置日志级别,*填写包名
-
# 设置com.lxy包的日志级别为debug
-
logging.level.com.lxy=debug
再次运行查看:
六、整合jdbc
导入资料中的t_user.sql文件 如果你也想要这个资源请在下发评论,我会发给你
引入依赖
(引入依赖之前需要先把之前添加的德鲁伊连接池的依赖删除)
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-jdbc
</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-test
</artifactId>
-
</dependency>
当然,不要忘了数据库驱动,SpringBoot并不知道我们用的什么数据库,这里我们选择MySQL:
-
<dependency>
-
<groupId>mysql
</groupId>
-
<artifactId>mysql-connector-java
</artifactId>
-
<version>8.0.23
</version>
-
</dependency>
配置连接池
其实,在刚才引入jdbc启动器的时候,SpringBoot已经自动帮我们引入了一个连接池:
HikariCP应该是目前速度最快的连接池了,我们看看它与c3p0的对比:
因此,我们只需要在application.properties中指定连接池参数即可:
-
# 连接四大参数
-
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
-
spring.datasource.username=root
-
spring.datasource.password=linda198721
-
-
# 可省略,SpringBoot自动推断
-
spring.datasource.driverClassName=com.mysql.jdbc.Driver
-
spring.datasource.hikari.idle-timeout=60000
-
spring.datasource.hikari.maximum-pool-size=30
-
spring.datasource.hikari.minimum-idle=10
实体类创建:
-
package com.lxy.domain;
-
-
import java.util.Date;
-
-
public
class
User {
-
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;
-
-
@Override
-
public String
toString
() {
-
return
"User{" +
-
"id=" + id +
-
", userName='" + userName +
'\'' +
-
", name='" + name +
'\'' +
-
", updated=" + updated +
-
", note='" + note +
'\'' +
-
'}';
-
}
-
-
public Long
getId
() {
-
return id;
-
}
-
-
public
void
setId
(Long id) {
-
this.id = id;
-
}
-
-
public String
getUserName
() {
-
return userName;
-
}
-
-
public
void
setUserName
(String userName) {
-
this.userName = userName;
-
}
-
-
public String
getPassword
() {
-
return password;
-
}
-
-
public
void
setPassword
(String password) {
-
this.password = password;
-
}
-
-
public String
getName
() {
-
return name;
-
}
-
-
public
void
setName
(String name) {
-
this.name = name;
-
}
-
-
public Integer
getAge
() {
-
return age;
-
}
-
-
public
void
setAge
(Integer age) {
-
this.age = age;
-
}
-
-
public Integer
getSex
() {
-
return sex;
-
}
-
-
public
void
setSex
(Integer sex) {
-
this.sex = sex;
-
}
-
-
public Date
getBirthday
() {
-
return birthday;
-
}
-
-
public
void
setBirthday
(Date birthday) {
-
this.birthday = birthday;
-
}
-
-
public Date
getCreated
() {
-
return created;
-
}
-
-
public
void
setCreated
(Date created) {
-
this.created = created;
-
}
-
-
public Date
getUpdated
() {
-
return updated;
-
}
-
-
public
void
setUpdated
(Date updated) {
-
this.updated = updated;
-
}
-
-
public String
getNote
() {
-
return note;
-
}
-
-
public
void
setNote
(String note) {
-
this.note = note;
-
}
-
}
dao类创建:
-
package com.lxy.dao;
-
-
import com.lxy.domain.User;
-
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
JdbcDao {
-
-
@Autowired
-
private JdbcTemplate jdbcTemplate;
-
-
public List<User>
findAll
(){
-
return jdbcTemplate.query(
"select * from tb_user",
new
BeanPropertyRowMapper<>(User.class));
-
}
-
}
测试:
-
package com.lxy.dao;
-
-
import com.lxy.domain.User;
-
import junit.framework.TestCase;
-
import org.junit.Test;
-
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.boot.test.context.SpringBootTest;
-
import org.springframework.test.context.junit4.SpringRunner;
-
-
-
@RunWith(SpringRunner.class)
-
@SpringBootTest
-
public
class
JdbcDaoTest
extends
TestCase {
-
-
@Autowired
-
private JdbcDao jdbcDao;
-
-
@Test
-
public
void
findAll
(){
-
jdbcDao.findAll().forEach(user -> {
-
System.out.println(user);
-
});
-
}
-
}
这里可以看到SpringBoot支持下划线转驼峰的形式,成功取到userName。
七、整合mybatis
7.1 mybatis整合
7.1.1 测试案例
SpringBoot官方并没有提供Mybatis的启动器,不过Mybatis官网自己实现了:
-
<!--mybatis -->
-
<dependency>
-
<groupId>org.mybatis.spring.boot
</groupId>
-
<artifactId>mybatis-spring-boot-starter
</artifactId>
-
<version>1.3.2
</version>
-
</dependency>
配置,基本没有需要配置的:
-
# mybatis 别名扫描
-
mybatis.type-aliases-package=com.lxy.domain
-
# mapper.xml文件位置,如果没有映射文件,请注释掉
-
mybatis.mapper-locations=classpath:mappers/*.xml
实体类,直接使用jdbc用到的实体类
-
package com.lxy.domain;
-
-
import java.util.Date;
-
-
public
class
User {
-
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;
-
-
@Override
-
public String
toString
() {
-
return
"User{" +
-
"id=" + id +
-
", userName='" + userName +
'\'' +
-
", name='" + name +
'\'' +
-
", updated=" + updated +
-
", note='" + note +
'\'' +
-
'}';
-
}
-
-
public Long
getId
() {
-
return id;
-
}
-
-
public
void
setId
(Long id) {
-
this.id = id;
-
}
-
-
public String
getUserName
() {
-
return userName;
-
}
-
-
public
void
setUserName
(String userName) {
-
this.userName = userName;
-
}
-
-
public String
getPassword
() {
-
return password;
-
}
-
-
public
void
setPassword
(String password) {
-
this.password = password;
-
}
-
-
public String
getName
() {
-
return name;
-
}
-
-
public
void
setName
(String name) {
-
this.name = name;
-
}
-
-
public Integer
getAge
() {
-
return age;
-
}
-
-
public
void
setAge
(Integer age) {
-
this.age = age;
-
}
-
-
public Integer
getSex
() {
-
return sex;
-
}
-
-
public
void
setSex
(Integer sex) {
-
this.sex = sex;
-
}
-
-
public Date
getBirthday
() {
-
return birthday;
-
}
-
-
public
void
setBirthday
(Date birthday) {
-
this.birthday = birthday;
-
}
-
-
public Date
getCreated
() {
-
return created;
-
}
-
-
public
void
setCreated
(Date created) {
-
this.created = created;
-
}
-
-
public Date
getUpdated
() {
-
return updated;
-
}
-
-
public
void
setUpdated
(Date updated) {
-
this.updated = updated;
-
}
-
-
public String
getNote
() {
-
return note;
-
}
-
-
public
void
setNote
(String note) {
-
this.note = note;
-
}
-
}
创建接口:
-
package com.lxy.dao;
-
-
import com.lxy.domain.User;
-
-
import java.util.List;
-
-
public
interface
UserDao {
-
public List<User>
findAll
();
-
}
创建UserDao.xml映射文件:
-
<?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.lxy.dao.UserDao">
-
<select id="findAll" resultType="user">
-
select * from tb_user
-
</select>
-
</mapper>
7.1.2 Mapper的加载接口代理对象方式有2种
第一种:使用@Mapper注解(不推荐)
需要注意,这里没有配置mapper接口扫描包,因此我们需要给每一个Mapper接口添加 @Mapper 注解,才能被识别。
-
@Mapper
-
public
interface
UserMapper {
-
}
第二种设置MapperScan,注解扫描的包(推荐)
@MapperScan("dao所在的包"),自动搜索包中的接口,产生dao的代理对象
-
package com.lxy;
-
-
import org.mybatis.spring.annotation.MapperScan;
-
import org.springframework.boot.SpringApplication;
-
import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-
@SpringBootApplication
-
@MapperScan("com.lxy.dao")
-
public
class
Application {
-
public
static
void
main
(String[] args) {
-
SpringApplication.run(Application.class,args);
-
}
-
}
测试
引入测试构建:(之前已经引入)
测试代码:
-
package com.lxy.dao;
-
-
import com.lxy.domain.User;
-
import junit.framework.TestCase;
-
import org.junit.Test;
-
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.boot.test.context.SpringBootTest;
-
import org.springframework.test.context.junit4.SpringRunner;
-
-
import java.util.List;
-
-
@RunWith(SpringRunner.class)
-
@SpringBootTest
-
public
class
UserDaoTest {
-
@Autowired
-
private UserDao userDao;
-
@Test
-
public
void
testFindAll
() {
-
List<User> list = userDao.findAll();
-
System.out.println(list);
-
}
-
}
测试结果:
这里可以看到mybatis是不支持下划线转驼峰的写法的
7.2 通用mapper
7.2.1 概念
使用Mybatis时,最大的问题是,要写大量的重复SQL语句在xml文件中,除了特殊的业务逻辑SQL语句之外,还有大量结构类似的增删改查SQL。而且,当数据库表结构改动时,对应的所有SQL以及实体类都需要更改。这大量增加了程序员的负担。避免重复书写CRUD映射的框架有两个
通用mybatis(tk mybatis)
mybatis plus,通能更加强大,后面实战项目中讲解
7.2.2 案例测试
(1)通用Mapper的作者也为自己的插件编写了启动器,我们直接引入即可:
因为tk mybatis中包含mybatis,所以我们可以把之前的mybatis删除
(2)实体类代码:
tk mybatis 实体类使用的注解是jpa注解
在这里主键必须采用自增策略,并且要标注好是id主键
-
package com.lxy.domain;
-
-
import javax.persistence.GeneratedValue;
-
import javax.persistence.GenerationType;
-
import javax.persistence.Id;
-
import javax.persistence.Table;
-
import java.util.Date;
-
-
@Table(name = "tb_user")
-
public
class
User {
-
-
@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;
-
-
@Override
-
public String
toString
() {
-
return
"User{" +
-
"id=" + id +
-
", userName='" + userName +
'\'' +
-
", name='" + name +
'\'' +
-
", updated=" + updated +
-
", note='" + note +
'\'' +
-
'}';
-
}
-
-
public Long
getId
() {
-
return id;
-
}
-
-
public
void
setId
(Long id) {
-
this.id = id;
-
}
-
-
public String
getUserName
() {
-
return userName;
-
}
-
-
public
void
setUserName
(String userName) {
-
this.userName = userName;
-
}
-
-
public String
getPassword
() {
-
return password;
-
}
-
-
public
void
setPassword
(String password) {
-
this.password = password;
-
}
-
-
public String
getName
() {
-
return name;
-
}
-
-
public
void
setName
(String name) {
-
this.name = name;
-
}
-
-
public Integer
getAge
() {
-
return age;
-
}
-
-
public
void
setAge
(Integer age) {
-
this.age = age;
-
}
-
-
public Integer
getSex
() {
-
return sex;
-
}
-
-
public
void
setSex
(Integer sex) {
-
this.sex = sex;
-
}
-
-
public Date
getBirthday
() {
-
return birthday;
-
}
-
-
public
void
setBirthday
(Date birthday) {
-
this.birthday = birthday;
-
}
-
-
public Date
getCreated
() {
-
return created;
-
}
-
-
public
void
setCreated
(Date created) {
-
this.created = created;
-
}
-
-
public Date
getUpdated
() {
-
return updated;
-
}
-
-
public
void
setUpdated
(Date updated) {
-
this.updated = updated;
-
}
-
-
public String
getNote
() {
-
return note;
-
}
-
-
public
void
setNote
(String note) {
-
this.note = note;
-
}
-
}
注意事项:
1. 默认表名=类名,字段名=属性名
2. 表名可以使用 @Table(name = "tableName") 进行指定
3. @Column(name = "fieldName") 指定
4. 使用 @Transient 注解表示跟字段不进行映射5. 主键和主键策略必须进行指定
不需要做任何配置就可以使用了。
(3)定义mapper接口,不需要编写映射文件xxx.xml
由于在Application.java中使用了接口扫描的注解,这里的Mapper已经可以自动注入了,不需要再引入@Repository;
接口需要继承Mapper,注意引入tk.mybatis.mapper;
一旦继承了Mapper,继承的Mapper就拥有了Mapper所有的通用方法;
-
package com.lxy.dao;
-
-
import com.lxy.domain.User;
-
import tk.mybatis.mapper.common.Mapper;
-
-
public
interface
UserMapper
extends
Mapper<User> {
-
-
}
(4) mapper接口扫描
注意要把Application.java中,MapperScan类改成tk-mybatis构件的类
(5)测试
-
package com.lxy.dao;
-
-
import com.lxy.domain.User;
-
import junit.framework.TestCase;
-
import org.junit.Test;
-
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.boot.test.context.SpringBootTest;
-
import org.springframework.test.context.junit4.SpringRunner;
-
import tk.mybatis.mapper.entity.Example;
-
-
@RunWith(SpringRunner.class)
-
@SpringBootTest
-
public
class
UserMapperTest
extends
TestCase {
-
-
@Autowired
-
private UserMapper userMapper;
-
-
@Test
-
public
void
testFindAll
() {
-
userMapper.selectAll().forEach(user -> {
-
System.out.println(user);
-
});
-
}
-
-
@Test
-
public
void
testByExample
() {
-
Example
example
=
new
Example(User.class);
-
example.createCriteria().andLike(
"name",
"%a%");
-
userMapper.selectByExample(example).forEach(user -> {
-
System.out.println(user);
-
});
-
}
-
-
}
(6)自定义映射方法
创建一个UserMapper.xml
-
<?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.lxy.dao.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>
在UserMapper接口中添加方法:
-
package com.lxy.dao;
-
-
import com.lxy.domain.User;
-
import tk.mybatis.mapper.common.Mapper;
-
-
import java.util.List;
-
-
public
interface
UserMapper
extends
Mapper<User> {
-
-
public List<User>
findByUser
(User user);
-
}
测试:
-
@Test
-
public
void
testFindByUser
() {
-
User
user
=
new
User();
-
user.setName(
"a");
-
user.setNote(
"c");
-
userMapper.findByUser(user).forEach(user1 -> {
-
System.out.println(user1);
-
});
-
-
}
7.2.3 tk.mybatis中Mapper的内置方法
Select 方法: List<T> select(T record); 说明:根据实体中的属性值进行查询,查询条件使用等号
方法: T selectByPrimaryKey(Object key); 说明:根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
方法: List<T> 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<T> 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条件删除数据
八、thymeleaf
概念:
Thymeleaf 是一个跟 FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下特点:
(1)动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,无网络显示静态内容,有网络用后台得到数据替换静态内容
(2)与SpringBoot完美整合,springboot默认整合thymeleaf
8.1 入门案例
8.1.1 编写接口
编写UserService,调用UserMapper的查询所有方法
-
package com.lxy.service;
-
-
import com.lxy.dao.UserMapper;
-
import com.lxy.domain.User;
-
import org.springframework.beans.factory.annotation.Autowired;
-
-
import java.util.List;
-
-
public
class
UserService {
-
-
@Autowired
-
private UserMapper userMapper;
-
-
public List<User>
findAll
(){
-
return userMapper.selectAll();
-
}
-
}
8.1.2 编写一个controller,返回一些用户数据,放入模型中,等会在页面渲染
-
package com.lxy.controller;
-
-
import com.lxy.domain.User;
-
import com.lxy.service.UserService;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.stereotype.Controller;
-
import org.springframework.ui.Model;
-
-
-
import java.util.List;
-
-
@Controller
-
public
class
UserController {
-
-
@Autowired
-
private UserService userService;
-
-
public String
all
(Model model){
-
List<User> list = userService.findAll();
-
model.addAttribute(
"users",list);
-
// 返回模板名称(就是classpath:/templates/目录下的html文件名)
-
return
"users";
-
}
-
}
8.1.3 引入启动器
直接引入启动器:
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-thymeleaf
</artifactId>
-
</dependency>
SpringBoot会自动为Thymeleaf注册一个视图解析器:
与解析JSP的InternalViewResolver类似,Thymeleaf也会根据前缀和后缀来确定模板文件的位置:
默认前缀: classpath:/templates/
默认后缀: .html
所以如果我们返回视图: users ,会指向到 classpath:/templates/users.html
一般我们无需进行修改,默认即可。
8.1.4 静态页面
根据上面的文档介绍,模板默认放在classpath下的templates文件夹,我们新建一个html文件放入其中:
编写html模板,渲染模型中的数据:
注意,把html 的名称空间,改成: xmlns:th="http://www.thymeleaf.org" 会有语法提示
-
<!DOCTYPE html>
-
<html 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>序号
</th>
-
<th>id
</th>
-
<th>姓名
</th>
-
<th>用户名
</th>
-
<th>年龄
</th>
-
<th>性别
</th>
-
<th>生日
</th>
-
<th>备注
</th>
-
<th>操作
</th>
-
</tr>
-
<tr th:each="user,status:${users}" th:object="${user}">
-
<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 href="#">删除
</a>
-
<a href="#">修改
</a>
-
<a href="#">审核
</a>
-
</td>
-
</tr>
-
</table>
-
</div>
-
</body>
-
</html>
8.1.5 测试
接下来,我们打开页面测试一下:
8.1.6 模板缓存
Thymeleaf会在第一次对模板解析之后进行缓存,极大的提高了并发处理能力。但是这给我们开发带来了不便,修改页面后并不会立刻看到效果,我们开发阶段可以关掉缓存使用:
-
# 开发阶段关闭thymeleaf的模板缓存
-
spring.thymeleaf.cache=false
注意:
在Idea中,我们需要在修改页面后按快捷键: Ctrl + Shift + F9 对项目进行rebuild才可以。
我们可以修改页面,测试一下。
8.2 thymeleaf详解
8.2.1 表达式
它们分为三类
1. 变量表达式
2. 选择或星号表达式
3. URL表达式
(1) 变量表达式
变量表达式即OGNL表达式或Spring EL表达式(在Spring中用来获取model attribute的数据)。如下所示:
${session.user.name}
它们将以HTML标签的一个属性来表示:
-
<h5>表达式
</h5>
-
<span>${text}
</span>
-
<span th:text="${text}">你好 thymleaf
</span>
(2) 选择(星号)表达式
选择表达式很像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(map)来执行,如下: *{customer.name}
被指定的object由th:object属性定义:users.html
-
<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>
-
....
(3) URL表达式
URL表达式指的是把一个有用的上下文或回话信息添加到URL,这个过程经常被叫做URL重写。 @{/order/list} URL还可以设置参数: @{/order/details(id=${orderId}, name=*{name})} 相对路径:@{../documents/report}
让我们看这些表达式:
-
<form th:action="@{/createOrder}">
-
<a href="main.html" th:href="@{/main}">
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>
8.2.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,…
(2) 文本操作(Text operations)
字符串连接(String concatenation): +
文本替换(Literal substitutions): |The name is ${name}|
(3) 算术运算(Arithmetic operations)
二元运算符(Binary operators): +, -, *, /, %
减号(单目运算符)Minus sign (unary operator): -
(4) 布尔操作(Boolean operations)
二元运算符(Binary operators): and, or
布尔否定(一元运算符)Boolean negation (unary operator): !, not
(5) 比较和等价(Comparisons and equality)
比较(Comparators): >, <, >=, <= (gt, lt, ge, le)
等值运算符(Equality operators): ==, != (eq, ne)
(6) 条件运算符(Conditional operators)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
8.2.3 常用th标签
还有非常多的标签,这里只列出最常用的几个
8.2.4 基本用法
(1) 赋值、字符串拼接
字符串拼接还有另外一种简洁的写法
-
<a th:href="|/update/${user.id}|">修改
</a>
-
<a th:href="'/approve/' + ${user.id}">审核
</a>
(2) 条件判断 If/Unless
Thymeleaf中使用th:if和th:unless属性进行条件判断,下面的例子中, <a> 标签只有在 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>
th:unless于th:if恰好相反,只有表达式中的条件不成立,才会显示其内容。
也可以使用 (if) ? (then) : (else) 这种语法来判断显示的内容
(3) for 循环
-
<tr th:each="user,status:${users}" th:object="${user}" th:bgcolor="${status.even} ? 'gray'">
-
<th th:text="${status.count}">1
</th>
-
<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称作状态变量,属性有:
index:当前迭代对象的index(从0开始计算)
count: 当前迭代对象的index(从1开始计算)
size:被迭代对象的大小
current:当前迭代变量
even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)
first:布尔值,当前循环是否是第一个
last:布尔值,当前循环是否是最后一个
(4) 内联文本
使用的是后台传过来的Model中的数据
内联文本:[[…]]内联文本的表示方式,使用时,必须先用th:inline=”text/javascript/none”激活,th:inline可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text的代码少,不利于原型显示。
在thymeleaf指令中显示:
<h6 th:text="${text}">静态内容</h6>
使用内联文本显示model attribute:
-
<h5>内联文本
</h5>
-
<div>
-
<h6 th:inline="text">[[${text}]]
</h6>
-
<h6 th:inline="none">[[${text}]]
</h6>
-
<h6>[[${text}]]
</h6>
-
</div>
原则能用指令就用th指令
(5) 内联js
内联文本:[[…]]内联文本的表示方式,使用时,必须先用th:inline=”text/javascript/none”激活,th:inline可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text的代码少,不利于原型显示。
-
<h5>内联js
</h5>
-
<script th:inline="javascript">
-
/*<![CDATA[*/
-
var text = '[[${text}]]';
-
alert(text);
-
/*]]>*/
-
</script>
(6) 内嵌变量
为了模板更加易用,Thymeleaf还提供了一系列Utility对象(内置于Context中),可以通过#直接访问:
dates : java.util.Date**的功能方法类。
calendars : 类似#dates,面向java.util.Calendar
numbers : 格式化数字的功能方法类
strings : 字符串对象的功能类,contains,startWiths,prepending/appending等等。
objects: 对objects的功能类操作。
bools: 对布尔值求值的功能方法。
arrays:对数组的功能类方法。
lists: 对lists功能类方法
sets
maps
下面用一段代码来举例一些常用的方法:
1. dates
-
<h5>内置变量
</h5>
-
<h6 th:text="${#dates.createNow()}">获取当前日期
</h6>
2.strings
-
<h5>内置变量
</h5>
-
<h6 th:text="${#dates.createNow()}">获取当前日期
</h6>
-
<h6 th:text="${#strings.substring(text, 6, 9)}">截取字符串
</h6>
-
<h6 th:text="${#strings.length(text)}">获得长度
</h6>
-
<h6 th:text="${#strings.randomAlphanumeric(6)}">随机字符串
</h6>
-
<h6 th:text="${#strings.equals(text, 'hello text....')}">
</h6>
8.3 使用thymeleaf布局
使用thymeleaf布局非常的方便
在/resources/templates/目录下创建footer.html,内容如下
-
<!DOCTYPE html>
-
<html xmlns:th="http://www.thymeleaf.org">
-
<body>
-
<footer th:fragment="copy(title)">
-
© 2020 lxy版权所有
<br>
-
<span th:text="${title}">title footer
</span>
-
</footer>
-
</body>
-
</html>
在页面任何地方引入:
-
<h5>thymeleaf布局
</h5>
-
<div th:insert="footer :: copy('开课吧1')">
</div>
-
<div th:replace="footer :: copy('开课吧2')">
</div>
-
<div th:include="footer :: copy('开课吧3')">
</div>
th:insert :保留自己的主标签,保留th:fragment的主标签。
th:replace :不要自己的主标签,保留th:fragment的主标签。
th:include :保留自己的主标签,不要th:fragment的主标签。(官方3.0后不推荐)
返回的HTML如下:
-
<h5>thymeleaf布局
</h5>
-
<div>
<footer>
-
© 2020 lxy版权所有
<br>
-
<span>lxy
</span>
-
</footer>
</div>
-
<footer>
-
© 2020 lxy版权所有
<br>
-
<span>lxy
</span>
-
</footer>
-
<div>
-
© 2020 lxy版权所有
<br>
-
<span>lxy
</span>
-
</div>
九、Mybatis Plus
9.1 简介
Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,避免了我们重复CRUD语句。
9.2 快速入门
9.2.1 创建工程,引入依赖
-
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
<modelVersion>4.0.0
</modelVersion>
-
-
<groupId>com.lxy
</groupId>
-
<artifactId>mybatis-plus-demo-quickstart-test
</artifactId>
-
<version>1.0-SNAPSHOT
</version>
-
-
<parent>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-parent
</artifactId>
-
<version>2.3.0.RELEASE
</version>
-
<relativePath/>
-
</parent>
-
<properties>
-
<project.build.sourceEncoding>UTF-8
</project.build.sourceEncoding>
-
<project.reporting.outputEncoding>UTF-8
</project.reporting.outputEncoding>
-
<java.version>1.8
</java.version>
-
<mybatisplus.version>3.3.2
</mybatisplus.version>
-
<skipTests>true
</skipTests>
-
</properties>
-
<dependencies>
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter
</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>com.h2database
</groupId>
-
<artifactId>h2
</artifactId>
-
<scope>runtime
</scope>
-
</dependency>
-
<dependency>
-
<groupId>com.baomidou
</groupId>
-
<artifactId>mybatis-plus-boot-starter
</artifactId>
-
<version>${mybatisplus.version}
</version>
-
</dependency>
-
<dependency>
-
<groupId>org.assertj
</groupId>
-
<artifactId>assertj-core
</artifactId>
-
<scope>test
</scope>
-
</dependency>
-
<!--简化开发 实体类不用写get set方法-->
-
<dependency>
-
<groupId>org.projectlombok
</groupId>
-
<artifactId>lombok
</artifactId>
-
<scope>provided
</scope>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-test
</artifactId>
-
<scope>test
</scope>
-
</dependency>
-
<dependency>
-
<groupId>com.github.pagehelper
</groupId>
-
<artifactId>pagehelper
</artifactId>
-
<version>5.1.11
</version>
-
</dependency>
-
</dependencies>
-
<build>
-
<plugins>
-
<plugin>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-maven-plugin
</artifactId>
-
</plugin>
-
</plugins>
-
</build>
-
</project>
9.2.2 配置文件application.yml
yml配置简介
在Springboot中,推荐使用properties或者YAML文件来完成配置,但是对于较复杂的数据结构来说,YAML又远远优于properties。我们快速介绍YAML的常见语法格式。
先来看一个Springboot中的properties文件和对应YAML文件的对比:
-
#properties(示例来源于Springboot User guide):
-
environments.dev.url=http://dev.bar.com
-
environments.dev.name=Developer Setup
-
environments.prod.url=http://foo.bar.com
-
environments.prod.name=My Cool App
-
my.servers[0]=dev.bar.com
-
my.servers[1]=foo.bar.com
可以明显的看到,在处理层级关系的时候,properties需要使用大量的路径来描述层级(或者属性),比如environments.dev.url和environments.dev.name。其次,对于较为复杂的结构,比如数组(my.servers),写起来更为复杂。而对应的YAML格式文件就简单很多:
-
#YAML格式
-
environments:
-
dev:
-
url: http://dev.bar.com
-
name: Developer Setup
-
prod:
-
url: http://foo.bar.com
-
name: My Cool App
-
my:
-
servers:
-
- dev.bar.com
-
- foo.bar.com
application.yml :
-
# DataSource Config
-
spring:
-
datasource:
-
driver-class-name: org.h2.Driver
-
schema: classpath:db/schema-h2.sql
-
data: classpath:db/data-h2.sql
-
url: jdbc:h2:mem:test
-
username: root
-
password: test
-
# Logger Config
-
logging:
-
level:
-
com.lxs.quickstart: debug
数据库脚本文件/db/data-h2.sql和/db/schema-h2.sql(拷贝)
h2数据库是一个基于内存的数据库,在jvm启动时,自动执行脚本加载相应的数据
springboot 中使用h2数据库直接按照上面配置,配置schema表结构脚本和data数据脚本即可
注意这里用户名密码可以省略不写,或者随意设定
9.2.3 实体类
-
package com.lxy.quickstart.entity;
-
-
import lombok.Data;
-
-
@Data
-
public
class
User {
-
private Long id;
-
private String name;
-
private Integer age;
-
private String email;
-
}
9.2.4 dao
-
package com.lxy.quickstart.mapper;
-
-
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
import com.lxy.quickstart.entity.User;
-
-
public
interface
UserMapper
extends
BaseMapper<User> {
-
}
9.2.5 启动类
-
package com.lxy.quickstart;
-
-
import org.mybatis.spring.annotation.MapperScan;
-
import org.springframework.boot.SpringApplication;
-
import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-
@SpringBootApplication
-
@MapperScan("com.lxy.quickstart.mapper")
-
public
class
Application {
-
public
static
void
main
(String[] args) {
-
SpringApplication.run(Application.class, args);
-
}
-
}
9.2.6 测试
-
package com.lxy.quickstart.mapper;
-
-
import com.lxy.quickstart.entity.User;
-
import junit.framework.TestCase;
-
import org.junit.Assert;
-
import org.junit.Test;
-
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.boot.test.context.SpringBootTest;
-
import org.springframework.test.context.junit4.SpringRunner;
-
-
import java.util.List;
-
-
@RunWith(SpringRunner.class)
-
@SpringBootTest
-
public
class
UserMapperTest
extends
TestCase {
-
-
@Autowired
-
private UserMapper userMapper;
-
-
@Test
-
public
void
testSelect
() {
-
System.out.println((
"----- selectAll method test ------"));
-
List<User> userList = userMapper.selectList(
null);
-
//Assert.assertEquals(6, userList.size());
-
userList.forEach(System.out::println);
-
}
-
}
测试结果:
9.3 常用注解
MyBatisPlus提供了一些注解供我们在实体类和表信息出现不对应的时候使用。通过使用注解完成逻辑上匹配。
9.3.1 mybatis plus注解策略配置
如果mysql自增主键注解策略设置如下
@TableId(type = IdType.AUTO)
private Long id;
默认主键策略:
/**
* 采用雪花算法生成全局唯一主键
**/
ASSIGN_ID(3),
9.3.2 排除实体类中非表字段
使用 @TableField(exist = false) 注解
9.4 内置增删改查
9.4.1 增加
-
@Test
-
public
void
testInsert
() {
-
User
user
=
new
User();
-
user.setName(
"aaa");
-
user.setEmail(
"lxy@163.com");
-
user.setAge(
3);
-
Assert.assertTrue(userMapper.insert(user) >
0);
-
userMapper.selectList(
null).forEach(System.out :: println);
-
}
9.4.2 删除
-
@Test
-
public
void
testDelete
() {
-
// //主键删除
-
// userMapper.deleteById(3l);
-
// userMapper.selectList(null).forEach(System.out :: println);
-
-
// //批量删除:1
-
// userMapper.delete(new QueryWrapper<User>().like("name", "J"));
-
// userMapper.selectList(null).forEach(System.out :: println);
-
-
// //批量删除:2
-
// userMapper.delete(Wrappers.<User>query().like("name", "J"));
-
// userMapper.selectList(null).forEach(System.out :: println);
-
//批量删除:3
-
userMapper.delete(Wrappers.<User>query().lambda().like(User::getName,
"J"));
-
userMapper.selectList(
null).forEach(System.out :: println);
-
-
}
9.4.3 更新
-
@Test
-
public
void
testUpdate
() {
-
// //基本修改
-
// userMapper.updateById(new User().setId(1l).setName("慧科"));
-
// userMapper.selectList(null).forEach(System.out :: println);
-
// //批量修改:1
-
// mapper.update(null, Wrappers.<User>update().set("email", "huike@163.com").like("name","J"));
-
// mapper.selectList(null).forEach(System.out :: println);
-
//批量修改:2
-
userMapper.update(
new
User().setEmail(
"huike@163.com"), Wrappers.<User>update().like(
"name",
"J"));
-
userMapper.selectList(
null).forEach(System.out :: println);
-
}
在更新时,使用的方法必须先在对应的实体类加上
@Accessors(chain = true)这个注解,此注解代表使用get set方法之后返回的是实体类的对象,而不是void,所以可以使用如上的更新方法便于操作。
9.4.4 查询
-
@Test
-
public
void
testSelectTwo
() {
-
//基本查询
-
// System.out.println(mapper.selectOne(Wrappers.<User>query().eq("name", "Tom")));
-
//投影查询
-
userMapper.selectList(
new
QueryWrapper<User>().select(
"id",
"name")).forEach(user -> {
-
System.out.println(user);
-
});
-
}
映射查询只能查找到两列数据,其余列全部为null。
9.5 分页
9.5.1 内置分页
(1)添加分页插件
-
package com.lxy.quickstart.config;
-
-
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
-
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
-
import org.springframework.context.annotation.Bean;
-
-
public
class
MybatisPlusConfig {
-
/**
-
* 分页插件
-
*/
-
@Bean
-
public PaginationInterceptor
paginationInterceptor
() {
-
// 开启 count 的 join 优化,只针对 left join !!!
-
return
new
PaginationInterceptor().setCountSqlParser(
new
JsqlParserCountOptimize(
true));
-
}
-
}
(2) 优化left join count场景
在一对一join操作时,也存在优化可能,看下面sql :
-
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
(3) 测试
-
@Test
-
public
void
testPage
() {
-
System.out.println(
"------ baseMapper 自带分页 ------");
-
Page<User> page =
new
Page<>(
1,
5);
-
IPage<User> pageResult = userMapper.selectPage(page,
new
QueryWrapper<User>().eq(
"age",
331));
-
System.out.println(
"总条数 ------> " + pageResult.getTotal());
-
System.out.println(
"当前页数 ------> " + pageResult.getCurrent());
-
System.out.println(
"当前每页显示数 ------> " + pageResult.getSize());
-
pageResult.getRecords().forEach(System.out :: println);
-
}
9.5.2 自定义xml分页
application.yml配置文件
-
# 配置mybatis plus
-
mybatis-plus:
-
type-aliases-package: com.lxy.quickstart.entity #别名搜索
-
mapper-locations: classpath:/mappers/*.xml #加载映射文件
UserMapper接口:
-
package com.lxy.quickstart.mapper;
-
-
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
import com.baomidou.mybatisplus.core.metadata.IPage;
-
import com.lxy.quickstart.entity.User;
-
import org.apache.ibatis.annotations.Param;
-
-
public
interface
UserMapper
extends
BaseMapper<User> {
-
/**
-
* 如果映射的接口方法有2个参数需要@Param定义参数名,定义参数名后,映射文件中使用p.属性 c.属性,具体访
-
问
-
*
-
* @param page
-
* @param condition
-
* @return
-
*/
-
public IPage<User>
selectUserByPage
(@Param("p") IPage<User> page, @Param("c") User condition);
-
-
}
UserMapper.xml映射文件 :
-
<?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.lxy.quickstart.mapper.UserMapper">
-
<sql id=
"selectSql">
-
SELECT
-
*
-
FROM
-
user
-
</sql>
-
<select id=
"selectUserByPage" resultType=
"user">
-
<include refid=
"selectSql"></include>
-
<where>
-
<
if test=
"c.age !=null">
-
age = #{c.age}
-
</
if>
-
<
if test=
"c.email !=null">
-
and email like
'%${c.email}%'
-
</
if>
-
</where>
-
</select>
-
</mapper>
测试:
-
@Test
-
public
void
testXmlPage
() {
-
System.out.println(
"------ baseMapper 自定义xml分页 ------");
-
Page<User> page =
new
Page<>(
1,
5);
-
User
user
=
new
User();
-
user.setAge(
331);
-
user.setEmail(
"test");
-
IPage<User> pr = userMapper.selectUserByPage(page, user);
-
System.out.println(
"总条数 ------> " + pr.getTotal());
-
System.out.println(
"当前页数 ------> " + pr.getCurrent());
-
System.out.println(
"当前每页显示数 ------> " + pr.getSize());
-
pr.getRecords().forEach(System.out :: println);
-
}
9.5.3 pageHelper分页
引入pageHelper依赖:
-
<dependency>
-
<groupId>com.github.pagehelper
</groupId>
-
<artifactId>pagehelper
</artifactId>
-
<version>5.1.11
</version>
-
</dependency>
mybatis plus 整合pageHelper的配置类 :
-
package com.lxy.quickstart.config;
-
-
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
-
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
-
import com.github.pagehelper.PageInterceptor;
-
import org.springframework.context.annotation.Bean;
-
-
public
class
MybatisPlusConfig {
-
/**
-
* 分页插件
-
*/
-
@Bean
-
public PaginationInterceptor
paginationInterceptor
() {
-
// 开启 count 的 join 优化,只针对 left join !!!
-
return
new
PaginationInterceptor().setCountSqlParser(
new
JsqlParserCountOptimize(
true));
-
}
-
-
/**
-
* 两个分页插件都配置,不会冲突
-
* pagehelper的分页插件
-
*/
-
@Bean
-
public PageInterceptor
pageInterceptor
() {
-
return
new
PageInterceptor();
-
}
-
}
映射文件:
-
<?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.lxy.quickstart.mapper.UserMapper">
-
<sql id="selectSql">
-
SELECT
-
*
-
FROM
-
user
-
</sql>
-
<select id="selectUserByPage" resultType="user">
-
<include refid="selectSql">
</include>
-
<where>
-
<if test="c.age !=null">
-
age = #{c.age}
-
</if>
-
<if test="c.email !=null">
-
and email like '%${c.email}%'
-
</if>
-
</where>
-
</select>
-
-
<select id="selectUserByPage2" resultType="user">
-
<include refid="selectSql">
</include>
-
<where>
-
<if test="age !=null">
-
age = #{age}
-
</if>
-
<if test="email !=null">
-
and email like '%${email}%'
-
</if>
-
</where>
-
</select>
-
</mapper>
UserMapper添加代码:
-
package com.lxy.quickstart.mapper;
-
-
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
import com.baomidou.mybatisplus.core.metadata.IPage;
-
import com.lxy.quickstart.entity.User;
-
import org.apache.ibatis.annotations.Param;
-
-
import java.util.List;
-
-
public
interface
UserMapper
extends
BaseMapper<User> {
-
/**
-
* 如果映射的接口方法有2个参数需要@Param定义参数名,定义参数名后,映射文件中使用p.属性 c.属性,具体访
-
问
-
*
-
* @param page
-
* @param condition
-
* @return
-
*/
-
public IPage<User>
selectUserByPage
(@Param("p") IPage<User> page, @Param("c") User condition);
-
-
public List<User>
selectUserByPage2
(User condition);
-
}
测试类:
-
@Test
-
public
void
testPageHelper
(){
-
//条件对象
-
User
u
=
new
User();
-
u.setAge(
331);
-
u.setEmail(
"test");
-
-
PageInfo<User> page = PageHelper.startPage(
2,
10).doSelectPageInfo(() ->{
-
//自定义xml映射文件
-
userMapper.selectUserByPage2(u);
-
// //使用mp内置方法
-
// userMapper.selectList(Wrappers.<User>query());
-
});
-
-
page.getList().forEach(System.out :: println);
-
-
System.out.println(
"总行数=" + page.getTotal());
-
System.out.println(
"当前页=" + page.getPageNum());
-
System.out.println(
"每页行数=" + page.getPageSize());
-
System.out.println(
"总页数=" + page.getPages());
-
System.out.println(
"起始行数=" + page.getStartRow());
-
System.out.println(
"是第一页=" + page.isIsFirstPage());
-
System.out.println(
"是最后页=" + page.isIsLastPage());
-
System.out.println(
"还有下一页=" + page.isHasNextPage());
-
System.out.println(
"还有上一页=" + page.isHasPreviousPage());
-
System.out.println(
"页码列表" + Arrays.toString(page.getNavigatepageNums()));
-
-
}
至此SpringBoot的知识点及其源码就全部结束了,如果您看到了这里,我相信一定对您有很大的帮助,如果方便的话还请给博主三连,谢谢观看!!!
转载:https://blog.csdn.net/m0_57209427/article/details/128098454