SpringBoot第二部分


Mybatis框架集成

Mybatis是作用于持久层的的框架,主要与数据库进行交互,可以简化程序编写时与数据库的交互

配置与实现

配置依赖项

<!-- mybatis 集成 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- springboot 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- c3p0 数据源 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>

配置application.yml

# 数据源配置
spring:
datasource:
type: com.mchange.v2.c3p0.ComboPooledDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/DBName?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root

# mybatis 配置
mybatis:
mapper-locations: classpath:/mappers/*.xml
type-aliases-package: com.xxxk.springframework.po
configuration:
## 下划线转驼峰配置
map-underscore-to-camel-case: true

# pagehelper
pagehelper:
helper-dialect: mysql

Mybatis通过XML文件配置与接口的映射执行sql语句,在resources/mappers目录下添加xml配置,id属性与接口方法名对应

<?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.xxxx.springboot.dao.UserMapper">
<select id="queryUserByUserName" parameterType="string"
resultType="com.example.User">
select
id,user_name,user_pwd
from tb_user
where user_name=#{userName}
</select>
</mapper>

创建接口

public interface UserMapper {
User queryUserByUserName(String userName);
}

Service层注入接口调用方法

@Service
public class UserService {

@Resource
private UserMapper userMapper;

public User queryUserByUserName(String userName) {
return userMapper.queryUserByUserName(userName);
}
}

最后的启动类需要@MapperScan扫描指定包中创建的接口

对于分页查询,将查询的参数封装在一个类中,

public class UserQuery {

private Integer pageNum = 1;
private Integer pageSize = 10;
private String userName;
}

定义接口

public interface UserMapper {
public List<User> selectByParams(UserQuery userQuery);
}

配置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.xxxx.springboot.dao.UserMapper">
<select id="selectByParams" parameterType="com.xxxx.springboot.query.UserQuery"
resultType="com.xxxx.springboot.po.User">
select
*
from
tb_user
<where>
<if test="null != userName and userName !=''">
and user_name like concat('%',#{userName}, '%')
</if>
</where>
</select>
</mapper>

Service层进行对应实现

@Service
public class UserService {

@Resource
private UserMapper userMapper;

public PageInfo<User> queryUserByParams(UserQuery userQuery){
PageHelper.startPage(userQuery.getPageNum(), userQuery.getPageSize());
return new PageInfo<User>(userMapper.selectByParams(userQuery));
}
}

Swagger2文档构建

在 Spring Boot 多终端项目中,通常使用一套 API 接口为不同终端提供服务。为方便联调测试,后端需提供接口文档,可通过 Swagger2 自动生成,免去手动编写文档的繁琐

环境配置

配置依赖项

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

添加配置类

@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.xxxx.springboot.controller"))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("用户管理接口API文档")
.version("1.0")
.build();
}
}

常用注解

@Api

用在请求的类上,具有tag属性,说明该类的作用

@Api(tags="APP用户注册Controller")

@ApiOperation

用在请求的方法上,说明方法的作用,value属性说明方法的作用,notes属性为方法的备注说明

@ApiOperation(value="用户注册", notes="手机号、密码都是必填项,年龄是选填项,但必须是数字")

@ApiImplicitParams

用在请求的方法上,包含一组参数说明,@ApiImplicitParam用在 @ApiImplicitParams 注解中,指定一个请求参数的配置信息

属性名 说明 示例值
name 参数名 "userId"
value 参数的汉字说明、解释 "用户ID"
required 参数是否必须传 true / false
paramType 参数放在哪个地方 "header" / "query" / "path" / "body" / "form"
对应注解 请求参数获取方式 @RequestHeader / @RequestParam / @PathVariable
datatype 参数类型,默认为String "Integer" / "String" / "Boolean"
defaultValue 参数的默认值 "0" / "default"

paramType 对应关系

paramType 值 对应注解 使用场景
header @RequestHeader 从请求头获取参数
query @RequestParam 从URL查询参数获取
path @PathVariable 从URL路径获取(RESTful接口)
body @RequestBody 从请求体获取(不常用)
form - 表单提交(不常用)

示例如下

@ApiImplicitParams({
@ApiImplicitParam(name="mobile", value="手机号", required=true, paramType="form"),
@ApiImplicitParam(name="password", value="密码", required=true, paramType="form"),
@ApiImplicitParam(name="age", value="年龄", required=true, paramType="form", dataType="Integer")
})

@ApiResponses

用于请求的方法上,表示一组响应,@ApiResponse用在 @ApiResponses 中,一般用于表达一个错误的响应信息

属性名 说明 示例值
code HTTP 状态码 400, 404, 500
message 响应信息描述 "请求参数没填好", "用户不存在"
response 抛出的异常类(可选) IllegalArgumentException.class, UserNotFoundException.class

示例如下

@ApiOperation(value = "select请求", notes = "多个参数,多种的查询参数类型")
@ApiResponses({
@ApiResponse(code = 400, message = "请求参数没填好"),
@ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对")
})

@ApiModel

用于响应类上,表示一个返回响应数据的信息(这种一般用在post创建的时候,使用@RequestBody这样的场景,请求参数无法使用@ApiImplicitParam注解进行描述的时候),@ApiModelProperty用在属性上,描述响应类的属性

@ApiModel(description = "返回响应数据")
public class RestMessage implements Serializable {
@ApiModelProperty(value = "是否成功")
private boolean success = true;
@ApiModelProperty(value = "返回对象")
private Object data;
@ApiModelProperty(value = "错误编号")
private Integer errCode;
@ApiModelProperty(value = "错误信息")
private String message;

}

启动项目,访问项目页面/swagger_ui.html即可查看接口文档,例如http://localhost:8080/swagger_ui.html


应用热部署

热部署是指在不重启应用的情况下,实时更新代码、修复缺陷或增加功能,使修改立即生效

配置与使用

添加DevTools依赖项

<!-- DevTools 的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!--当前这个项目被继承之后,这个不向下传递-->
<optional>true</optional>
</dependency>

同时在 plugin 中添加 devtools 生效标志

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork><!-- 如果没有该配置,热部署的devtools不生效 -->
</configuration>
</plugin>

全局文件配配置,将 spring.devtools.restart.enabled 设为 false,则重启功能会被禁用,但类加载器仍然会初始化

spring:
## 热部署配置
devtools:
restart:
enabled: true
# 设置重启的目录,添加目录的文件需要 restart
additional-paths: src/main/java
# 解决项目自动重新编译后接口报404的问题
poll-interval: 3000
quiet-period: 1000

IDEA配置自动编译,File->Strrings->Compiler->Build Project automatically

Help -> ind Action ->搜索Registry->勾选compiler.automake.allow.when.app.running


单元测试

做过 web 项目开发的对于单元测试都不陌生。SpringBoot 框架对单元测试也提供了良好的支持,可以快速、恰当地测试业务代码功能

配置

添加依赖项

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>

Service业务方法测试

使用@Runwith注解指定特定的类进行单元测试,在Spring环境下的单元测试使用SpringRunner.class,使用@SpringBootTest注解指定入口类,通常是 @SpringBootApplication 注解的主类

使用@Before注解设定在测试方法执行前的行为,@After注解设定在测试方法执行后的行为,@Test注解标记要测试的方法

控制层接口方法测试

视图层代码使用 MockMvc 进行测试,其实现了对http请求的模拟,不需依赖网路环境,使用@AutoConfigureMvc注解进行使用

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
@AutoConfigureMockMvc
public class TestUserController {
private Logger log = LoggerFactory.getLogger(TestUserController.class);

@Autowired
private MockMvc mockMvc;

@Test
public void apiTest01() throws Exception {
MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/user/list")
.contentType("text/html")
.accept(MediaType.APPLICATION_JSON);

ResultActions perform = mockMvc.perform(request);

perform.andExpect(MockMvcResultMatchers.status().isOk());

MvcResult mvcResult = perform.andReturn();

MockHttpServletResponse response = mvcResult.getResponse();

log.info("响应状态: {}", response.getStatus());
log.info("响应内容: {}", response.getContentAsString());
}

}

分布式缓存Ehcache集成

EhCache 是一个成熟的 Java 缓存框架,最初从 Hibernate 发展而来。它是进程内缓存系统,提供了多种灵活的缓存管理方案,包括内存存储、磁盘文件存储和分布式存储方式,具有快速、简单的特点

相关注解

@CacheConfig 注解

标注在类上,用于配置该类中所有缓存方法的公共属性,例如设置缓存名称

@CacheConfig(cacheNames = "users")
public interface UserService { ... }

@Cacheable 注解

应用到读取数据的方法上,即可缓存的方法,如查找方法。它首先从缓存中读取数据,如果没有命中,再调用相应方法获取数据,然后把数据添加到缓存中

参数 作用
value 指定缓存存储的集合名
cacheNames value 的别名(Spring 4 新增)
key 缓存对象在 Map 中的 key 值
condition 缓存条件(调用前判断)
unless 缓存条件(调用后判断)
keyGenerator 指定自定义 key 生成器
cacheResolver 指定缓存解析器
cacheManager 指定缓存管理器

@CachePut 注解

应用到写数据的方法上,如新增、修改方法。调用方法时会自动把相应的数据放入缓存。@CachePut 的参数与 @Cacheable 类似

@CachePut(value = "user", key = "#user.id")
public User save(User user) {
users.add(user);
return user;
}

@CacheEvict 注解

应用到移除数据的方法上,如删除方法。调用方法时会从缓存中移除相应的数据

@CacheEvict(value = "user", key = "#id")
void delete(final Integer id);

除了同 @Cacheable 一样的参数之外,@CacheEvict 还有下面两个参数:

allEntries非必需,默认为 false。当为 true 时,会移除所有数据

beforeInvocation非必需,默认为 false,会在调用方法之后移除数据。当为 true 时,会在调用方法之前移除数据

@Caching注解

用于组合多个 Cache 注解使用

@Caching(
put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.age")
}
)

环境配置

添加依赖项

<!-- Ehcache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

resources目录下新建ehcache.xml,用于相关配置

进行全局配置

spring:
## Ehcache缓存配置
cache:
ehcache:
config: classpath:ehcache.xml

启动类启用缓存,添加@EnableCaching注解

@SpringBootApplication
@EnableCaching
public class Starter {

public static void main(String[] args) {
SpringApplication.run(Starter.class, args);
}
}

此外,bean对象均需实现序列化接口用于存储对象


定时调度Quartz集成

配置依赖项

<!-- Quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

定义定时任务类,实现Job接口,在execute()方法中实现任务逻辑

public class MyFirstJob implements Job {

private Logger log = LoggerFactory.getLogger(MyFirstJob.class);

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info(sdf.format(new Date()) + "-->" + "Hello Spring Boot Quartz...");
}
}

创建配置类,实例化JobDetail并定义Trigger注册到scheduler

@Configuration
public class QuartzConfig {

@Bean
public JobDetail jobDetail1() {
return JobBuilder.newJob(MyFirstJob.class).storeDurably().build();
}

@Bean
public Trigger trigger1() {
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever();
return TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.withSchedule(scheduleBuilder)
.forJob(jobDetail1())
.build();
}
}

事务控制与全局异常

事务控制

使用@Transactional注解进行事务控制,在遇到异常时进行回滚,确保数据同步

全局异常

SpringMVC 中对异常统一处理提供了相应的方式,推荐使用实现 HandlerExceptionResolver 接口的方式,对代码侵入性较小

@ControllerAdvice注解

该注解组合了 @Component 注解功能,最常用的就是作为全局异常处理的切面类。同时通过该注解可以指定包扫描的范围。@ControllerAdvice 约定了几种可行的返回值,如果是直接返回 model 类的话,需要使用 @ResponseBody 进行 JSON 转换

@ExceptionHandler注解

该注解在 Spring 3.X 版本引入,在处理异常时标注在方法级别,代表当前方法处理的异常类型有哪些。具体应用以 Restful 接口为例,测试保存用户接口

定义全局异常处理类

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResultInfo exceptionHandler(Exception e) {
ResultInfo resultInfo = new ResultInfo();
resultInfo.setCode(300);
resultInfo.setMsg("操作失败!");

if(e instanceof ParamsException) {
ParamsException pe = (ParamsException) e;
resultInfo.setMsg(pe.getMsg());
resultInfo.setCode(pe.getCode());
}

return resultInfo;
}
}

数据校验Validation集成

在日常项目开发中,对于前端提交的表单,后台接口接收到表单数据后,为了程序的严谨性,通常后端会加入业务参数的合法校验操作来避免程序的非技术性 bug。这里对于客户端提交的数据校验,Spring Boot 通过 spring-boot-starter-validation 模块包含了数据校验的工作

JSR303:JSR303 是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如 @Null@NotNull@Pattern,位于 javax.validation.constraints 包下。JSR-349 是其升级版本,添加了一些新特性

Hibernate Validation:Hibernate Validation 是对这个规范的实现,并增加了一些其他校验注解,如 @Email@Length@Range 等等

Spring Validation:Spring Validation 对 Hibernate Validation 进行了二次封装,在 Spring MVC 模块中添加了自动校验,并将校验信息封装进了特定的类中

相关注解

注解 功能描述
@AssertFalse 可以为 null,如果不为 null 则必须为 false
@AssertTrue 可以为 null,如果不为 null 则必须为 true
@DecimalMax 设置不能超过的最大值
@DecimalMin 设置不能小于的最小值
@Digits 必须是数字,且整数部分和小数部分的位数在指定范围内
@Future 日期必须在当前日期的未来
@Past 日期必须在当前日期的过去
@Max 值不得超过指定的最大值
@Min 值不得小于指定的最小值
@NotNull 不能为 null(但可以是空字符串、空集合等)
@Pattern 必须满足指定的正则表达式
@Size 集合、数组、Map 等的 size() 值必须在指定范围内
@Email 必须是合法的电子邮件格式
@Length 字符串长度必须在指定范围内
@NotBlank 字符串不能为 null,且 trim() 后不能等于空字符串
@NotEmpty 不能为 null,集合、数组、Map 等 size() 不能为 0;字符串 trim() 后可以等于空字符串
@Range 值必须在指定的范围内
@URL 必须是一个合法的 URL

对字段属性值进行校验

public class User implements Serializable {
private Integer id;

@NotBlank(message = "用户名不能为空!")
private String userName;

@NotBlank(message = "用户密码不能为空!")
@Length(min = 6, max = 10, message = "密码长度至少6位但不超过10位!")
private String userPwd;

@Email
private String email;

}

对接口方法的形参进行校验

@PostMapping("user")
@ApiOperation(value = "用户添加")
@ApiImplicitParam(name = "user", value = "用户实体类", dataType = "User")
public ResultInfo saveUser(@Valid User user) {
ResultInfo resultInfo = new ResultInfo();
return resultInfo;
}

不满足数据校验的情况会抛出Bind Exception异常,可以结合全局异常对其进行处理