若依开发笔记
若依项目开发笔记(二期开发笔记)
开发前的碎碎记
springboot二期开发笔记
这个标题记录我二期开发过程中的问题。
问题1:
数据库怎么还会有数据,明明不是我的本地连接啊?
代码里面数据库配置是老哥的远程mysql服务器连接ip,所以我可以连接上
问题2:
我明明在navicat里面输的是老哥的电脑的ip地址,可为什么访问不到dict这个数据库?
你个笨蛋,mysql学创建用户,授权用户的时候全忘了?mysql是一个关系型数据库管理系统,注意,它本身就是一个系统软件,运行在你的电脑上,然后你的电脑就是一台MYSQL服务器。关系指的是表与表之间的关系,联系;数据库管理系统指的是它可以管理多个数据库,也就是你在navicat里面创建的多个数据库。
然后MYSQL分为客户端和服务端,mysql服务器可以创建多个用户,默认root本身就有且是超级管理员。多个用户从客户端访问,也就是navicat访问,别人访问你的mysql服务器,需要输入你给人家创建的用户和密码,然后创建的时候设置有权限,什么只能看到特定数据库什么的。
问题3:
感觉又对服务器是什么产生疑问了,什莫事服务器,什么是客户端?
首先明确两点:(1)服务器的本质是提供服务。(2)服务器是软硬结合体
所以:你的电脑是一台硬件,mysql是服务器软件,然后你的电脑运行mysql服务器软件后,就可以被称作mysql服务器,给客户端提供服务,客户端可以是你电脑上的navicat或者他人电脑上的navicat。
然后查看你的电脑的ip地址,一般就是wifi的ip
客户端的区分:客户端设备和客户端程序
注意术语和专业的区别
问题4:
拓展延申java中的服务器软件有哪些?
问题五
端口号是干什么的?我修改端口号怎么知道这个端口号有没有有效?
1. 端口的基本概念
端口号:端口号是一个 16 位的数字,范围是 0 到 65535。它用于标识一台计算机上运行的特定服务或应用程序。
IP 地址 + 端口号:IP 地址用于标识网络中的一台设备,而端口号用于标识这台设备上运行的特定服务。例如:
192.168.1.100:80 表示 IP 地址为 192.168.1.100 的设备上的 HTTP 服务。
192.168.1.100:3306 表示同一设备上的 MySQL 数据库服务
问题6
怎么设计端口号有效?怎么检查端口是否被占用?
1. 确认端口号是否有效
端口号是一个 16 位的数字,范围是 0 到 65535。根据 IANA 的规定,端口号分为以下几类:
0-1023:系统保留端口,通常用于常见的系统服务(如 HTTP、FTP 等),需要管理员权限才能绑定。
1024-49151:注册端口,通常用于用户应用程序(如 Tomcat、MySQL 等)。
49152-65535:动态或私有端口,通常用于临时连接。
建议:
选择 1024 到 49151 之间的端口号,避免使用系统保留端口。
确保端口号未被其他程序占用。
2. 检查端口是否被占用
在设置端口号之前,需要确认该端口是否已被其他程序占用。以下是检查方法:
方法 1:使用 netstat 命令
打开命令提示符(Win + R,输入 cmd,回车)。
运行以下命令:
cmd
netstat -ano | findstr :端口号
例如,检查端口 8080:
cmd
netstat -ano | findstr :8080
如果输出结果为空,说明该端口未被占用;如果有输出,说明端口已被占用。
方法 1:使用 netstat 和 findstr(Windows)
打开命令提示符(Win + R,输入 cmd,回车)。
使用 netstat -ano 命令列出所有端口,然后通过 findstr 过滤指定范围。 例如,检查端口范围 8000 到 9000:
cmd
netstat -ano | findstr /R ":800[0-9] :80[1-9][0-9] :9000"
解释:
:800[0-9] 匹配 8000 到 8009。
:80[1-9][0-9] 匹配 8010 到 8099。
:9000 匹配 9000。
问题7
为什么我这数据库创建@’%‘类型的用户后无法登录mysql本地服务器?
原来是匿名用户的问题,把匿名用户删除就行了。
参考文章:
额外加一点用户创建的指令
##创建用户并设置密码和访问地址
CREATE USER 'yzh01'@'%' IDENTIFIED BY '123';
##授予用户对某个数据库的所用权限
GRANT ALL PRIVILEGES ON mybatis.* TO 'yzh01'@'%';
GRANT ALL PRIVILEGES ON springboot.* TO 'yzh01'@'%';
##授予权限后必须刷新权限生效
FLUSH PRIVILEGES;
问题8
rbac开发前,我想找一个工程开始做,经过对比数据库的表的分析。
1.jichu-master(springboot里面的)———-淘汰(原因:数据库的表不一样,是一个杂例子)
2.jichu-master(mybatis里面最先发的)——淘汰(原因:rbac杂表,是一个杂例子)
3.shiro—————-(保存)
shiro居然是Apach的一个安全框架的名字,这个项目刚好使用了shiro框架,用的springboot+mybatis的框架。
shiro是一个相对完整的网站项目,主要用它:
- 生成代码功能对mybatis单表的增删改查自动生成,生成的代码mybatis-plus也可以用。
- 后期有实力了再来看他项目是怎么写的。
4.mybatis-plus的自动生成三层架构用到一个mybatisplus插件。
5.老哥架构师官网——保存
http://124.221.43.118:8888/system/user
这个是老哥架构师管理系统的官网,是一个教学项目,是对shiro项目的功能完善。我没有源码
目前主要用他的页面和数据库练业务。
1 环境配置
导入的jichumaster123里面的pom坐标,另外加一下springboot起步依赖的坐标。
2 springboot_demo03笔记(springboot整合mybatis)
第一次整合mybatis,jsp三层开发练习记录(收费的单表)
2.1 笔记
1.
注意:
(1)创建完springboot项目要在yml文件下配置一下数据源信息。
(2)把生成的数据源信息删除,否则字符会导致错误。
(3)把生成的模板类删除,要不然模板类的包路径需要自己移动。
2.
记录一下踩坑经过
1.springboot是不支持jsp的,所以需要在pom里面导入四个坐标,
2.之后直接在main目录下建立web文件夹就行,然后放jsp文件
3.在yml文件里配置前缀后缀。
4.注意:web文件夹里面的WEB-INF文件夹只能通过控制器类访问,直接在浏览器输入jsp地址无法访问。
5.然后如果你自己有jsp模板的话,不用把web文件夹添加到项目路径里,(添加这一步只是让idea知道只是web文件,然后可以给你提供
jsp模板,你自己有的话,不用添加也可以。
6.还有就是配置jsp的实时渲染方式:
# 添加项目路径和开启实时渲染jsp
servlet:
context-path: /springboot
jsp:
init-parameters:
development: true
7.注意yml里面的层级要对应好,我第一次就是datasource和mvc不在同一级。
8.最后一点最重要,不要乱添加目录为根资源啥的,乱添加web文件夹啥的,要不然环境很乱。
我第一次就是乱添加,导致最后把.idea和iml文件删除再次打开项目也不行,只能重新建立项目才可以。一定不要乱添加。
8.使用Apipost工具测试的笔记
(1)数据库配置里面的主机地址是哥的数据库地址,所以我可以连接到数据库,这是远程库,我说咋没有库也可以连接到。
(2)重点测试了@PathVariable和@RequestParam的区别,然后写法,笔记本里面记录了。
(3)RequestBody和Responsebody是把请求数据和响应数据封装为json格式,一般是多个请求数据对应一个实体类的属性的时候封装。
(4)@RestController=@Controller+@responsebody,所以@RestController自动会把响应数据封装为json格式。
2.2 如何封装数据到jsp页面
/*1.留下一个问题:如果用@controller,怎么把数据返回到页面上
1.在servlet中是借助request和response和session处理请求和封装数据的返回到jsp页面里面
2.springmvc是用这个ModelAndView类(找时间学一下ModelAndView这个类)*/
3 rbac业务开发
一个菜单对应多个菜单项,一个菜单项对应多个功能模块
3.1 梳理表结构和关系
4.2
1.第一步:梳理这几张表
tb_user:用户表
tb_role:角色表
tb_menu:菜单表
tb_user_role:用户角色关系表,多对多
tb_role_menu:角色菜单关系表,多对多
rbac权限模型:role-base-access-control 基于角色的访问控制
一个用户多个角色,一个角色在菜单中不同权限,当添加用户时候只需要赋予对应角色就关联了权限。
tb_dept:部门表
tb_post:岗位表
部门-用户:一对多 部门-岗位:一对多
tb_post_user:用户岗位关系表,多对多
3.2 部门管理
1.展示所有部门
页面点击用户管理模块树形结构显示所有部门。
2.掌握树结构的实现方法
/*此类用于构建树结构
* 1.创建map集合,存储所有树节点
* 注意方法返回类型还是TreeNode类型,是一个嵌套的节点,扩散后是一个树结构*/
/* 2.遍历传入的节点集合,放到map集合里面*/
/*···3.再次遍历节点集合,根据每个节点的id判断是否是根节点
* 是:把它付给root,初始化root是空的
* 否:找到其父节点,把他放到父节点的子结点中*/
/*笔记中的方法有些问题:
* 1.需要在他笔记的基础上多处理三个异常
* 要处理三个空指针异常
1.这里要判断一下父节点是不是为空
2.children要初始化,否则默认为null
3.root默认为null
2.测试中输出json格式,他的那种方法不行,需要转换基本类型或者map,list
* 使用这个方法
* // 使用 Jackson 序列化
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(tree);
System.out.println(json); */
4.3
1. if (parentNode != null) {
if (parentNode.getChildren() == null) {
parentNode.setChildren(new ArrayList<Dept>());
}
parentNode.getChildren().add(node);//这个地方一定要在children判断外面执行
第一个查询所有数据并树形展示后,发现,树形结构数据缺失,是因为 parentNode.getChildren().add(node);这个方法
应该在children外面执行,因为当父节点豪创科技第一次初始化后,后面再加入豪创科技的子节点里面直接跳过if,所以,不会再添加,
从而导致数据缺失
2.
/*lombok自动生成的toString方法默认展示所有数据,
* 这样会导致最后页面中数据全部展示,不符合要求,所以要自己自定义一下*/
说错了,嵌套形式自己没法修改,返回数据后让前端进行修改。
2.添加部门
问题1:
为什么除了填写的字段外,其他的插入后都自动生成了。我的插入怎么没有。
4.
Jackson 的作用
Jackson 是一个用于处理 JSON 的 Java 库,它主要完成以下两个任务:
将 JSON 字符串反序列化为 Java 对象(JSON → 对象)。
将 Java 对象序列化为 JSON 字符串(对象 → JSON)。
在 Spring Boot 中,Jackson 是默认的 JSON 处理库。
所以:@RequestBody和@ResponseBody底层都是依赖jackson进行处理的。
Date和localDateTime类型在用Apipost测的时候自动生成的日期和时间格式是yyyy-MM-dd HH:mm:ss这种,
但是jackson默认支持2025-04-04T21:16:09日期和时间中间带个T的形式,所以我们自己要配置自定义的日期格式
方法1:yml里面配置全局日期格式为这种不带T的,idea自动生成这种不带T的。
spring
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
方法2:实体类属性上面加注解
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
5.增加用户的时候,需要使用辅助方法查询用户是否存在,注意不能只通过名字查询,否则无法按树结构加入进去
select * from tb_dept where dept_name = #{deptName} and parent_id = #{parentId}
要按名字加上父节点id这样才能判断具体哪个节点里面是否存在,如果不加父节点id,默认就是整个表的数据,肯定存在。
3.查询和修改部门
4.5
删除部门功能
5.
/*下面这两个toAjax类是受保护类型,只能在同包下和继承这个类的子类可以访问。*/
/**
* 响应返回结果
*
* @param rows 影响行数
* @return 操作结果
*/
protected AjaxResult toAjax(int rows)
{
return rows > 0 ? success() : error();
}
/**
* 响应返回结果
*
* @param result 结果
* @return 操作结果
*/
protected AjaxResult toAjax(boolean result)
{
return result ? success() : error();
}
6.
前端传递的数据
如果前端未传递某个字段,后端接收到的值通常是 null。
如果前端传递了一个空字符串(例如用户未输入内容),后端接收到的值通常是 ''。
<if test="deptName != null and dept_name != ''">#{deptName}</if>
<if test="status != null and status != ''">#{status}</if>
这个if里面的test的属性是java中类的名称,别写成数据库的字段了,兄弟
7.
<where>
<if test="deptName != null and deptName != ''">and dept_name = #{deptName}</if>
<if test="status != null and status != ''">and status = #{status}</if>
</where>
兄弟,一直前端返回的数据出不来,原来是and和dept_name和status没加,兄弟,sql语句千万别忘了。
3.3 用户、角色、菜单管理
4.6
8.
idea又要重新破解了,我真服了,这是第三次需要重新破解了,这一次,点击插件后,输入激活码一直是无效。
我卸载又重新安装,再次还是不行,当我绝望之时,重新去那个提供工具的页面,重新下载了新的工具jetbrain,然后重新解压,就又好了,
就很玄学。这次要提前备份信息,以便下次下载还要重新配置。
如果下次再次重新破解,先下载最新的jetbtain,再操作,下次出现这种情况如果脚本文件执行后还不行
直接help-》Edit Custom VM Options,看看里面有没有那个脚本文件,如果没有,将那个脚本手动导入
9.
<if test="parentId != null">parent_id = #{parentId},</if>
更新的时候,输入parent_id为0的时候,数据库里面一直无法修改为0,但是其他数据可以修改,原来是parentId != ’‘空字符串限制了,
需要将这个限制删去才可以,mybatis框架会把传入的0视为'0'等价于空字符串
10.
<!--请求参数不为空校验-->
<!-- validation -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
================两个包都要引入,但是校验信息在日志里,没有打印到控制台和返回前端。留作一个问题======
4.7
用户表的操作
11.查询所有用户,包括其对应的部门信息
注意项:
- <resultMap>里面的字段是根据查询结果映射的,先查询再映射,所以查询的别名要记得及时更改。
- <resultMap>里面只写你查询结果中的映射即可,没写的,springboot不会映射,他只映射,你resultMap里面的字段。
12.如果create_time是Date类型,不要在<if>标签中直接与String类型的值进行比较。
<if test="createTime != null and createTime != ''">and u.create_time = #{createTime}</if>
要把上面if标签里面的and createTime != ''去掉,不能是字符串类型
4.8
13.
public AjaxResult(Type type, String msg, Object data)
{
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
if (data != null)
{
super.put(DATA_TAG, data);
}
}
springboot使用jackson库自动将return的数据转为json格式,json对象的内容就是构造器方法的内容。
今天学到的还是有知识的:
14.
添加用户,更新用户
用户-角色-岗位,
用户更新的时候,角色-用户关系表和岗位-用户关系表,都要更新
怎么更新:
建立UserPostMapper接口和UserRoleMapper,然后在里面添加删除和更新语句
userService处理类里面执行多条sql语句,就可以实现关联更新。
15.
- 注意Role的类名大小写要写对,避免错误
- tb_user表的主键user_id居然没有勾选自动递增,低级错误啊
- 第二次遇见这个错误了 insert into tb_user_post values (#{user_id}, #{post_id})
一直报错org.apache.ibatis.binding.BindingException: Parameter 'user_id' not found. Available parameters are [postId, userId, param1, param2]
为什么找不到这个userId原来还是#里面写属性的时候没有写到对应的属性名,真实不好写。
------------顶级破案了,-----
当mapper接口方法里面有多个参数时候mybatis默认用arg0,arg1,param1,param2,来解析参数,所以我的postId一直识别不出来,
所以我加上@Param注解之后才可以成功识别,这就是旧知识的一样啊。
16.
<update id="updateUser">
update tb_user
<trim prefix="set" suffixOverrides=",">
<if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
<if test="phonenumber != null and phonenumber != ''">phonenumber = #{phonenumber},</if>
<!-- <if test="userName != null and userName != ''">user_name = #{userName},</if>-->
<if test="sex != null and sex != ''">sex = #{sex},</if>
<if test="deptId != null and deptId != ''">dept_id = #{deptId},</if>
<if test="email != null and email != ''">email = #{email},</if>
<if test="password != null and password != ''">password = #{password},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="remark != null and remark != ''">remark = #{remark},</if>
</trim>
where user_id = #{userId}
</update>
这个用户修改的动态sql注意,,逗号一定是后缀的去掉,可别写成前缀了,这个地方注意啊,兄弟。
17
@Override
@Transactional(rollbackOn = Exception.class)//保证所有sql要么都成功,要么都回滚
public Boolean isUpdateUser(User user, Integer postId, Integer roleId) throws Exception {
int row = userMapper.updateUser(user);
Integer userId = user.getUserId();
if (row > 0) {
int i = userPostMapper.updateUserPostId(userId, postId);
/*这个地方注意,原来我自己写的有问题,执行关系表的插入和更新语句不用return,要不然if--else后面无法执行更新语句了
* 这直接两种情况包圆了。*/
if (i <= 0) {
throw new Exception("更新用户岗位关系表错误");
}
int j = userRoleMapper.updateUserRoleId(userId, roleId);
if (j <= 0) {
throw new Exception("更新用户角色表错误");
}
return true;
} else {
return false;
}
}
错误:一直可以插入但就是无法修改用户角色表的角色id,原来是这个地方逻辑有问题啊!
总结一下目前进度
-------------------------------------基本完成-----------------------------
3.4dept
增删改查list
list:难点在于树结构
删:要不要删除user 表中响应dept id的数据呢?
-------------------------------------基本完成-----------------------------
3.1 user -done
增删改查list
增:要有相关的role list和post list- 更新user role表,更新user post表
查:要有相关的role和post - 查询user role表拿到role list,查询user post表拿到post list
改:还要改role和post的关系表
list:要有dept的name
删:还要删除user role和user post里user相关数据
user表还差一个删除和相关的查询
4.9号明天开menu菜单表,感受多个根节点的代码奇妙之处。
4.9
接着写user表的删除和查询
18.
调用查询用户根据条件的时候,还是响应体的data里面没有数据,原来是创建时间是默认生成的,我只输入一个名字,但是没有注意到
创建时间也给我传过去了,所以这个创建时间没有对应的用户,然后创建时间别删错,不是关联属性的创建时间,不删直接为空也可以找到
19.
升级这个查询用户根据条件的方法,查找用户,部门名字,岗位名字,角色名字都给我出来
六张表连接就行了
20.
<if test="userName != null and userName != ''">and u.user_name like concat('%', #{userName}, '%')</if>
模糊查询用concat函数来实现,然后放到两个%之间标识,查询含用户输入的所有用户信息。
21.
总结一个很傻逼的问题。MybatisX这个插件bug很大啊,我<collection>标签里面的MenuId属性一直爆红,搞得我都自我怀疑不是映射外表
主键了,没想到在mybatis_demo04这个项目里面也爆红,原来是这个插件导致的,关闭就好了。
后来我又手动去官网下载了低版本用,1.6.1,可以正常使用了,但是高亮那个错误还是不行。
22.
再补充一个响应数据没有外表集合的数据的原因:
<collection>标签里面的column字段没有用查询结果的别名,而是用原来的字段。
23.
再总结一个问题:
我测试角色的添加方法时候一直报500错误,于是心生一计,在查询用用户是否存在的下面sout这个查询结果,
果真发现,我输入新用户也有查询结果,自已一看查询结果居然是所有角色数据,然后我去sql语句里面看查询语句,发现,
select r.role_id rid, r.role_name rname, r.role_key rk, r.role_sort rss, r.status rs, m.menu_id mid, m.menu_name mname
from tb_role r
join tb_role_menu rm
on r.role_id = rm.role_id
join tb_menu m
on rm.menu_id = m.menu_id
sql语句只有查询,没有限制条件,这就导致我isaddRole一直返回false。
加上限制条件后,执行查询条件查询成功但是没有数据,又是一种没有数据的情况,补上,之所以没有数据
是因为角色表的这个角色和角色菜单关系表,菜单表没有共同的数据,这就导致sql连接后查出来的数据为空。
24.
写了这么多增删改查,有个困惑?
既然你xml里面的sql语句语法有错误,直接程序中断抛出异常了,我在service层判断row>0有什么意义?
row>0是在sql语法正确的情况下,编译可以通过,但当时数据库里面添加的主键冲突,删除的数据不存在,查询的数据不存在等情况导致的row<=0
所以还是有意义的。第二个困惑?
异常抛出程序会中断吗?我记得不会中断?
如果一直抛出,到处理的最后一层还抛出,就像controller层,出现异常就会再controller层中断,抛出后面的语句都不会执行,service层
抛出后面的语句也不会执行,直接抛出到coontroller层,只有再controller层catch捕获后,后面的语句才可以执行,不过业务逻辑不完整了,
相比于程序直接中断后面不执行,这就是区别。
25.
再补充一个congtroller层data没数据的情况
select里面少了parent_id,和menu_id,因为service层是根据这两个id来构建数结构的,所以不能少,展示是前端的事,后端不管。
<!-- List<Menu> selectAllMenu();-->
<select id="selectAllMenu" resultMap="selectAllMenuResult">
select menu_id, parent_id, menu_name, order_num, path, menu_type, perms
from tb_menu
</select>
<resultMap id="selectAllMenuResult" type="Menu">
<id column="menu_id" property="menuId" />
<result column="menu_name" property="menuName" />
<result column="order_num" property="orderNum" />
<result column="path" property="path" />
<result column="menu_type" property="menuType" />
<result column="perms" property="perms" />
</resultMap>
</mapper>
26.
<select id="selectAllMenu" resultMap="selectAllMenuResult">
select menu_id, parent_id, menu_name, order_num, path, menu_type, perms
from tb_menu
</select>
<resultMap id="selectAllMenuResult" type="Menu">
<id column="menu_id" property="menuId" />
<result column="menu_name" property="menuName" />
<result column="order_num" property="orderNum" />
<result column="path" property="path" />
<result column="menu_type" property="menuType" />
<result column="perms" property="perms" />
</resultMap>
注意这个地方,resultMap里面我没有配置parent_id到parentId的显式映射,到那时parentId还是可以传到service层进行
树结构的构造,说明这个值是有的,mybatis无法实现自动驼峰映射,除非显式配置,但是我用的是mybatis-plus,默认启用了驼峰映射
27.
List<Menu> menus = menuMapper.selectMenuByCondition(menu);
if (menus.isEmpty())
这个地方注意:不能够判断menus!=null,因为mybatis执行完sql语句默认返回空集合,所以null会导致永远为真。注意
28.
springboot中返回一个对象或者一个list集合,即使你在实体类里面不写toString方法,他也会返回json对象的形式。
json对象的内容是:
jackson 会序列化 User 对象的所有 非空字段(除非字段被标记为 transient 或使用 @JsonIgnore 注解忽略)。
字段的名称会直接作为 JSON 的键,字段的值会作为 JSON 的值。
然后总结三点:
1.如果不想让某个字段在json对象里面出现,这样
- 加transient private transient String password; // 不会被序列化
- 字段上方加@JsonIgnore注解
2.如果想让默认没有显式的null消失,这样
- 在yml文件中配置:spring.jackson.default-property-inclusion=non_null
3.如果想要显式的json键名不是属性名,这样:
- @JsonProperty("userId")
private Long id;
29. @ExceptionHandler(MethodArgumentNotValidException.class)
public AjaxResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.error("请求地址{},参数验证失败{}", requestURI, e.getObjectName(),e);
return AjaxResult.error(message);
}
String message = e.getBindingResult().getFieldError().getDefaultMessage();
这个地方是获取你的注解@NotNULL里面的自定义的消息,e是控制台爆出的异常
log.error("请求地址{},参数验证失败{}", requestURI, e.getObjectName(),e);
然后这个地方的参数,如果有e的话,控制台把异常堆栈信息爆出来,如果不加e,只有2025-04-11 20:35:23.094 ERROR 25884 --- [nio-8080-exec-1] c.y.s.exception.ExceptionAdvice : 请求地址/rbac/dept/addDept,参数验证失败dept
这个异常堆栈信息不会爆出来,如果没有全局统一异常信息处理,这个不为空的信息会出现在日志中,以warn的形式。
截至4.11,业务开发基本写完
3.4 rbac完整笔记
4.3
1.第一步:梳理这几张表
tb_user:用户表
tb_role:角色表
tb_menu:菜单表
tb_user_role:用户角色关系表,多对多
tb_role_menu:角色菜单关系表,多对多
rbac权限模型:role-base-access-control 基于角色的访问控制
一个用户多个角色,一个角色在菜单中不同权限,当添加用户时候只需要赋予对应角色就关联了权限。
tb_dept:部门表
tb_post:岗位表
部门-用户:一对多 部门-岗位:一对多
tb_post_user:用户岗位关系表,多对多
2.掌握树结构的实现方法
/*此类用于构建树结构
* 1.创建map集合,存储所有树节点
* 注意方法返回类型还是TreeNode类型,是一个嵌套的节点,扩散后是一个树结构*/
/* 2.遍历传入的节点集合,放到map集合里面*/
/*···3.再次遍历节点集合,根据每个节点的id判断是否是根节点
* 是:把它付给root,初始化root是空的
* 否:找到其父节点,把他放到父节点的子结点中*/
/*笔记中的方法有些问题:
* 1.需要在他笔记的基础上多处理三个异常
* 要处理三个空指针异常
1.这里要判断一下父节点是不是为空
2.children要初始化,否则默认为null
3.root默认为null
2.测试中输出json格式,他的那种方法不行,需要转换基本类型或者map,list
* 使用这个方法
* // 使用 Jackson 序列化
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(tree);
System.out.println(json); */
4.4
1. if (parentNode != null) {
if (parentNode.getChildren() == null) {
parentNode.setChildren(new ArrayList<Dept>());
}
parentNode.getChildren().add(node);//这个地方一定要在children判断外面执行
第一个查询所有数据并树形展示后,发现,树形结构数据缺失,是因为 parentNode.getChildren().add(node);这个方法
应该在children外面执行,因为当父节点豪创科技第一次初始化后,后面再加入豪创科技的子节点里面直接跳过if,所以,不会再添加,
从而导致数据缺失
2.
/*lombok自动生成的toString方法默认展示所有数据,
* 这样会导致最后页面中数据全部展示,不符合要求,所以要自己自定义一下*/
说错了,因该在映射文件里面修改字段。
3.
添加部门
xml标签里面if判断,!=null指的是数据库里面不写值默认返回null,
而!=‘’指的是用户表单不写值直接提交返回‘’。
一般都一起使用,细节先不管了。
4.
Jackson 的作用
Jackson 是一个用于处理 JSON 的 Java 库,它主要完成以下两个任务:
将 JSON 字符串反序列化为 Java 对象(JSON → 对象)。
将 Java 对象序列化为 JSON 字符串(对象 → JSON)。
在 Spring Boot 中,Jackson 是默认的 JSON 处理库。
所以:@RequestBody和@ResponseBody底层都是依赖jackson进行处理的。
Date和localDateTime类型在用Apipost测的时候自动生成的日期和时间格式是yyyy-MM-dd HH:mm:ss这种,
但是jackson默认支持2025-04-04T21:16:09日期和时间中间带个T的形式,所以我们自己要配置自定义的日期格式
方法1:yml里面配置全局日期格式为这种不带T的,idea自动生成这种不带T的。
spring
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
方法2:实体类属性上面加注解
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
5.增加用户的时候,需要使用辅助方法查询用户是否存在,注意不能只通过名字查询,否则无法按树结构加入进去
select * from tb_dept where dept_name = #{deptName} and parent_id = #{parentId}
要按名字加上父节点id这样才能判断具体哪个节点里面是否存在,如果不加父节点id,默认就是整个表的数据,肯定存在。
4.5
删除部门功能
5.
/*下面这两个toAjax类是受保护类型,只能在同包下和继承这个类的子类可以访问。*/
/**
* 响应返回结果
*
* @param rows 影响行数
* @return 操作结果
*/
protected AjaxResult toAjax(int rows)
{
return rows > 0 ? success() : error();
}
/**
* 响应返回结果
*
* @param result 结果
* @return 操作结果
*/
protected AjaxResult toAjax(boolean result)
{
return result ? success() : error();
}
6.
前端传递的数据
如果前端未传递某个字段,后端接收到的值通常是 null。
如果前端传递了一个空字符串(例如用户未输入内容),后端接收到的值通常是 ''。
<if test="deptName != null and dept_name != ''">#{deptName}</if>
<if test="status != null and status != ''">#{status}</if>
这个if里面的test的属性是java中类的名称,别写成数据库的字段了,兄弟
7.
<where>
<if test="deptName != null and deptName != ''">and dept_name = #{deptName}</if>
<if test="status != null and status != ''">and status = #{status}</if>
</where>
兄弟,一直前端返回的数据出不来,原来是and和dept_name和status没加,兄弟,sql语句千万别忘了。
4.6
8.
idea又要重新破解了,我真服了,这是第三次需要重新破解了,这一次,点击插件后,输入激活码一直是无效。
我卸载又重新安装,再次还是不行,当我绝望之时,重新去那个提供工具的页面,重新下载了新的工具jetbrain,然后重新解压,就又好了,
就很玄学。这次要提前备份信息,以便下次下载还要重新配置。
如果下次再次重新破解,先下载最新的jetbtain,再操作,下次出现这种情况如果脚本文件执行后还不行
直接help-》Edit Custom VM Options,看看里面有没有那个脚本文件,如果没有,将那个脚本手动导入
9.
<if test="parentId != null">parent_id = #{parentId},</if>
更新的时候,输入parent_id为0的时候,数据库里面一直无法修改为0,但是其他数据可以修改,原来是parentId != ’‘空字符串限制了,
需要将这个限制删去才可以,mybatis框架会把传入的0视为'0'等价于空字符串
10.
<!--请求参数不为空校验-->
<!-- validation -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
================两个包都要引入,但是校验信息在日志里,没有打印到控制台和返回前端。留作一个问题======
4.7
用户表的操作
11.查询所有用户,包括其对应的部门信息
注意项:
- <resultMap>里面的字段是根据查询结果映射的,先查询再映射,所以查询的别名要记得及时更改。
- <resultMap>里面只写你查询结果中的映射即可,没写的,springboot不会映射,他只映射,你resultMap里面的字段。
12.如果create_time是Date类型,不要在<if>标签中直接与String类型的值进行比较。
<if test="createTime != null and createTime != ''">and u.create_time = #{createTime}</if>
要把上面if标签里面的and createTime != ''去掉,不能是字符串类型
4.8
13.
public AjaxResult(Type type, String msg, Object data)
{
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
if (data != null)
{
super.put(DATA_TAG, data);
}
}
springboot使用jackson库自动将return的数据转为json格式,json对象的内容就是构造器方法的内容。
今天学到的还是有知识的:
14.
添加用户,更新用户
用户-角色-岗位,
用户更新的时候,角色-用户关系表和岗位-用户关系表,都要更新
怎么更新:
建立UserPostMapper接口和UserRoleMapper,然后在里面添加删除和更新语句
userService处理类里面执行多条sql语句,就可以实现关联更新。
15.
- 注意Role的类名大小写要写对,避免错误
- tb_user表的主键user_id居然没有勾选自动递增,低级错误啊
- 第二次遇见这个错误了 insert into tb_user_post values (#{user_id}, #{post_id})
一直报错org.apache.ibatis.binding.BindingException: Parameter 'user_id' not found. Available parameters are [postId, userId, param1, param2]
为什么找不到这个userId原来还是#里面写属性的时候没有写到对应的属性名,真实不好写。
------------顶级破案了,-----
当mapper接口方法里面有多个参数时候mybatis默认用arg0,arg1,param1,param2,来解析参数,所以我的postId一直识别不出来,
所以我加上@Param注解之后才可以成功识别,这就是旧知识的一样啊。
16.
<update id="updateUser">
update tb_user
<trim prefix="set" suffixOverrides=",">
<if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
<if test="phonenumber != null and phonenumber != ''">phonenumber = #{phonenumber},</if>
<!-- <if test="userName != null and userName != ''">user_name = #{userName},</if>-->
<if test="sex != null and sex != ''">sex = #{sex},</if>
<if test="deptId != null and deptId != ''">dept_id = #{deptId},</if>
<if test="email != null and email != ''">email = #{email},</if>
<if test="password != null and password != ''">password = #{password},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="remark != null and remark != ''">remark = #{remark},</if>
</trim>
where user_id = #{userId}
</update>
这个用户修改的动态sql注意,,逗号一定是后缀的去掉,可别写成前缀了,这个地方注意啊,兄弟。
17
@Override
@Transactional(rollbackOn = Exception.class)//保证所有sql要么都成功,要么都回滚
public Boolean isUpdateUser(User user, Integer postId, Integer roleId) throws Exception {
int row = userMapper.updateUser(user);
Integer userId = user.getUserId();
if (row > 0) {
int i = userPostMapper.updateUserPostId(userId, postId);
/*这个地方注意,原来我自己写的有问题,执行关系表的插入和更新语句不用return,要不然if--else后面无法执行更新语句了
* 这直接两种情况包圆了。*/
if (i <= 0) {
throw new Exception("更新用户岗位关系表错误");
}
int j = userRoleMapper.updateUserRoleId(userId, roleId);
if (j <= 0) {
throw new Exception("更新用户角色表错误");
}
return true;
} else {
return false;
}
}
错误:一直可以插入但就是无法修改用户角色表的角色id,原来是这个地方逻辑有问题啊!
总结一下目前进度
-------------------------------------基本完成-----------------------------
3.4dept
增删改查list
list:难点在于树结构
删:要不要删除user 表中响应dept id的数据呢?
-------------------------------------基本完成-----------------------------
3.1 user -done
增删改查list
增:要有相关的role list和post list- 更新user role表,更新user post表
查:要有相关的role和post - 查询user role表拿到role list,查询user post表拿到post list
改:还要改role和post的关系表
list:要有dept的name
删:还要删除user role和user post里user相关数据
user表还差一个删除和相关的查询
4.9号明天开menu菜单表,感受多个根节点的代码奇妙之处。
4.9
接着写user表的删除和查询
18.
调用查询用户根据条件的时候,还是响应体的data里面没有数据,原来是创建时间是默认生成的,我只输入一个名字,但是没有注意到
创建时间也给我传过去了,所以这个创建时间没有对应的用户,然后创建时间别删错,不是关联属性的创建时间,不删直接为空也可以找到
19.
升级这个查询用户根据条件的方法,查找用户,部门名字,岗位名字,角色名字都给我出来
六张表连接就行了
20.
<if test="userName != null and userName != ''">and u.user_name like concat('%', #{userName}, '%')</if>
模糊查询用concat函数来实现,然后放到两个%之间标识,查询含用户输入的所有用户信息。
21.
总结一个很傻逼的问题。MybatisX这个插件bug很大啊,我<collection>标签里面的MenuId属性一直爆红,搞得我都自我怀疑不是映射外表
主键了,没想到在mybatis_demo04这个项目里面也爆红,原来是这个插件导致的,关闭就好了。
后来我又手动去官网下载了低版本用,1.6.1,可以正常使用了,但是高亮那个错误还是不行。
22.
再补充一个响应数据没有外表集合的数据的原因:
<collection>标签里面的column字段没有用查询结果的别名,而是用原来的字段。
23.
再总结一个问题:
我测试角色的添加方法时候一直报500错误,于是心生一计,在查询用用户是否存在的下面sout这个查询结果,
果真发现,我输入新用户也有查询结果,自已一看查询结果居然是所有角色数据,然后我去sql语句里面看查询语句,发现,
select r.role_id rid, r.role_name rname, r.role_key rk, r.role_sort rss, r.status rs, m.menu_id mid, m.menu_name mname
from tb_role r
join tb_role_menu rm
on r.role_id = rm.role_id
join tb_menu m
on rm.menu_id = m.menu_id
sql语句只有查询,没有限制条件,这就导致我isaddRole一直返回false。
加上限制条件后,执行查询条件查询成功但是没有数据,又是一种没有数据的情况,补上,之所以没有数据
是因为角色表的这个角色和角色菜单关系表,菜单表没有共同的数据,这就导致sql连接后查出来的数据为空。
24.
写了这么多增删改查,有个困惑?
既然你xml里面的sql语句语法有错误,直接程序中断抛出异常了,我在service层判断row>0有什么意义?
row>0是在sql语法正确的情况下,编译可以通过,但当时数据库里面添加的主键冲突,删除的数据不存在,查询的数据不存在等情况导致的row<=0
所以还是有意义的。第二个困惑?
异常抛出程序会中断吗?我记得不会中断?
如果一直抛出,到处理的最后一层还抛出,就像controller层,出现异常就会再controller层中断,抛出后面的语句都不会执行,service层
抛出后面的语句也不会执行,直接抛出到coontroller层,只有再controller层catch捕获后,后面的语句才可以执行,不过业务逻辑不完整了,
相比于程序直接中断后面不执行,这就是区别。
25.
再补充一个congtroller层data没数据的情况
select里面少了parent_id,和menu_id,因为service层是根据这两个id来构建数结构的,所以不能少,展示是前端的事,后端不管。
<!-- List<Menu> selectAllMenu();-->
<select id="selectAllMenu" resultMap="selectAllMenuResult">
select menu_id, parent_id, menu_name, order_num, path, menu_type, perms
from tb_menu
</select>
<resultMap id="selectAllMenuResult" type="Menu">
<id column="menu_id" property="menuId" />
<result column="menu_name" property="menuName" />
<result column="order_num" property="orderNum" />
<result column="path" property="path" />
<result column="menu_type" property="menuType" />
<result column="perms" property="perms" />
</resultMap>
</mapper>
26.
<select id="selectAllMenu" resultMap="selectAllMenuResult">
select menu_id, parent_id, menu_name, order_num, path, menu_type, perms
from tb_menu
</select>
<resultMap id="selectAllMenuResult" type="Menu">
<id column="menu_id" property="menuId" />
<result column="menu_name" property="menuName" />
<result column="order_num" property="orderNum" />
<result column="path" property="path" />
<result column="menu_type" property="menuType" />
<result column="perms" property="perms" />
</resultMap>
注意这个地方,resultMap里面我没有配置parent_id到parentId的显式映射,到那时parentId还是可以传到service层进行
树结构的构造,说明这个值是有的,mybatis无法实现自动驼峰映射,除非显式配置,但是我用的是mybatis-plus,默认启用了驼峰映射
27.
List<Menu> menus = menuMapper.selectMenuByCondition(menu);
if (menus.isEmpty())
这个地方注意:不能够判断menus!=null,因为mybatis执行完sql语句默认返回空集合,所以null会导致永远为真。注意
28.
springboot中返回一个对象或者一个list集合,即使你在实体类里面不写toString方法,他也会返回json对象的形式。
json对象的内容是:
jackson 会序列化 User 对象的所有 非空字段(除非字段被标记为 transient 或使用 @JsonIgnore 注解忽略)。
字段的名称会直接作为 JSON 的键,字段的值会作为 JSON 的值。
然后总结三点:
1.如果不想让某个字段在json对象里面出现,这样
- 加transient private transient String password; // 不会被序列化
- 字段上方加@JsonIgnore注解
2.如果想让默认没有显式的null消失,这样
- 在yml文件中配置:spring.jackson.default-property-inclusion=non_null
3.如果想要显式的json键名不是属性名,这样:
- @JsonProperty("userId")
private Long id;
29. @ExceptionHandler(MethodArgumentNotValidException.class)
public AjaxResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.error("请求地址{},参数验证失败{}", requestURI, e.getObjectName(),e);
return AjaxResult.error(message);
}
String message = e.getBindingResult().getFieldError().getDefaultMessage();
这个地方是获取你的注解@NotNULL里面的自定义的消息,e是控制台爆出的异常
log.error("请求地址{},参数验证失败{}", requestURI, e.getObjectName(),e);
然后这个地方的参数,如果有e的话,控制台把异常堆栈信息爆出来,如果不加e,只有2025-04-11 20:35:23.094 ERROR 25884 --- [nio-8080-exec-1] c.y.s.exception.ExceptionAdvice : 请求地址/rbac/dept/addDept,参数验证失败dept
这个异常堆栈信息不会爆出来,如果没有全局统一异常信息处理,这个不为空的信息会出现在日志中,以warn的形式。
AjaxResult笔记
1.
import static com.warrior.utils.BaseController.success;
这个是个新特性,静态导入特性:某个类的静态方法别的类使用有两种方式:
(1)import static com.warrior.utils.BaseController.success;静态导入后,直接在实用类里面调用方法名就行
(2)使用静态方法的类名.静态方法名调用。
漂亮,又学一个新特性。jdk5引入的。
2.
/*枚举注意:
* 1.枚举是一个类:枚举常量隐式调用枚举方法。
* 2.枚举是一个类:有自己的属性:value
* 3.枚举是一个类:有自己的构造方法:构造方法把传入的值付给自己的value属性
* 4.枚举是一个类:有自己的方法:把属性作为值返回给外界
5. Type type = Type.SUCCESS;
System.out.println(type); // 输出:SUCCESS
System.out.println(type.value()); // 输出:0*/
public enum Type
{
/** 成功 */
SUCCESS(0),
/** 警告 */
WARN(301),
/** 错误 */
ERROR(500);
private final int value;
Type(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
3.
研究一遍后:发现这样使用:不用你自己写,知道流程就行
(1)BaseController中封装了controller层与业务处理无关的方法。
(2)具体的响应数据的处理方式由AjaxResult类进行数据的封装,BaseController只是调用。
4 springboot整合mybatis-plus
springboot_rbac和springboot_mybatisplus两个项目记录的笔记
1.导pom
<!-- mybatis-plus起步依赖插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<!--mp自动生成代码,api文档注释需要swagger注解需要的依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger2.version}</version>
</dependency>
2.配置yml文件(#格线是mp的配置)
# tomcat服务器端口配置
server:
port: 8080
servlet:
context-path: /rbac
# spring数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/rbac?serverTimezone=Asia/Shanghai&useTimezone=true/&characterEncoding=utf8
username: root
password: Yin2040411
max-wait: 10000
initial-size: 5
# 配置jackson的日期时间格式
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
#####################################################################################################
# mybatis-plus框架配置
mybatis-plus:
# 配置映射文件和别名
mapper-locations: classpath*:/com/yzh/springboot_rbac/mapper/*.xml
type-aliases-package: com.yzh.springboot_rbac.entity
# mp逻辑删除配置:
global-config:
db-config:
logic-delete-field: del_flag
logic-delete-value: 0 #逻辑删除
logic-not-delete-value: 1 #正常
# 数据库字段大写转小写配置
capital-mode: true
configuration:
# 下划线自动转托驼峰配置(不配置也是开启的,这里显式配置一下啦)
map-underscore-to-camel-case: true
# 让sql语句日志打印到控制台配置
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 控制台打印日志的详细程度配置
# trace:最详细日志信息
logging:
level:
root: info # 默认就是info
com.yzh.springboot_rbac.service.impl: trace
com.yzh.springboot_rbac.controller: trace
#日志生成路径配置
logback:
logDir: log/dev
appName: springboot_rbac
fileType: log
#######################################################################################
logback-spring.xml要注意一下该环境否则启动后一直卡在那里
logback-spring.xml里面这地方复制过来要改一下环境默认的default,这里需要改为default环境,
要不然项目一直卡在哪里启动不了,因为我没有配置dev的yml文件,所以只能使用default的文件。
<!--开发环境:打印控制台-->
<!--这里需要改为default环境,要不然项目一直卡在哪里启动不了,因为我没有配置dev的yml文件,所以只能使用
default的文件-->
<springProfile name="default">
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</springProfile>
3.
在springboot_mybatisplus项目中练习mp提供的方法记录
###################################################
如果想要在测试类中链式调用set方法设置对象的值,一定要把实体类里面的对应
set方法返回类型改为实体类形,retrun this
public User setName(String name) {
this.name = name;
return this;
}
public User setAge(Integer age) {
this.age = age;
return this;
}
public User setBir(Date bir) {
this.bir = bir;
return this;
}
public void testInsert() {
User user = new User();
user.setAge(20).setName("张三").setBir(new Date());
userMapper.insert(user);
}
#############################################################
4.
springboot中自定义测试类实现测试两种方法
- 1.测试类要加@SpringBootTest这个注解,springboot项目测试类要加这个注解,
否则无法测试,因为springboot项目需要依赖springboot来管理bean,否则usermapper无法注入。
- 2.自定义测试类继承默认生成的测试类,因为默认生成的测试类里面使用了这个注解
5.
雪花算法的:ASSIGN_ID实体类使用Long/String,数据库字段使用bigInt/varchar
ASSIGN_UUID实体类使用String,数据库字段使用Varchar
按照上述即可,实体类Integer和int数值范围对应数据库int范围太小,报错。
ASSIGN_ID设置后,mp自动生成1911670271488589825并插入到数据库中,数据库不用设置自增
ASSIGN_UUID:d7d3d88d136905d4362bcb7e1cef04cf,数据库不用设置自增
AUTO:需要依赖数据库设置自增才可以添加id,否则报错。
6.lambda表达式和引用方式如下:效果一样
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
users.forEach(user -> System.out.println(user));
7.常用的方法与条件构造器
@SpringBootTest
public class testBaseController {
/*此测试对自定义mapper继承BaseController测试*/
@Autowired
private UserMapper userMapper;
@Test
public void testSelectAllUser() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
users.forEach(user -> System.out.println(user));
}
@Test
public void testSelectByCondition() {
/*查询常用的有两个:
* selectById:根据id查,参数是id的值
* selectList:查询所有用户,null空表示无条件所有用户*/
// User user = userMapper.selectById(1);
// System.out.println(user);
/*基于条件的查询*/
// QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// queryWrapper.like("name", "李");
// List<User> list = userMapper.selectList(queryWrapper);
// list.forEach(user -> System.out.println(user));
/*lambda的查询*/
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(true, User::getName, "多");
List<User> list = userMapper.selectList(lambdaQueryWrapper);
list.forEach(user -> System.out.println(user));
}
@Test
public void testInsert() {
User user = new User();
user.setAge(20).setName("李四").setBir(new Date());
userMapper.insert(user);
/*insert(T entity):插入方法只有一个,参数为实体类*/
}
@Test
public void testdelete() {
/*删除常用的有两个:
* deleteById:genjuid删除,参数是id的值
* delete:根据条件删除,参数是条件构造器*/
/*普通删除*/
// userMapper.deleteById(2);
/*基于条件修改
* querywrapper形式*/
// QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq(true, "id", 3);
// userMapper.delete(queryWrapper);
/*lambdaqueryWrapper形式*/
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(true, User::getId, 4);
userMapper.delete(lambdaQueryWrapper);
}
@Test
public void testUpdate1() {
/*updateById():参数是实体对象,基于id修改:先查询,把查询的对象赋值给user,然后修改*/
User user = userMapper.selectById(1);
user.setAge(10).setName("伍六七");
userMapper.updateById(user);
}
@Test
public void testUpdate2() {
/*基于条件修改:
update()参数是修改后的实体类和条件构造器里面的条件
* QueryWrapper相当于sql中的where表达式eq相当于where age = 10*/
User user = userMapper.selectById("1");
user.setName("多阔霍");
// QueryWrapper<User> updateWrapper = new QueryWrapper<>();
// updateWrapper.eq("age", 10);
// userMapper.update(user, updateWrapper);
/*写一个lambdaWrapper,这个是使用lambda的方法引用实现正向映射数据库字段的值
* 注意,这里的方法引用不是lambda里面的获取的值,而是只获取方法名,即属性名
* 方法的引用根据应用上下文来执行,lambda中方法引用会输出一个值,但是mp中输出列名*/
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(true, User::getAge, 10);
userMapper.update(user, lambdaQueryWrapper);
}
}
8.
注意:
- Iservice使用的话要和BaseMapper一起使用,所以一定要自定义一个具体的接口继承BaseMapper,
如果直接施一公Iservice,spring无法注入泛型接口BaseMapper,必须指定具体实例接口
- 但是可以不适用Iservice,只用BaseMapper的crud即可,Iservice提供了更复杂的方法,而且不用自己写方法在service层。
5 redis开发笔记
1.
工具类里面封装的set方法value写成了string,导致我无法存入user类型
// redisUtil.set("userId" + user.getUserId(), "user{" + user + "}");
不用工具类,用原生的opsForValue的set方法可以存储user实体类类型
// redisTemplate.opsForValue().set(user.getUserName(), user);
2.
public List<DictData> selectDictDataByType(String dictType) {
Object dictDataList1 = redisUtil.get(dictType);
//这个地方注意:要用!=null判断,如果redis里面没有这个键会返回null,这时候再用isEmpty()判断是否是空列表就不行了
- mybatis返回list用isEmpty判断是否是空列表,mybatis不会返回空,他会返回空列表。
- redis这里如果键不存在的话,返回的是null,而不是空列表
if (dictDataList1 != null) {
System.out.println("这是redis里面存储的数据" + dictDataList1);
return (List<DictData>) dictDataList1;
} else {
List<DictData> dictDataList2 = dictDataMapper.selectDictDataBytype(dictType);
System.out.println("这是mysql里面存储的数据" + dictDataList2);
redisTemplate.opsForValue().set(dictType, dictDataList2);
return dictDataList2;
}
3.
<insert id="insertDictType" parameterType="DictType">
insert into tb_dict_type(
<if test="dictName != null and dictName != ''">dict_name,</if>
<if test="dictType != null and dictType != ''">dict_type,</if>
<if test="status != null">status,</if>
<if test="remark != null and remark != ''">remark,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time
)values(
<if test="dictName != null and dictName != ''">#{dictName},</if>
<if test="dictType != null and dictType != ''">#{dictType},</if>
<if test="status != null">#{status},</if>
<if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate()
)
</insert>
sysdate(),返回数据插入的时间,由数据库服务器生成
4.
看哪个完整系统接口的时候,发现他添加数据时候service层校验逻辑写在了controller层,
经过查找发现,传统的项目,service层的业务逻辑包括了从数据库查出来的校验逻辑,而现在的项目,校验方法还是写在
service层,但是校验逻辑写在了controller层也可以,看你自己了。
5.写了这几个接口,我现在总结一下我书写的命名规范:
(1)crud名字命名上(简单crud,不涉及校验逻辑的情况下):
- mapper层方法名用:
- insert
- delete
- update
- select
- service层和controller层方法名:
- add/save
- delete
- update
- select
controller层的请求地址可以省略后缀重点表示crud就可以
(2)如果包含校验逻辑
controller和mapper层方法命名不变
-service层变化
- isAdd (是否添加)
- isUpdate(是否修改)
- delete 删除 这个删除注意:(业务表用update逻辑删除,字典表用delete物理删除)
- select
校验逻辑一般简单开发就两种:
- 用javaweb的传统方法:先执行一个selectByid/selectByCondition,查询数据库里面是否有值,然后再根据返回结果添加
- 用抛出异常,这里分为两种情况:
- 如果你自己不想自定义,也不用返回给前端响应结果,只想在控制台看到异常
IllegalStateException用于抛出查过来的用户为空时候的异常
表示“数据状态不合法”,适合业务逻辑校验(如订单不存在):
java
User user = userDao.findById(userId);
if (user == null) {
throw new IllegalStateException("用户不存在"); // ✅ 标准异常,无需自定义
}
IllegalArgumentException用于想要再service判断请求此参数的时候
当方法参数明显无效或违反基本约定时抛出:
public User getUserById(Long userId) {
if (userId == null || userId <= 0) {
throw new IllegalArgumentException("用户ID必须为正数");
}
- 如果想要自己定义具体的不同类型异常,并响应给前端,用rbac业务里面的异常处理方法
- 注意这里异常处理可以放在controller抛出,或者service抛出,我感觉还是再service
比较符合各类校验。
(3)补一个mybatis查询结果的常见情况
查询方法 返回值(无数据时) 判断方式
- User findById(Long id) null if (user == null)
- List<User> selectList() 空集合 ([]) if (list.isEmpty())
- Map<String, User> selectMap() 空 Map ({}) if (map.isEmpty())
6.
4.24 字典类型表的crud写完了
准备些字典数据表的crud
7.
4.24晚上仔细看了看前后端分离和不分离区别
- 前后端不分离特征:
- 路由控制权再controller层,controller层既要返回或者跳转页面路由还要把数据返回到页面上
- 数据绑定:返回的页面中的数据由后端程序员绑定插入(jsp可能还会混合java代码)。由后端程序员决定插入数据到哪里。
- 页面渲染:页面由服务器渲染完成
总体上:前端就写三大件,然后把三大件给后端。
-后端如果用jsp,后端程序员要把html转为jsp,开发。
-后端如果用thymlef模板,后端程序员不用转为jsp,直接再html里面加属性,绑数据。
注意:这两种都是前后端不分离的情形,数据绑定都是由后端决定的。
-前后端分离特征:
- 路由控制权在前端,前端控制页面路由的跳转,调用后端urlAPI。
- 数据绑定自由,后端只返回数据格式json,前端获取到数据,自行决定如何展示,展示多少。
- 页面渲染,前端页面渲染由浏览器完成。
7.今天写删除和更新字典数据的时候,发现很省力的地方。
- 删除的service层的删除返回类型void,只在controller层返回成功就行。
- 更新的service层更新返回row,只在controller层判断row就行,
原来是在service层判断row,在返回boolean类型,controller在判断boolea,这样只判断一次row更方便。
- 添加和查询该判断还是要判断的,就删除和修改可以省力点。
8.
在 MySQL 数据库中,DATE_FORMAT函数的语法为DATE_FORMAT(date, format),
其中date是要格式化的日期或日期时间表达式,format是指定的格式字符串。
- DATE_FORMAT(NOW(), '%Y-%m-%d')会将当前时间按照年-月-日的格式进行显示,如2025-04-25。
- 注意:%y表示两位,%Y表示四位。
9.
<if test="status != null and status != ''" >status = #{status},</if>
<if test="remark != null and remark != ''">remark = #{remark},</if>
再次巩固这里
- 前端不写任何东西,后端sql里面得到的是''空字符串
- 前端json中删除这个字段,后端sql里没有是null
10.throw new ServiceException(String.format("%1$s已经被分配,无法删除", dictType.getDictName()));
String.fommat()里面%1$s,别忘了s,,String.format("%1$s已经被分配,无法删除", dictType.getDictName()) 这行代码的作用是将 dictType.getDictName() 的值插入到格式字符串中 %1$s 的位置,生成一个新的字符串,
11.截至2025/4/25,字典数据表和字典类型表,基本的缓存已经全部完成和测试完成。
接下来,看那个缓存注解和刷新缓存,重置缓存的方法。
12.截至2025/4/25晚上22.46,缓存系列全部写完测完。
13.明天尽量把限流加进来。4.26,限流成功熟悉并加入
14.补充一下第七点前后端分离的情况
不论是前后端分离还是不分离,前端都是通过浏览器的网络接口向后端发送请求的。
只不过前后端不分离的html代码中的数据是由后端服务器生成的,js什么的都是前端写的,运行在浏览器里面。
- 前后端分离,前端服务器,后端服务器
1.浏览器根据输入的地址向服务器(前端服务器或与前端资源所在位置对应的服务器)发送请求,获取前端的 HTML、CSS、JavaScript 等静态资源。
2.浏览器接收并解析这些资源,构建出页面的初始结构和样式,并执行 JavaScript 代码来初始化页面的交互逻辑等。
3.当用户在页面上进行操作,触发与后端数据交互的请求时,浏览器会根据前端代码中定义的逻辑,向后端服务器发送请求,请求中通常会包含相关的参数和数据。
4.后端服务器接收到请求后,进行相应的处理,如查询数据库、进行业务逻辑计算等,然后将处理结果以 JSON、XML 等格式的数据响应给浏览器。
5.浏览器接收后端返回的数据后,会将数据交给前端的 JavaScript 代码进行处理。前端 JavaScript 代码会根据数据来更新页面的内容、样式或执行其他交互操作,而不是将数据交给前端服务器处理。前端服务器主要负责提供静态资源,在前后端分离架构中,一般不会处理后端返回给浏览器的数据。
6.所以说前端服务器只是存放静态资源用的,处理后端数据还是在浏览器里面根据js代码处理
- 前后端不分离,在浏览器上输入地址,直接访问后端服务器,后端服务器返回生成的页面和数据给浏览器解析。
- 浏览器是一个软件应用程序,负责解析前端代码,前端代码运行的环境。
- 前端是设计页面的,编写三大件代码,设计后由浏览器解析代码并渲染在浏览器显示屏上。
渲染是指将 HTML、CSS 和 JavaScript 代码转换为用户可见的页面的过程
6 rabbit MQ开发笔记
参数校验的问题汇总
参数校验分为这几种情况
1.用户密码是否为空校验
2.用户名或者密码错误校验(用户名错误直接返回空了,只能是密码错误)
3.用户不存在校验(根据用户名判断)
目前了解到的方法是
1.是否为空校验用@NotNull注解。
2.用户名不存在或者密码错误在service层校验,然后抛出自定义的异常
-
只定义自定义异常,但是不处理,只会抛出在控制台
-
如果捕获这个异常,需要定义捕获处理类,然后把异常以键值对形式返回给前端(就是在响应体中)
然后前端根据错误,动态展示在页面上,以前jsp是自己写异常,自己展示页面上,没有前后端分离。
作者:yzh先生
博客:https://www.cnblogs.com/ZiJun
本文版权归作者和博客园共有,转载请在文章中注明原文链接:https://www.cnblogs.com/ZiJun/p/18852481 ,愿尊重劳动成果,谢谢!
若有关于博客内容的各种问题,欢迎在评论区讨论或发消息,让我们一起进步!