环境整合配置
创建项目
创建一个普通的 maven 项目
项目文件结构
添加核心依赖
- spring-boot-starter-parent 放在 parent 标签下
- spring-boot-starter-web
- mybatis-spring-boot-starter
- pagehelper-spring-boot-starter
- mysql-connector-java
- spring-boot-starter-jdbc
- commons-lang3 工具包
- spring-boot-starter-validation
- lombok 开发依赖
pom.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| <?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.6</version> <relativePath /> </parent>
<groupId>org.example</groupId> <artifactId>shsxt-spring-boot-mybatis-quickstart</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging>
<name>shsxt-spring-boot-mybatis-quickstart</name> <url>http://www.example.com</url>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.5</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
<build> <finalName>spring-boot-mybatis-quickstart</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.6.6</version> </plugin> </plugins>
</build> </project>
|
配置插件
pom.xml1 2 3 4 5 6 7 8 9 10
| <build> <finalName>spring-boot-mybatis-quickstart</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.6.6</version> </plugin> </plugins> </build>
|
application.yml 整合配置
application.yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| server: port: 8080 servlet: context-path: /admin
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&&useSSL=false&serverTimezone=GMT%2B8 username: root password: root
mybatis: mapper-locations: classpath:/mappers/*.xml type-aliases-package: org.exmaple.po configuration: map-underscore-to-camel-case: true
pagehelper: helper-dialect: mysql
logging: level: org: example: dao: debug
|
使用c3p0数据源
增加依赖
pom.xml1 2 3 4 5
| <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.5</version> </dependency>
|
配置application.yml
注意数据源的配置和jdbc的不同,这里写什么取决于type对应的类的属性,c3p0的com.mchange.v2.c3p0.ComboPooledDataSource类的属性是什么,这里就要写什么。
application.yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| server: port: 8080 servlet: context-path: /admin
spring: datasource: type: com.mchange.v2.c3p0.ComboPooledDataSource driverClass: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&&useSSL=false&serverTimezone=GMT%2B8 user: root password: root maxPoolSize: 200 minPoolSize: 10 initialPoolSize: 10 acquireIncrement: 5 idleConnectionTestPeriod: 28000 maxIdleTime: 28000
mybatis: mapper-locations: classpath:/mappers/*.xml type-aliases-package: org.exmaple.po configuration: map-underscore-to-camel-case: true
pagehelper: helper-dialect: mysql
logging: level: org: example: dao: debug
|
创建数据源配置类
一定要配置这个类,否则连不上数据源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package org.example.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration public class DataSourceConfiguration extends WebMvcConfigurerAdapter { @Bean(name = "dataSource") @Qualifier(value = "dataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource(){ DataSource dataSource= DataSourceBuilder.create() .type(com.mchange.v2.c3p0.ComboPooledDataSource.class).build(); return dataSource; } }
|
代码实现
C3p0 数据源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package org.example.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration public class DataSourceConfiguration extends WebMvcConfigurerAdapter { @Bean(name = "dataSource") @Qualifier(value = "dataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource(){ DataSource dataSource= DataSourceBuilder.create() .type(com.mchange.v2.c3p0.ComboPooledDataSource.class).build(); return dataSource; } }
|
断言工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package org.example.util;
import org.example.exceptions.ParamsException;
public class AssertUtil {
public static void isTrue(boolean flag, String msg){ if(flag){ throw new ParamsException(msg); } } }
|
自定义参数异常类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package org.example.exceptions;
public class ParamsException extends RuntimeException{ private Integer code = 300; private String msg = "参数异常";
public ParamsException() { super("参数异常"); }
public ParamsException(String msg) { super(msg); this.msg = msg; }
public ParamsException(Integer code) { super("参数异常"); this.code = code; }
public ParamsException(Integer code, String msg) { super("参数异常"); this.code = code; this.msg = msg; }
public Integer getCode() { return code; }
public void setCode(Integer code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; } }
|
分页查询条件类
1 2 3 4 5 6 7 8 9 10 11 12 13
| package org.example.query;
import lombok.Data;
@Data public class UserQuery { private Integer pageNum = 1; private Integer pageSize = 10; private String userName; }
|
全局异常处理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| package org.example;
import org.example.exceptions.ParamsException; import org.springframework.validation.BindException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap; import java.util.Map;
@ControllerAdvice public class GlobalExceptionHandlerResolver {
@ExceptionHandler(value = Exception.class) @ResponseBody public Map<String, Object> exceptionHandler (Exception ex){ Map<String, Object> map = new HashMap<>(); map.put("code",500); map.put("msg","系统异常");
return map; }
@ExceptionHandler(value = ParamsException.class) @ResponseBody public Map<String, Object> paramsExceptionHandler (ParamsException ex){ Map<String, Object> map = new HashMap<>(); map.put("code",ex.getCode()); map.put("msg",ex.getMsg());
return map; }
@ExceptionHandler(value = BindException.class) @ResponseBody public Map<String, Object> paramsExceptionHandler (BindException ex){ Map<String, Object> map = new HashMap<>(); map.put("code",500); map.put("msg",ex.getBindingResult().getFieldError().getDefaultMessage());
return map; } }
|
定义JavaBean对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package org.example.po;
import lombok.Data; import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
@Data public class User { private Integer userId;
@NotBlank(message = "用户名不能为空") private String userName;
@NotBlank(message = "密码不能为空") @Length(min =6, max = 10, message="密码长度至少6位但不超过10位") private String userPwd; }
|
Dao层接口定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package org.example.dao;
import org.example.po.User; import org.example.query.UserQuery; import org.springframework.stereotype.Repository;
import java.util.List;
public interface UserMapper {
User findUserByName(String userName);
User findUserById(Integer userId);
List<User> findUserByParam(UserQuery userQuery);
int addUser(User user);
int updateUser(User user);
int deleteUserById(Integer userId); }
|
SQL映射文件定义
UserMapper.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?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="org.example.dao.UserMapper"> <select id="findUserByName" parameterType="string" resultType="org.example.po.User"> select * from tb_user where user_name=#{userName} </select>
<select id="findUserById" parameterType="int" resultType="org.example.po.User"> select * from tb_user where user_id=#{userId} </select>
<select id="findUserByParam" parameterType="org.example.query.UserQuery" resultType="org.example.po.User"> select * from tb_user <where> <if test ="null != userName and '' != userName"> and user_name like concat('%',#{userName}, '%') </if> </where> </select>
<insert id="addUser"> insert into tb_user(user_name, user_pwd) values (#{userName}, #{userPwd}) </insert>
<update id="updateUser"> update tb_user set user_name = #{userName}, user_pwd=#{userPwd} where user_id=#{userId} </update>
<delete id="deleteUserById"> delete from tb_user where user_id = #{userId} </delete> </mapper>
|
Service业务逻辑层代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| package org.example.service;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.apache.commons.lang3.StringUtils; import org.example.dao.UserMapper; import org.example.po.User; import org.example.query.UserQuery; import org.example.util.AssertUtil; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import java.util.List; import java.util.Map;
@Service public class UserService { @Resource private UserMapper userMapper;
public User findUserByName(String userName){
return this.userMapper.findUserByName(userName); }
public User findUserById(Integer userId){ return this.userMapper.findUserById(userId); }
public PageInfo<User> findUserByParam(UserQuery userQuery){ PageHelper.startPage(userQuery.getPageNum(), userQuery.getPageSize()); List<User> userList = this.userMapper.findUserByParam(userQuery); return new PageInfo<User>(userList); }
public void addUser(User user) { AssertUtil.isTrue(user == null, "用户信息不能为空"); AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()),"用户姓名不能为空"); AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"密码不能为空"); AssertUtil.isTrue(this.findUserByName(user.getUserName()) != null, "用户名不能重复");
AssertUtil.isTrue(this.userMapper.addUser(user) < 1, "添加用户失败"); }
@Transactional(propagation = Propagation.REQUIRED) public void updateUser(User user){ AssertUtil.isTrue(user == null, "用户信息不能为空"); AssertUtil.isTrue(user.getUserId() == null,"用户数据异常"); AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()),"用户姓名不能为空"); AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"密码不能为空"); User checkUser = this.userMapper.findUserByName(user.getUserName()); AssertUtil.isTrue(checkUser != null && !checkUser.getUserId().equals(user.getUserId()), "用户名已存在");
AssertUtil.isTrue(this.userMapper.updateUser(user) < 1, "修改用户失败"); }
public void deleteUserById(Integer userId){ AssertUtil.isTrue( userId == null ||this.userMapper.findUserById(userId) == null, "用户不存在"); AssertUtil.isTrue(this.userMapper.deleteUserById(userId) < 1, "删除用户失败"); } }
|
Controller控制器定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| package org.example.controller;
import com.github.pagehelper.PageInfo; import org.example.exceptions.ParamsException; import org.example.po.User; import org.example.query.UserQuery; import org.example.service.UserService; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.validation.Valid; import java.util.HashMap; import java.util.Map;
@RestController @RequestMapping("/users") public class UserController { @Resource private UserService userService;
@GetMapping("{userName}/username") public User findUserByName(@PathVariable String userName){
return this.userService.findUserByName(userName); }
@GetMapping("{userId}/id") public User findUserById(@PathVariable Integer userId){ return this.userService.findUserById(userId); }
@GetMapping("list") public PageInfo<User> findUserByPage(UserQuery userQuery){ return this.userService.findUserByParam(userQuery); }
@PostMapping public Map<String, Object> addUser(@RequestBody @Valid User user){ Map<String, Object> map = new HashMap<>(); this.userService.addUser(user); map.put("code", 200); map.put("msg", "添加用户成功");
return map; }
@PutMapping public Map<String, Object> updateUser(@RequestBody @Valid User user){ Map<String, Object> map = new HashMap<>(); this.userService.updateUser(user); map.put("code", 200); map.put("msg", "修改用户成功");
return map; }
@DeleteMapping("{userId}") public Map<String, Object> deleteUser(@PathVariable Integer userId){ Map<String, Object> map = new HashMap<>(); this.userService.deleteUserById(userId); map.put("code", 200); map.put("msg", "删除用户成功");
return map; } }
|
添加应用启动入口
启动入口写在 controller 层测父包根目录下
Starter.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package org.example;
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @MapperScan("org.example.dao") public class Starter { public static void main(String[] args) { SpringApplication.run(Starter.class); } }
|
全局异常与事务控制
事务控制
在使用Jdbc作为数据库访问技术时,SpringBoot框架定义了基于 jdbc 的 PlatformTransactionManager
接口的实现 DataSourceTransactionManager
, 并在 Spring Boot 应用启动时自动进行配置。如果使用 jpa ,Spring Boot 同样提供了对应的实现。
数据访问技术 | 实现 |
JDBC | DataSourceTransactionManager |
JPA | JpaTransactionManager |
hibernate | HibernateTransactionManager |
JDO | JdoTransactionManager |
分布式事务 | JtaTransactionManager |
MyBatis 底层数据访问是基于 jdbc 实现的,所以在 Spring Boot 环境下对事务进行控制,事务实现由 Spring Boot 实现并自动配置,在使用时通过注解方式(@Transactional
)标注相关方法加入事务控制即可。
声明式事务配置1 2 3 4
| @Transactional(propagation = Propagation.REQUIRED) public void updateUser(User user){ }
|
全局异常处理
SpringMvc 中对于异常统一处理提供了响应的处理方式,推荐使用的是实现接口 HandlerExceptionResolver
的方式,对代码侵入性较小。Spring Boot 同样提供了对异常的全局性处理,相关注解如下:
该注解组合了 @Component
注解功能,最常用的就是作为全局异常处理的切面类并交给IOC容器维护,同时通过该注解可以指定包扫描的范围。@ControllerAdvice
约定了几种可行的返回值,如果是直接返回 model 类的话,需要使用 @ResponseBody
进行 json
转换。如果返回一个视图,则直接返回一个字符串,这个字符串就是视图的名称。
该注解在 Spring3.* 版本引入,在处理异常时标注在方法级别,代表当前方法处理的异常类型有哪些。
全局异常处理类1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @ControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class) @ResponseBody public ResultInfo exceptionHandler(Exception e){ ResultInfo resultInfo = nwe ResultInfo(); resultInfo.setCode(300); resultInfo.setMsg("操作失败");
if(e instanceof ParamsException){ ParamsException ex = (ParamsException) e; resultInfo.setMsg(ex.getMsg()); resultInfo.setCode(ex.getCode()); }
return resultInfo; } }
|
如果想捕捉特定的异常,只需要将 value 的值指定为你想捕捉的异常类就行,如下面代码只捕捉未登录异常(自定义异常)
特定异常处理类1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @ControllerAdvice public class NoLoginExceptionHandler { @ExceptionHandler(value = NoLoginException.class) @ResponseBody public ResultInfo exceptionHandler(NoLoginException e){ ResultInfo resultInfo = nwe ResultInfo(); resultInfo.setCode(401); resultInfo.setMsg("您还没有登录");
return resultInfo; } }
|
数据校验 Validation
日常开发中,对于前端提交的表单,后台接口接收数据后,为了程序的严谨性,通常后端会加入业务参数的合法性校验操作来避免程序的非技术性bug。对于客户端提交的数据验证,SpringBoot通过 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 模块中添加了自动校验,并将校验信息封装进了特定的类中。
环境配置
实现参数校验,程序必须引入 spring-boot-starter-validation
依赖,在引入 spring-boot-starter-web
依赖时,该模块会自动依赖 spring-boot-starter-validation
,所以程序中 spring-boot-starter-web
会一并依赖 spring-boot-starter-validation
到项目中(从SpringBoot2.3开始需要单独引入)。
校验相关注解
注解 |
功能 |
@AssertFalse |
可以为null,如果不为null的话必须为 false |
@AssertTrue |
可以为null,如果不为null的话必须为 true |
@DecimalMax |
设置不能超过最大值 |
@DecimalMin |
设置不能超过最小值 |
@Digits |
设置必须是数字且数字整数的位数和小数的位数必须在指定范围内 |
@Future |
日前必须在当前日前的未来 |
@Past |
日前必须在当前日前的过去 |
@Max |
最大不能超过此最大值 |
@Min |
最大不能小于此最小值 |
@NotNull |
不能为null,但可以为空 |
@Pattern |
必须满足指定的正则表达式 |
@Size |
集合、数组、map等的Size()值必须在指定范围内 |
@Email |
必须是email格式 |
@Length |
长度必须在指定范围内 |
@NotBlank |
字符串不能为null,且trim()后也不能等于空 |
@NotEmpty |
不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于空 |
@Length |
长度必须在指定范围内 |
@Range |
值必须在指定范围内 |
@URL |
必须是一个URL |
校验注解使用
- 实体类参数校验
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class User{ 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; }
|
- 接口方法形参 使用
@Valid
注解表示该参数启用参数校验。
1 2 3
| public void saveUser(@Valid User user){ }
|
- 异常捕捉
参数校验抛出的是 BindException
异常,可以在全局异常捕获中获取其信息,如下面的代码
1 2 3 4
| if(e instanceof BindException){ BindException be = (BindExcepiton) e; resultInfo.setMsg(be.getBindingResult().getFieldError().getDefaultMessage()); }
|