单表增删改查设置
新增员工
1.需求分析设计
接口设计
-
根据产品原型,为属性添加限制(例如 uid唯一,手机号11位数字等)
-
方法post,路径/admin/employee
-
前端给后端是json格式传入,后端给前端是Result封装json为JAVA对象
项目约定:管理端请求前缀为/admin,用户端请求/user为前缀
数据库设计
- 数据库字段以及约束
2.代码开发
-
根据接口设DTO
也可以直接传给employee实体,但是当前端传来的json数据与实体中的属性差别较大,推荐使用DTO封装传来的属性
@Data public class EmployeeDTO implements Serializable{ private Long id; private String username;private String name;private String phone;private String sex; private String idNumber; } -
DTO的值赋值给实体
前提 属性名一致
BeanUtils.copyproperties(DTO,entity)其他实体属性,需手动设置
//设置密码 employee.setPassword(DigestUtils.md5DigestAsHex("123456".getbytes)); //getbytes是因为MD5是针对字节加密的,因此要进行转换
3.测试
1.接口文档(为主):
安全校验:设置拦截器(interceptor)获取http的header(即携带token的地方)
配置全局token:没有登陆,无法通过校验,配置token(拿到身份)进行测试
2.前后端联调
ngix登录,尝试添加员工,在数据库中看是否成功·
4. 完善功能
-
用户名重复异常信息未处理
描述:将重复员工信息进行注册报错500
解决:将控制台报错信息交给全局异常handler进行捕获,状态变为200,这里是将后台报错信息传给前面
注意:如果登录token过期了,会返回401,需要重新获取,重新配置全局token
@ExceptionHandler public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){ //Duplicate entry 'wu' for key 'idx_username' //目的为了提示xx已经存在 1.捕获这条信息2.以空格分割转数组元素,读取第三个数组,result对象传出去 String message = ex.getMessage(); //如果是此种异常处理办法 if(message.contains("Duplicate entry" )){ String[] split=message.split(" "); String username = split[2]; String msg = username + MessageConstant.ALRERDY_LIVE; return Result.error(msg); } else { String msg = MessageConstant.UNKNOWN_ERROR; return Result.error(msg); } } } -
创建人和修改人设置成固定值了“serviceimpl里面的save方法”
描述:在新增用户时,修改者和创建者(当前登录账号)是默认值,需要改为动态获取
已知账号登陆时,需要先传给后端,后端生成JWT令牌,JWT令牌传给前端,前端每次请求后端都携带一个token并经过后端校验后执行操作。
controller里jwt令牌创建是包含用户id,因此可以通过解码token得到id,传导给impl.save方法.
controller–>service需要通道Threadlocal,因为每次前端传来请求就会分配一个线程,其中controller/intercreptor/service在同一个线程中,可以通过线程的局部变量ThreadLocal传导中间变量。
安全隐患:线程会复用,一定要在 finally(写在拦截器) 中 remove(),否则下个请求可能“捡到”上一个用户的上下文,使用标准的 Spring Security 配置:不用自己清理,SecurityContextPersistenceFilter 会清掉。
但如果你自己写 ThreadLocal(绕过 Spring Security),那就必须自己在 finally 里 remove()。//自己写 public static Long getUserId() { return userIdHolder.get(); } //用spring public static Long getUserId() { return ThreadLocal.get(); } /**在 Filter/Interceptor 中验证 JWT → 构造认证对象 → 放进 SecurityContext(封装的工具类本质上基于 ThreadLocal),然后在 Controller/Service 用 @AuthenticationPrincipal 或封装的 UserContext.get() 取即可。
分页查询
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//select *from limit 0,10
//mybatis 通过pagehelper 拦截器将其拆分为两句SQL执行
//此句记录当前进程的页面也页面条目
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
//必须返回page,sql语句执行,拦截器生效,开始改写SQL语句注入计数信息和分页信息
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
Long total = page.getTotal();
//这里使用实体而非传输对象(DTO)是因为数据库返回的是实体,与上面的对应,不同层之间数据传输用对象
List<Employee> records = page.getResult();
return new PageResult(total, records);
}
给前端返回的日期以数组形式存在
解决·
·//方式一:在属性上加入注解,对日期进行格式化,JsonFonmat(pattern = "yyyy-MN-dd HH:mm: ss")
private LocalDateTime dpdateTime;
·//方式二:在WebMvcConfiguration中扩展Spring MVC的消息转换器,统一对日期类型进行格式化处理
s
**
*扩展mvc框架的消息转换器*@param converters
*l
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("开始扩展消息转换器...");
//创建—个消息转化器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpNessageConverter();//设置对象转换器,可以将ava对象转为json字符串
converter.setobjectMapper(new JacksonobjectMapper( ));
//将我们自己的转换器放入spring MVc框架的容器中
converters.add(o, converter) ;
知识补充
1.@Autowired:依赖注入
@Autowired
private EmployeeService employeeService;//Spring,请帮我把
EmployeeService这个对象准备好,并自动塞进(注入)这个employeeService变量里。”
| 项目 | 含义 |
|---|---|
@Autowired | 让 Spring 自动注入对象(依赖注入) |
EmployeeService | 你要注入的对象类型(接口 / 类) |
不用 new | 由 Spring 统一管理对象的创建与销毁 |
| 好处 | 解耦、可维护、方便测试与扩展 |
2.数据流完整流程(Controller → Service → Mapper → DB)
前端给后端传数据的时候,数据以DTO的形式存在,而后端和数据库传数据的时候,数据需要以实体的形式存在
前端发请求(JSON)
⬇
DTO(接收请求的数据)
⬇
Controller 接收 DTO
⬇
Service 层处理业务逻辑(Service(接口定义,@Autowired)—>Serviceimpl(具体实现))
⬇
Entity / PO(对应数据库表)
⬇
Mapper (操作数据库的接口无需 Impl,MyBatis 自动实现)
⬇数据库(真实存储数据)
⬇
Entity(查询结果封装成对象)
⬇
VO(封装成返回前端的视图对象)
⬇
Controller 封装 Result 返回给前端
3.DTO与实体的关系,mapper与持久层的关系
DTO是前端传给后端的,后端给数据库(持久层)需要使用mapper
4.Mapper与Service关系
| 层级 | 是否要自己写实现类 | 实现逻辑写在哪里 | 谁来完成“接口 + 实现”的拼接 |
|---|---|---|---|
| Service | ✅ 是(serviceimpl) | 写在 ServiceImpl 类 | 自己写代码 |
| Mapper | ❌ 否 | 写在 XML 或注解中 | ✅ MyBatis 自动拼接 |
//MAPPER流程
//serviceimpl.java
@Autowired
public Employeemapper employeemapper//注入
//将DTO转为实体对象
//相同属性赋值
BeanUntils.copyproperties(DTO,ENETITY)
//其他属性手动赋值,注意常量
//转换完成插入到数据库mapper
employeemapper.insert(entity)
//Employeemapper.java
//在这里定义insert方法的实现
//这里使用注解实现(单表增删改查较为简单),复杂的可写入XML实现
@Insert("SQL语句")
void insert(Employee employee);
//这里方法为空是约定俗成的,因为mybatis会自动执行注解里面的东西,mapper里面只是接口定义所以直接为空即可,mybatis会把二者自动组合在一起,
-
拓展XML写mapper定义办法(适合复杂场景开发)
//接口定义 @Mapper public interface EmployeeMapper { void insert(Employee employee); Employee getById(Long id); } //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.xxx.mapper.EmployeeMapper"> <!-- 插入操作 --> <insert id="insert" parameterType="Employee"> INSERT INTO sky_take_out.employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user, status) VALUES (#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status}) </insert> </mapper> //在application.yaml配置mybatis mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.xxx.entity -
idea连接数据库
右侧边栏datasource→mysql→下载相关配置【下载失败使用下面的problem里的修复功能会自动扫描缺失的jar包】→输入数据库用户名和密码→左下角测试连接→连接成功apply,完成连接,右键点击连接名称,选择想要连接的数据库。(默认是·default)
IDEA自动提示sql语句设置
setting-Language–SQL dialects两个都设置为mysql,返回sql语句:alt+enter选择language injection选择mysql。
7.常见http状态码含义
| 状态码 | 中文解释 | 出现原因 | 谁的错 |
|---|---|---|---|
| 200 | 成功 | 数据或操作正常 | ✅双方都正常 |
| 401 | 未认证 | Token 问题/权限不足 | ❌客户端问题 |
| 500 | 服务器错误 | 代码或数据库异常 | ❌服务端问题 |
8.ThreadLocal
ThreadLocal并不是一个Thread,而是Thread的局部变量。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
使用方法
txt
ThreadLocal常用方法:
. public void set(T value)设置当前线程的线程局部变量的值.public T get() 返回当前线程所对应的线程局部变量的值
. public void remove() 移除当前线程的线程局部变量
一般被封装起来使用
9.PageHelper与SQL
startPage 在 Service 里调用,SQL 在 Mapper 里执行,两个文件需要通信,所以用 ThreadLocal 作为它们的共同变量,和前面的controller要和service通信一样,通过threadlocal
- pagehelper在mybatics启动时自动注册为拦截器。识别到startpage就记录当前threadlocal page,pagesize
- 当执行sql语句(select all)时 ,自动拦截变成1.select all 2.limit offset pagesize
如果不通过pagehelper,也可以自己在mapper里面写SQL语句,等效
SELECT COUNT(*) FROM employee WHERE name LIKE CONCAT('%', #{name}, '%');
SELECT * FROM employee WHERE name LIKE CONCAT('%', #{name}, '%') LIMIT #{offset}, #{pageSize};
308

被折叠的 条评论
为什么被折叠?



