首先,非常感谢[增删改查] SpringBoot + MyBatis(注解版)一文,让我以极低的门槛上手了Spring Boot。而该博主还有一篇同样优秀的使用 Vue + ElementUI + Webpack + VueRouter 做后台管理、RESTful 交互让我在前段时间完成了一个简易的管理系统。
本文第一部分会以0基础的视角讲解Spring Boot的增删改查例子,第二部分会加入登录和分页功能,至此,一个简单却可用的后端就算搭建完成了。(后续会将已完成的管理系统的前端抽离出最基本的功能,与此后端系统联通)
一、创建项目
我使用的IntelliJ IDEA创建项目:
不清楚Group和Artifact的可自行搜索。
这里可以把必需的依赖选上,比如Spring Web Starter。但是即使没有没有选上也没有关系,后面可以通过编辑pom文件进行增删。一路next,就可以完成Spring Boot项目的创建了。
二、项目结构
- 第一部分是具体代码实现,暂且跳过
- 第二部分是资源文件,static文件夹下放入前端打包的资源文件,这样整个项目打包后就可以部署到服务器上了。application.properties是一些配置信息,内容如下:
#配置mysql
spring.datasource.url=jdbc:mysql://localhost:3306/sys?useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=amm123
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#日志输出sql
logging.level.com.cyq7on.crud.dao=debug
- 第三部分就是pom文件,里面是对整个项目的配置,其中就包括项目开发所用到的所有第三方依赖。刚开始无需了解pom文件的每个细节,只需要知道如何增加一个依赖即可。其中我用到了lombok,这是一个可以通过@Getter@Setter等注解免除自己手写setXXX和getXXX方法的工具包。
<?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 https://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.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cyq7on</groupId>
<artifactId>crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>crud</name>
<description>CRUD Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
三、代码
项目入口,启动整个项目。
这个类是用于规范、统一的返回数据给前端,最终返回的json如下:
{
"data":xxx,
"errorCode": 0,
"message": "成功"
}
errorCode返回错误码,0代表成功,message返回具体信息,data返回每个接口需要的数据,可能是一个对象,也可能是数组。
public class Result<T>{
private T data;
/**
* 错误码
*/
private int errorCode;
/**
* 信息
*/
private String message;
private Result() {
}
public static <T> Result<T> ok() {
return ok(null,"");
}
public static <T> Result<T> ok(T data) {
return ok(data,"成功");
}
public static <T> Result<T> ok(String message) {
Result<T> result = new Result<>();
result.errorCode = 0;
result.message = message;
return result;
}
public static <T> Result<T> ok(T value,String message) {
Result<T> result = new Result<>();
result.errorCode = 0;
result.data = value;
result.message = message;
return result;
}
public static <T> Result<T> fail() {
Result<T> result = new Result<>();
result.errorCode = -1;
return result;
}
public static <T> Result<T> fail(String message) {
return fail(-1,message);
}
public static <T> Result<T> fail(int errorCode) {
return fail(errorCode,"");
}
public static <T> Result<T> fail(int errorCode, String message) {
Result<T> result = new Result<>();
result.errorCode = errorCode;
result.message = message;
return result;
}
}
实体类。
@Getter
@Setter
public class User {
//主键由数据库自动生成
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String userName;
private String avatar;
private int age;
private String tel;
}
控制层,这个类里面都是直接用前端交互的接口,比如第一个getUsers,是获取用户列表的接口,使用Get方式提交,tel代表电话号码,可选,使用postman请求如下:
其中的pageNo和pageSize暂时忽略,这是后面增加的分页功能使用的参数。
@RestController
@RequestMapping("/user")
public class UserController {
//自动装配,告诉spring此对象需要注入一个UserService实例
@Autowired
private UserService service;
@GetMapping("/list")
public Result<List<User>> getUsers(@RequestParam(value = "tel", required = false) String tel) {
return Result.ok(service.getUsers(tel));
}
@PostMapping("/add")
public Result<User> addUser(@RequestBody User user){
return Result.ok(service.addUser(user));
}
@PostMapping("/update")
public Result<User> updateUser(@RequestBody User user){
return Result.ok(service.updateUser(user));
}
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable(value = "id") int id){
int i = service.deleteUser(id);
if (i > 0) {
return Result.ok("删除用户成功");
}else {
return Result.fail("用户不存在");
}
}
}
增加和更新接口类似,只是使用Post方式提交,注解也变为了@RequestBody,postman请求如下:
删除接口使用Delete方式提交,需要传入用户id,postman请求如下:
可以看到,UserController里面的核心逻辑都是通过UserService实现的,这就是service层。UserService是一个接口,其实现类是UserServiceImpl。(可能有的同学会问,干嘛要多定义一个接口,直接定义一个实现类不更简洁?推荐一篇很早的博文:DAO层,Service层,Controller层、View层)
public interface UserService {
List<User> getUsers(String tel);
User addUser(User user);
User updateUser(User user);
int deleteUser(int id);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper mapper;
@Override
public List<User> getUsers(String tel) {
if (StringUtils.isEmpty(tel)) {
return mapper.getUsers();
} else {
return mapper.getUser(tel);
}
}
@Override
public User addUser(User user) {
mapper.addUser(user);
return user;
}
@Override
public User updateUser(User user) {
User userById = mapper.getUserById(user.getId());
if (userById == null) {
throw new RuntimeException("用户不存在");
}
if (!StringUtils.isEmpty(user.getUserName())){
userById.setUserName(user.getUserName());
}
if (!StringUtils.isEmpty(user.getAvatar())){
userById.setAvatar(user.getAvatar());
}
if (!StringUtils.isEmpty(user.getAge())){
userById.setAge(user.getAge());
}
if (!StringUtils.isEmpty(user.getTel())){
userById.setTel(user.getTel());
}
mapper.updateUser(userById);
return userById;
}
@Override
public int deleteUser(int id) {
return mapper.deleteUser(id);
}
}
可以看到,service层里又用到了UserMapper,这就是Dao层,Dao层主要负责与数据库的交互。
@Repository
@Mapper
public interface UserMapper {
@Select("select * from user where id=#{id}")
User getUserById(int id);
@Select("select * from user")
List<User> getUsers();
@Select("select * from user where tel LIKE CONCAT('%', ${tel}, '%')")
List<User> getUser(String tel);
@Insert("insert into user(userName,avatar,age,tel) values (#{userName},#{avatar},#{age},#{tel})")
int addUser(User user);
@Update("update user set userName=#{userName},avatar=#{avatar},age=#{age},tel = #{tel} where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id=#{id}")
int deleteUser(int id);
}
这里我使用的是mybatis的注解方式,注意除@Select注解之外的注解都只能返回int值,表示受影响的数据行数。
至此,我便自顶向下的介绍完了Spring Boot增删改查的实现。
四、登录和分页
在数据库中我新建了一个Admin表,代表管理员。同时,实体、控制层、Dao层和Service层也都新建了相应的类。
@RestController
@RequestMapping("")
public class AdminController {
@Autowired
private AdminService service;
@PostMapping("/login")
public Result<Void> login(@RequestBody Admin admin) {
int i = service.login(admin.getName(), admin.getPwd());
switch (i) {
case 0:
return Result.ok("登录成功");
case 1:
return Result.fail("用户名错误");
case 2:
return Result.fail("密码错误");
default:
return Result.fail();
}
}
}
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminMapper mapper;
@Override
public int login(String name, String pwd) {
Admin admin = mapper.getAdmin(name);
if (admin == null) {
return 1;
}
if (admin.getPwd().equals(pwd)) {
return 0;
}
return 2;
}
}
postman请求如下:
在pom文件中加入分页工具包依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
application.properties中增加配置:
# 分页合理化参数,当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页
pagehelper.reasonable=true
# 通过Mapper 接口参数来传递分页参数:分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值
pagehelper.support-methods-arguments=true
# 为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值
pagehelper.params=pageNum=pageNo
分页是获取用户列表的增强功能,给前端的data,除了原有的列表之外,至少还得有一个total字段,表示符合查询条件的总条数,故定义一个PageInfo类:
@Data
public class PageInfo<T> implements Serializable {
private long total;
private List<T> list;
public PageInfo() {
this.list = new ArrayList<>();
this.total = 0;
}
public PageInfo(List<T> list) {
if(list instanceof Page) {
Page<T> page = (Page<T>) list;
this.total = page.getTotal();
this.list = page.getResult();
}
}
// 以下都是一些方便构造实例的静态方法
public static <T> PageInfo<T> data(List<T> list) {
if(list == null) {
return new PageInfo<>();
}
return new PageInfo<>(list);
}
public static <T> PageInfo<T> data(List<T> list, long count) {
PageInfo<T> pageInfo = new PageInfo<>();
pageInfo.setList(list);
pageInfo.setTotal(count);
return pageInfo;
}
}
改造Service层的getUsers方法:
// 原来的
public List<User> getUsers(String tel) {
if (StringUtils.isEmpty(tel)) {
return mapper.getUsers();
} else {
return mapper.getUser(tel);
}
}
//改造后的
public PageInfo<User> getUsers(String tel, int pageNo, int pageSize) {
PageHelper.startPage(pageNo, pageSize);
List<User> list;
if (StringUtils.isEmpty(tel)) {
list = mapper.getUsers();
} else {
list = mapper.getUser(tel);
}
return PageInfo.data(list);
}
改造控制层的getUsers方法:
// 原来的
@GetMapping("/list")
public Result<List<User>> getUsers(@RequestParam(value = "tel", required = false) String tel) {
return Result.ok(service.getUsers(tel));
}
//改造后的
public Result<PageInfo<User>> getUsers(@RequestParam(value = "tel", required = false) String tel,
@RequestParam(value = "pageNo", defaultValue = "1") int pageNo,
@RequestParam(value = "pageSize", defaultValue = "20")int pageSize) {
return Result.ok(service.getUsers(tel,pageNo,pageSize));
}
其中pageNo表示当前页数,pageSize表示每页多少条数据,控制层默认值分别设置为了1和20。
One more thing
到目前为止,该系统毫无安全性可言,于是准备使用JWT+Shiro进行改造。
关于JWT概念推荐JSON Web Token 入门教程
新增的功能我不打算展开讲了,推荐Shiro + JWT + Spring Boot Restful 简易教程,我和他使用的框架不一样,但是思路是一致的。