吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1658|回复: 7
收起左侧

[Java 转载] 【笔记】整理实际开发中遇到的问题及解决方法

[复制链接]
yunjl 发表于 2021-11-19 10:45
本帖最后由 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为主键
I75uaq.png

没有数据插入,有则不插入
    <insert id="insert">
                    INSERT INTO user ( user_id, user_name)
                    SELECT  #{userId}, #{userName}
                    from DUAL
                    where  not 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 类型,值使用“,”拼接。
I75NZR.png
筛选只包含“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版本引发的,虽然不影响运行,但是强迫症表示非常不舒服
I77fBQ.png
解决办法:
在 mapper 接口上加个注解@Repository
I7HPgK.png

解决“请在微信客户端打开链接”

I7bXk9.png
浏览器设置:
I7bzSx.png
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
I7qPmD.png
还有就是使用 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 [Outlook Express 6] (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<>();
                    }

                    public  ListWrapper(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返回了)
Iqtrm4.png

IqtckR.png
运行一下
Iqtf1K.png
可以看到可以对参数进行校验了,但还还有一个问题,那就是这个不是我们想要的返回格式,它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就可以了,还不需要多统一异常处理。
IqtvjS.png

免费评分

参与人数 2吾爱币 +8 热心值 +2 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
独立勿扰 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| yunjl 发表于 2021-11-19 11:05
可能是Markdown语法的问题,有些出现了错误,我明天更新的时候修改下,抱歉了
xiaolong23330 发表于 2021-11-19 11:12
 楼主| yunjl 发表于 2021-11-19 11:16
xiaolong23330 发表于 2021-11-19 11:12
特殊符号那块没看懂

这是因为MarkDown语法导致的
原文是这样 &lt; &lt;= &gt; &gt;= &amp; &apos; &quot;
RatCreak 发表于 2021-11-19 13:32
特殊符号如何输入?
 楼主| yunjl 发表于 2021-11-19 13:34
RatCreak 发表于 2021-11-19 13:32
特殊符号如何输入?

原符号 < <= > >= & ’ "
替换符号 &lt; &lt;= &gt; &gt;= &amp; &apos; &quot;
vkery 发表于 2021-11-19 13:52
日期的话试试LocalDate
[Java] 纯文本查看 复制代码
        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:17
本帖最后由 yunjl 于 2021-11-19 16:18 编辑
可以的

[Java] 纯文本查看 复制代码
        
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);
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 11:53

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表