【笔记】整理实际开发中遇到的问题及解决方法
本帖最后由 yunjl 于 2021-11-20 00:38 编辑整理下我从开始工作遇到的一些问题吧,并附上解决方法。(持续更新)
有些问题可能会有更好的解决方法,欢迎补充。
## SQL 语句相关
### SQL 语句查集合数组(foreach )
foreach 也就是遍历迭代,在 SQL 中通常用在 in 这个关键词的后面
foreach 元素的属性主要有 item,index,collection,open,separator,close。
分别代表:
item 表示集合中每一个元素进行迭代时的别名,
index 用于表示在迭代过程中,每次迭代到的位置,
open 表示该语句以什么开始,
separator 表示在每次进行迭代之间以什么符号作为分隔 符,
close 表示以什么结束
<select id="selectByIds" resultType="com.txw.pojo.User">
select * from user where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
### SQL 表中没有数据插入有则特殊处理
首先看下表结构:id为主键
[!(https://z3.ax1x.com/2021/11/19/I75uaq.png)](https://imgtu.com/i/I75uaq)
#### 没有数据插入,有则不插入
<insert id="insert">
INSERT INTO user ( user_id, user_name)
SELECT#{userId}, #{userName}
from DUAL
wherenot exists(select * from user where user_id=#{userId} and user_name = #{userName});
</insert>
#### 没有数据插入,有则更新
主键(索引)唯一可区分的时候可以用 `ON DUPLICATE KEY UPDATE`
表中没有id为2的数据则插入“zhangsan”,有的话则更新为“lisi”
insert into `user`(id, user_name)
values
(2, "zhangsan")
ON DUPLICATE KEY UPDATE
id = 2 , user_name = "lisi";
当然也可以多条插入更新:
insert into `user`(id, user_name)
values
(2, "zhangsan"),
(3,"yanyanyanyan")
ON DUPLICATE KEY UPDATE
user_name = VALUES(user_name);
不过业务中最常用的就是这样吧:
<insert id="insert">
insert into user(user_id, user_name)
values
({userId}, #{userName})
ON DUPLICATE KEY UPDATE
#{userId}, #{userName}
</insert>
### 更新语句(有数据则更新,没有数据则不变)
<update id="update" >
UPDATE user
SET
<if test="userId != null and userId !=''">user_id=#{userId},</if>
<if test="userName != null and userName !=''">user_name=#{userName},</if>
<if test="userPassword != null and userPassword !=''">user_password=#{userPassword},</if>
where
id = #{id}
</update>
### 插入数据(更新数据)返回修改的数据的id
只需要在其对应xxxmapper.xml标签中加入以下属性即可:
useGeneratedKeys=”true” keyProperty=”id”
比如这样:
<insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into user(user_id, user_name)
values
({userId}, #{userName})
</insert>
useGeneratedKeys:默认为false,为true时,表示如果插入的表以自增列为主键,则允许 JDBC 支持自动生成主键,并可将自动生成的主键返回;
keyProperty:实体类对应主键的属性;
keyColumn:数据库中的主键;
这样的话插入的时候就会返回插入的的这条数据的id,在service层通过`user.getId`可以获取这个id
### 表中的一个字段含有多个值
字段名用 varchar 类型,值使用“,”拼接。
[!(https://z3.ax1x.com/2021/11/19/I75NZR.png)](https://imgtu.com/i/I75NZR)
筛选只包含“SALES”的数据 `FIND_IN_SET`
SELECT
realname
FROM
org_user
WHERE
org_id = 23
AND FIND_IN_SET('SALES', org_role)
## 业务相关
### 多表操作的时候一定要加事务
最常见的就是添加事务注解 `@Transactional(rollbackFor = Exception.class)`可以加在方法上也可以加在类上
@Override
@Transactional(rollbackFor = Exception.class)
public void add(User user) throws Exception {
//XXX业务处理
}
如果想深入了解这个注解何时会失效,自行百度哦,这里一句两句说不完。
### 报错:Request method 'POST' not supported
可以把注解 @ Controller 换成 @ RestController 试试,出现这个问题的原因很多
### 唯一的随机数
说起唯一的随机数,可能很容易想到 java.util.UUID,如下,可以产生 32 位长度的字符串。
`String str = UUID.randomUUID().toString().replaceAll("-", "");`
但是这样并不是一定没问题的,单机多线程的情况下也可能出现重复的情况。
为确保产生的字符串唯一,可在 uuid 后加一个随机数,如果再能加唯一用户名,电话 xxx 的就更加万无一失了。
String str = UUID.randomUUID().toString().replaceAll("-", "") + new Random().nextLong();
// 产生的字符串太长,浪费存储,再进行 MD5
// 可以使用 apache 的 org.apache.commons.codec.digest.DigestUtils
// 也可以是使用 java.security.MessageDigest 进行加密
// 注意返回的是长度为 16 的 byte 数组,使用 Hex 转换成 32 的 char 数组,在转成字符串
String uuid = new String(Hex.encodeHex(DigestUtils.md5(str)));
简单版就这样就可以了:
private static String nonce = RandomUtil.randomString(16); //唯一随机数
### 获取指定日期的月初和月末日期
需求:客户想要给定日期的近三个月的数据
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
String time ="2021-10-12";
Date timeA = format.parse(time);
Calendar c = Calendar.getInstance();
c.setTime(timeA);
//设置为上个月1号
c.add(Calendar.MONTH, -1);
c.set(Calendar.DAY_OF_MONTH, 1);
String startFormat = format.format(c.getTime());
Calendar d = Calendar.getInstance();
d.setTime(timeA);
//设置为下个月最后一天
d.add(Calendar.MONTH, +1);
d.set(Calendar.DAY_OF_MONTH,d.getActualMaximum(Calendar.DAY_OF_MONTH));
String endFormat = format.format(d.getTime());
System.out.println(startFormat);
System.out.println(endFormat);
执行结果:
`2021-09-01`
`2021-11-30`
### 依赖引入爆红
这是因为jdk版本引发的,虽然不影响运行,但是强迫症表示非常不舒服
[!(https://z3.ax1x.com/2021/11/19/I77fBQ.png)](https://imgtu.com/i/I77fBQ)
解决办法:
在 mapper 接口上加个注解`@Repository`
[!(https://z3.ax1x.com/2021/11/19/I7HPgK.png)](https://imgtu.com/i/I7HPgK)
### 解决“请在微信客户端打开链接”
[!(https://z3.ax1x.com/2021/11/19/I7bXk9.png)](https://imgtu.com/i/I7bXk9)
浏览器设置:
[!(https://z3.ax1x.com/2021/11/19/I7bzSx.png)](https://imgtu.com/i/I7bzSx)
MicroMessenger(如果是需要授权的界面会显示空白页)
请求:
请求头中设置参数
`User-Agent=Mozilla/5.0 (Linux; U; Android 2.3.6; zh-cn; GT-S5660 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MicroMessenger/4.5.255`
[!(https://z3.ax1x.com/2021/11/19/I7qPmD.png)](https://imgtu.com/i/I7qPmD)
还有就是使用 Fiddler 对手机抓包解决,比较麻烦点不过也是最好用的,具体百度吧,步骤太多了。
### 13 位时间戳:
String timestamp = System.currentTimeMillis()+""; //时间戳
### 传给mapper.xml多个参数时
使用@Param注解标注
@Param 是 MyBatis 所提供的,作为 Dao 层的注解,作用是用于传递参数,从而可以与 SQL 中的的字段名相对应,一般在 2=<参数数<=5 时使用最佳。
Mapper 接口方法:
public int getUsersDetail(@Param("userid") int userid);
对应 Sql Mapper.xml 文件:
<select id="getUserDetail" statementType="CALLABLE" resultMap="baseMap">
SELECT * FROM user_detail WHERE user_id =#{userid}
</select>
说明:
当你使用了@Param 注解来声明参数时,使用 #{} 或 ${} 的方式都可以,当你不使用@Param 注解来声明参数时,必须使用使用 #{}方式。如果使用 ${} 的方式,会报错。
### 文件上传的格式检验
这个一般前端进行校验就行,有的需求需要防止用户改文件后缀名进行上传,这个时候就要前后端一起校验了,
这个后端校验当时真是被搞的头大,我采用的是验证文件的文件头,写了一个工具类,列举了常用的文件格式的文件头,如下:
private static void getAllFileType(){
FILE_TYPE_MAP.put("ffd8ffe000104a464946", "jpg"); //JPEG (jpg)
FILE_TYPE_MAP.put("89504e470d0a1a0a0000", "png"); //PNG (png)
FILE_TYPE_MAP.put("47494638396126026f01", "gif"); //GIF (gif)
FILE_TYPE_MAP.put("49492a00227105008037", "tif"); //TIFF (tif)
FILE_TYPE_MAP.put("424d228c010000000000", "bmp"); //16 色位图(bmp)
FILE_TYPE_MAP.put("424d8240090000000000", "bmp"); //24 位位图(bmp)
FILE_TYPE_MAP.put("424d8e1b030000000000", "bmp"); //256 色位图(bmp)
FILE_TYPE_MAP.put("41433130313500000000", "dwg"); //CAD (dwg)
FILE_TYPE_MAP.put("3c21444f435459504520", "html"); //HTML (html)
FILE_TYPE_MAP.put("3c21646f637479706520", "htm"); //HTM (htm)
FILE_TYPE_MAP.put("48544d4c207b0d0a0942", "css"); //css
FILE_TYPE_MAP.put("696b2e71623d696b2e71", "js"); //js
FILE_TYPE_MAP.put("7b5c727466315c616e73", "rtf"); //Rich Text Format (rtf)
FILE_TYPE_MAP.put("38425053000100000000", "psd"); //Photoshop (psd)
FILE_TYPE_MAP.put("46726f6d3a203d3f6762", "eml"); //Email (eml)
FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000", "doc"); //MS Excel 注意:word、msi 和 excel 的文件头一样
FILE_TYPE_MAP.put("255044462d312e350d0a", "pdf"); //Adobe Acrobat (pdf)
FILE_TYPE_MAP.put("2e524d46000000120001", "rmvb"); //rmvb/rm 相同
...等
}
我写好我自己的电脑测试没问题,然后就提交了,这个时候离谱的就来了,我同事是苹果电脑,Excel文件在他的电脑上修改保存过文件头就变了,识别成其他的格式,而且不同版本的Excel创建的文件文件头都不一样,都识别成其他的格式,很乱,后来也没有找到很好的办法解决这个,只能在识别后的判断处放松点。
### @Valid 无法校验List`<E>`
@Valid只能校验JavaBean,而List<E>不是JavaBean所以校验会失败,尝试了三种解决办法,
#### 方法1:对List进行Wrapper
既然List不是JavaBean,那我们就把它封装成JavaBean,我们定义一个ListWrapper类如下:
package com.wyq.firstdemo.util;
import lombok.Getter;
import lombok.Setter;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
@Setter
@Getter
public class ListWrapper<E> {
@Valid
private List<E> list;
public ListWrapper() {
list = new ArrayList<>();
}
publicListWrapper(List<E> list) {
this.list = list;
}
}
同时修改一下controller对应的方法:
// 使用包装类对list进行验证
@PostMapping("/insert/all")
public ServerResponse<String> insertList(@Valid @RequestBody ListWrapper<UserEntity> listWrapper, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
log.error(bindingResult.getFieldError().toString());
return ServerResponse.createByErrorMessage(bindingResult.getFieldError().getDefaultMessage());
}
userService.insertList(listWrapper.getList());
return ServerResponse.createBySuccess();
}
这样就可以对list进行校验了
注意:
由于对list进行了包装,如果我们传参的时候
[{},{}..]要改为{“list”: [{},{}..]}
#### 方法2:使用@Validated+@Valid
在controller类上面增加@Validated注解,并且删除方法参数中的BindingResult bindingResult(因为这个参数已经没有用了,异常统一有controller返回了)
[!(https://z3.ax1x.com/2021/11/19/Iqtrm4.png)](https://imgtu.com/i/Iqtrm4)
[!(https://z3.ax1x.com/2021/11/19/IqtckR.png)](https://imgtu.com/i/IqtckR)
运行一下
[!(https://z3.ax1x.com/2021/11/19/Iqtf1K.png)](https://imgtu.com/i/Iqtf1K)
可以看到可以对参数进行校验了,但还还有一个问题,那就是这个不是我们想要的返回格式,它controller自己返回的格式,所以我们需要做一个统一异常处理,代码如下:
@Slf4j
@RestControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler
@ResponseBody
public ServerResponse<String> handle(ConstraintViolationException exception) {
log.error(String.valueOf(exception));
Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
StringBuilder builder = new StringBuilder();
for (ConstraintViolation violation : violations) {
builder.append(violation.getMessage());
break;
}
return ServerResponse.createByErrorMessage(builder.toString());
}
}
经过统一异常处理,我们这边的返回结果就是我们想要的格式了
#### 方法3:自定义一个List(推荐吧)
先定义一个ValidList
/**
* 可被校验的List
*
* @param <E> 元素类型
* @author Deolin
*/
public class ValidList<E> implements List<E> {
@Valid
private List<E> list;
public ValidList() {
this.list = new ArrayList<>();
}
public ValidList(List<E> list) {
this.list = list;
}
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
....重写方法
}
对比方法3和方法1,有没有觉得代码有点相似,新建一个类,并且让他实现List接口,使这个类即具有了JavaBean的特性,又具有了List的特性,比方法1简单优雅很多。
只需要把List换成ValidList就可以了,还不需要多统一异常处理。
[!(https://z3.ax1x.com/2021/11/19/IqtvjS.png)](https://imgtu.com/i/IqtvjS) 可能是Markdown语法的问题,有些出现了错误,我明天更新的时候修改下,抱歉了 特殊符号那块没看懂 xiaolong23330 发表于 2021-11-19 11:12
特殊符号那块没看懂
这是因为MarkDown语法导致的
原文是这样 < <= > >= & ' " 特殊符号如何输入? RatCreak 发表于 2021-11-19 13:32
特殊符号如何输入?
原符号 < <= > >= & ’ "
替换符号 < <= > >= & ' " 日期的话试试LocalDate
LocalDate of = LocalDate.of(2021, 10, 15);
LocalDate start = of.minusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
System.out.println(start);
LocalDate end = of.plusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
System.out.println(end); 本帖最后由 yunjl 于 2021-11-19 16:18 编辑
可以的
String str = "2019-03-03";
//指定转换格式
DateTimeFormatter fmt2 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
//进行转换
LocalDate date = LocalDate.parse(str, fmt2);
LocalDate start = date.minusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
System.out.println(start);
LocalDate end = date.plusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
System.out.println(end);
页:
[1]