1.1 子路由
路由占位符
<!-- 定义主页面结构-->
<el-main>
<!-- 定义路由展现页面-->
<router-view></router-view>
</el-main>
子路由定义语法
1.2 用户列表实现
1.2.1 页面JS分析
- 生命周期函数
//利用钩子函数实现数据查询
mounted(){
this.getUserList()
}
- 获取数据JS
async getUserList(){
const {data: result} = await this.$http.get('/user/list',{
params: this.queryInfo
})
if(result.status !== 200) return this.$message.error("用户列表查询失败")
this.userList = result.data.rows
this.total = result.data.total
console.log("总记录数:"+this.total)
}
1.2.2 接口文档
1.2.3 编辑分页对象PageResult
package com.jt.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class PageResult {
private String query; //查询数据
private Integer pageNum; //页数
private Integer pageSize; //条数
private Long total; //总数
private Object rows; //查询结果
}
1.2.4 前后端数据关联
说明:在后端服务器需要额外的查询总数total及分页的结果rows
1.2.4 编辑UserController
1.3 用户状态修改
1.3.1 业务说明
- 说明:当点击开关,则实现状态信息的修改。status=true/false
- 请求路径
http://localhost:8091/user/status/1/false
1.3.2 VUE.JS 作用域插槽
<template slot-scope="scope">
<el-switch v-model="scope.row.status" @change="updateStatus(scope.row)"
active-color="#13ce66" inactive-color="#ff4949">
</el-switch>
</template>
1.3.3 页面JS分析
- 前端模板
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-switch v-model="scope.row.status" @change="updateStatus(scope.row)"
active-color="#13ce66" inactive-color="#ff4949">
</el-switch>
</template>
</el-table-column>
- 添加修改事件
async updateStatus(user){
//实现用户状态修改 注意使用模版字符串 ES6中提出的新用法 ${key}
//const {data: result} = await this.$http.put('/user/status/'+user.id+'/'+user.status)
const {data: result} = await this.$http.put(`/user/status/${user.id}/${user.status}`)
if(result.status !== 200) return this.$message.error("用户状态修改失败!")
this.$message.success("用户状态修改成功!")
},
1.3.4 接口文档
- 请求路径 /user/status/{id}/{status}
- 请求类型 PUT
- 请求参数: 用户ID/状态值数据
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
id | Integer | 用户ID号 | 不能为null |
status | boolean | 参数状态信息 | 不能为null |
- 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
1.3.5 后端Java
- Controller
/**
* 需求: 根据ID修改状态
* URL: /user/status/{id}/{status}
* @param user
* @return
*/
@PutMapping("/status/{id}/{status}")
public SysResult updateUserStatus(User user) {
return SysResult.success(userService.updateUserStatus(user));
}
- Mapper
<update id="updateUserStatus">
update user set status=#{status} where id=#{id}
</update>
1.4 用户新增
1.4.1 页面
<!-- 编辑用户新增对话框 visible.sync 控制对话框的显示-->
<el-dialog title="添加用户" :visible.sync="dialogVisible" width="50%" @close="closeDialog">
<!-- 定义用户提交表单数据-->
<el-form :model="addUserModel" :rules="rules" ref="addUserRef" label-width="100px" class="demo-ruleForm">
<el-form-item label="用户名" prop="username">
<el-input v-model="addUserModel.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="addUserModel.password" type="password"></el-input>
</el-form-item>
<el-form-item label="密码确认" prop="password2">
<el-input v-model="addUserModel.password2" type="password"></el-input>
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="addUserModel.phone"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="addUserModel.email"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addUserBtn">确 定</el-button>
</span>
</el-dialog>
1.4.2 JS
<el-button type="primary" @click="addUserBtn">确 定</el-button>
//校验用户数据 valid 表示校验是否通过
addUserBtn(){
this.$refs.addUserRef.validate(async valid => {
//如果校验成功 valid=true 如果失败 valid=false
//如果校验失败 则停止数据
if(!valid) return
const {data: result} = await this.$http.post('/user/addUser',this.addUserModel)
if(result.status !== 200) return this.$message.error("用户新增失败")
this.$message.success("用户新增成功")
//关闭对话框
this.dialogVisible = false
//重新获取用户列表
this.getUserList()
})
},
1.4.3 接口
1.4.4 后端
- Controller
/**
* 用户新增
* URL: /user/addUser
* 参数: JS对象经过浏览器解析变为JSON串
* {"username":"111","password":"222","password2":"222","email":"22@qq.com","phone":"13111111111"}
* 返回值: SysResult对象
* 对象转化为JSON @ResponseBody
* JSON转化为对象 @RequestBody 规则
*/
@PostMapping("/addUser")
public SysResult saveUser(@RequestBody User user){
userService.saveUser(user);
return SysResult.success();
}
- UserServiceImpl
/**
* 业务:实现业务数据封装
* 1.密码加密
* 2.设定默认状态
* 3.设定默认时间
*/
@Override
public void saveUser(User user) {
String password = user.getPassword();
String MD5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
//获取当前时间
Date date = new Date();
user.setPassword(MD5Pass).setStatus(true)
.setCreated(date).setUpdated(date);
userMapper.saveUser(user);
}
- UserMapper.xml
<!--实现user入库操作-->
<insert id="saveUser">
insert into user value (null,#{username},#{password},#{phone},#{email},#{status},#{created},#{updated})
</insert>
1.5 数据更新回显
1.5.1 页面 JS 分析
- 按钮
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>
</template>
- JS事件
async updateUserBtn(user){
this.updateDialogVisible = true
const {data: result} = await this.$http.get("/user/"+user.id)
if(result.status !== 200) return this.$message.error("用户查询失败")
this.updateUserModel = result.data
},
1.6 用户修改
1.6.1 前端
<span slot="footer" class="dialog-footer">
<el-button @click="updateDialogVisible = false" >取 消</el-button>
<el-button type="primary" @click="updateUser">确 定</el-button>
</span>
updateUser(){
//1.预校验数据
this.$refs.updateUserRef.validate(async valid => {
if(!valid) return this.$message.error("表单验证没有通过")
//根据接口文档要求封装数据
let user = {}
user.id = this.updateUserModel.id
user.phone = this.updateUserModel.phone
user.email = this.updateUserModel.email
const {data: result} = await this.$http.put(`/user/updateUser`,user)
if(result.status !== 200) return this.$message.error("用户修改失败")
this.$message.success("用户更新成功")
this.updateDialogVisible = false
this.getUserList()
})
},
1.6.2 后端
- UserController
@PutMapping("/updateUser")
public SysResult updateUser(@RequestBody User user) {
userService.updateUser(user);
return SysResult.success();
}
- UserMapper
@Update("update user set phone=#{phone},email=#{email},updated=#{updated} where id=#{id}")
void updateUser(User user);
1.7 用户删除操作
1.7.1 页面JS
<template slot-scope="scope">
<el-button type="danger" icon="el-icon-delete" size="small" @click="deleteUser(scope.row)"></el-button>
</template>
async deleteUser(user){
//1.消息确认框
const result = await this.$confirm('此操作将永久删除 '+user.username+', 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(error => error)
//如果确认 confirm 如果取消 cancel
if(result !== 'confirm'){
this.$message.info("删除取消")
}
const {data: result2} = await this.$http.delete(`/user/${user.id}`)
if(result2.status !== 200) return this.$message.error("删除失败")
this.$message.success("删除成功")
//重新加载 数据
this.getUserList()
}
2. 全局异常处理 / 返回错误信息
2.1 异常说明
将异常报错信息返回给用户
2.2 异常处理
@DeleteMapping("/{id}")
public SysResult deleteById(@PathVariable Integer id) {
//防止与MP方法冲突 业务方法最好添加业务名称
try {
userService.deleteUserById(id);
return SysResult.success();
}catch(Exception e) {
e.printStackTrace();
return SysResult.fail();
}
}
说明:
如果业务代码中添加了大量的 try - catch 则会导致业务复杂度变高,不便于维护。
2.3 Spring的全局异常处理机制
package com.jt.exception;
import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLException;
//1. 标识该类是全局异常处理机制,返回值都是JSON串
// 通知: AOP中的技术,解决特定问题
// 特点: 该异常处理机制,只拦截 Controller 层抛出的异常
@RestControllerAdvice
@ControllerAdvice
public class SystemExe {
/**
* 说明: 需要为全局异常定义一个方法
* 要求: 返回统一的业务数据 SysResult
* 拦截: 指定遇到某种异常实现 AOP 处理
*/
@ExceptionHandler({RuntimeException.class, SQLException.class})
public SysResult fail(Exception e) {
e.printStackTrace();
return SysResult.fail();
}
}
3. Spring事务控制
3.1 事务特性
- 原子性:事务要么同时成功,要么同时失败(不可分割)
- 隔离性:多个事务之间相互独立,互不干扰
- 一致性:保证数据的一致 ( 脏读 / 幻读 / 不可重复读 )
- 持久性:一旦事务提交,就应该持久化保存
3.2 Spring中默认的事务策略
说明:Spring整合Mybatis之后,业务层执行没有事务的支持,业务出现异常,Mysql也会正常提交
3.3 Spring添加事务
/**
* 问题: 出现异常时数据是否真的被删除
* 解决方案: @Transactional
* 作用:
* 1. 默认条件下,只拦截运行时异常
* 2. rollbackFor: 指定异常的类型回滚 rollbackFor = RuntimeException.class
* 3. noRollbackFor: 指定异常不回滚 noRollbackFor = RuntimeException.class
*/
// @Transactional(rollbackFor = RuntimeException.class)
@Transactional
@Override
public void deleteUserById(Integer id) {
// int a = 1/0;
userMapper.deleteUserById(id);
// int a = 1/0;
}
4 MybatisPlus
4.1 准备新测试环境
4.2 ORM知识回顾
核心概念:对象关系映射,以对象的方式操作数据库
mybatis回顾:Mybatis是半自动化的ORM映射框架
半自动:Sql是自己手写的,但是结果集映射是自动的
MybatisPlus:全自动的ORM映射框架,对Mybatis的扩展
4.3 MybatisPlus
4.3.1 MP介绍
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
4.3.2 导入Jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>Latest Version</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
4.3.3 MybatisPlus配置
#SpringBoot整合mybatisPlus
mybatis-plus:
#指定别名包
type-aliases-package: com.jt.pojo
#加载指定的xml映射文件
mapper-locations: classpath:/mybatis/mappers/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
4.3.4 Pojo类
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@TableName("user") //对象与表名映射
public class User implements Serializable {
@TableId(type = IdType.AUTO) //组件自增
/**
* 规则:
* 1.如果数据库中的字段与表中的属性名称一致,则可以省略不写@TableField
* 2.如果对象与表名一致,则名称可以省略
*/
// @TableField("id")
private Integer id;
private String name;
private Integer age;
private String sex;
}
4.5 SpringBoot测试API
需要在某个位置,直接从Spring容器中获取某个对象,该操作Spring提供了专门的单元测试注解
package com.jt.springboot_ssm;
import com.jt.mapper.UserMapper;
import com.jt.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
//只要@Test测试方法执行,则整个Spring容器启动,可以根据自身的需要实现依赖注入
//注意事项:该注解只能在测试类中使用,测试类的包路径必须在主启动类的同包及子包下
@SpringBootTest
class SpringbootSsmApplicationTests {
@Autowired
private UserMapper userMapper;
@Autowired
private UserService userService;
@Test
void contextLoads() {
// userMapper.insert();
}
}
4.6 MybatisPlus 工作原理
核心:以对象的方式操作数据库
调用步骤:
1. 用户执行userMapper.insert(user);
2. 根据继承关系 BaseMapper.inesrt(user);
3. MP在内部生成SQL之后交给Mybatis调用最终实现数据库操作
核心问题: MP是如何动态生成SQL语句的
例子:
Sql1:insert into user(字段名……) value(属性值……)
原理:
1. 用户调用接口方法 userMapper.insert(User)方法
2. 根据UserMapper的接口找到父级接口BaseMapper
3. 根据父级接口动态获取当前接口的泛型对象T
4. 根据泛型T 获取指定的注解@TableName(“demo_user”),之后获取表名demo_user
5. 根据泛型对象T,获取其中的属性,之后再找到属性的注解@TableField(“id”),之后再次获取注解的值, 即字段名称.
6. 根据字段名称,获取对应属性的值.
7. 根据Sql拼接 形成最终的可以执行的Sql.
8. MP将生成的Sql交给Mybatis执行入库操作.
4.6.1 MybatisPlus 入门案例
@Test
public void insertUser() {
User user = new User();
user.setId(null).setName("MybatisPlus").setAge(10).setSex("男");
userMapper.insert(user);
}
MybatisPlus 新增自动填充
@Data
@Accessors(chain=true)
public class BasePojo implements Serializable{
//新增操作时 自动填充
@TableField(fill = FieldFill.INSERT)
private Date created;
//新增和修改操作时自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updated;
}
4.7.1 根据Id查询数据
/**
* 学习技巧: MP设计思想!!!! 对象
* 查询Id=1的用户
*/
@Test
public void selectById() {
int id = 1;
User user = userMapper.selectById(id);
System.out.println(user);
}
4.7.2 根据name和sex查询数据
/**
* 查询name="大乔",sex="女"的用户
* 规则: 根据对象中不为null的属性进行业务操作
* 语法:
* 1.QueryWrapper条件构造器 动态拼接where条件
* 2.默认的关系连接符 and
* 例子:
* select * from demo_user where xx=xx and xx=xx
*/
@Test
public void selectByNS() {
User user = new User();
user.setName("大乔").setSex("女");
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
List<User> list = userMapper.selectList(queryWrapper);
System.out.println(list);
}
4.7.3 利用QueryMapper查询数据
/**
* 根据对象中不为null的属性进行查询
* 语法:
* 1.QueryWrapper 条件构造器 动态拼接where条件
* 2.默认的关系连接符 and
* 3.根据对象中部位null的属性进行业务操作
* 例子:
* select * from demo_user where xx=xx and xx=xx
*/
@Test
public void selectByNS() {
User user = new User();
user.setName("大乔").setSex("女");
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
List<User> list = userMapper.selectList(queryWrapper);
System.out.println(list);
}
/**
* 方式2:利用条件构造器,构建条件
* 说明:
* 1. eq =
* 2. gt >
* 3. lt <
* 4. ge (gteq) >=
* 5. le <=
* 6. ne !=
*/
@Test
public void selectByNS2() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "大乔")
.eq("sex", "女");
// .or()
List<User> list = userMapper.selectList(queryWrapper);
System.out.println(list);
}
/**
* 需求: 查询age>18岁的用户,并且sex=男.
*/
@Test
public void selectByAS() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",18)
.eq("sex","男");
List<User> list = userMapper.selectList(queryWrapper);
System.out.println(list);
}
4.7.4 like关键字
/**
* 需求: 查询name中包含"君",性别="女"
* Sql: where name like "%君%"
* 需求2: 查询name中以"君"结尾的,性别="女" like "%君"
*/
@Test
public void selectLike() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//queryWrapper.like("name","君") //"%君%"
queryWrapper.likeLeft("name","君") //"%君"
.eq("sex","女");
List<User> list = userMapper.selectList(queryWrapper);
System.out.println(list);
}
4.7.5 in关键字
/**
* 需求: 查询id=1,3,4,5的数据 并且按照年龄降序排列
* 规则: 基本类型有没有方法? 所以使用包装类型
* 面向对象开发
*/
@Test
public void selectIds() {
Integer[] ids = {1,3,4,5};
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id",ids)
.orderByDesc("age");
List<User> list = userMapper.selectList(queryWrapper);
System.out.println(list);
}
4.7.6 查询第一列字段
/**
* 需求:
* 只想获取第一列数据(主键),sex="女"
* 用法: .selectObjs(queryWrapper);
* 实际用途: 做关联查询时可以使用
*/
@Test
public void selectObjs() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("sex","女");
List list = userMapper.selectObjs(queryWrapper);
System.out.println(list);
}
4.7.7 动态Sql实现
/**
* 说明: 根据不为null的属性当做where 动态sql实现
* 需求: 查询age>18岁,sex=女的用户
* 参数说明:
* 1. boolean condition, true时,当前的条件才会成立
* false 该条件不拼接.
* 2.R column 字段信息
* 3.Object val 值
* 判断字符串API:
* Spring提供的API StringUtils.hasLength(sex);
*/
@Test
public void selectList2() {
Integer age = null;
String sex = "女";
//boolean flag = sex !=null && "".equals(sex);
boolean flag = StringUtils.hasLength(sex);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt(age!=null, "age",age )
.eq(flag,"sex",sex);
List<User> list = userMapper.selectList(queryWrapper);
System.out.println(list);
}
5 总结
5.1 注解总结
- 全局异常处理机子和
1. @RestControllerAdvice 标识全局异常处理
2. @ExceptionHandler({RuntimeException.class}) 拦截指定的异常类型 - 事务控制注解
@Transactional 控制事务 更新操作 - MP映射注解
1. @TableName(“demo_user”) //对象与表名映射
2. @TableId(type = IdType.AUTO) //主键自增
3. @TableField(“age”) //实现属性与字段映射 - 测试类注解
@SpringBootTest 可以引入注入spring容器中的对象之后进行单元测试