《开发实战》11 | 空值处理:分不清楚的null和恼人的空指针

11 | 空值处理:分不清楚的null和恼人的空指针

修复和定位恼人的空指针问题

NullPointerException 是 Java 代码中最常见的异常,最可能出现的场景归为以下5 种:

  1. 参数值是 Integer 等包装类型,使用时因为自动拆箱出现了空指针异常;
  2. 字符串比较出现空指针异常;
  3. 诸如 ConcurrentHashMap 这样的容器不支持 Key 和 Value 为 null,强行 put null 的Key 或 Value 会出现空指针异常;
  4. A 对象包含了 B,在通过 A 对象的字段获得 B 之后,没有对字段判空就级联调用 B 的方法出现空指针异常;
  5. 方法或远程服务返回的 List 不是空而是 null,没有进行判空就直接调用 List 的方法出现空指针异常。

有时候线上的空指针异常是很难排查的,因为是不能打断点的,往往是将代码进行拆分,或者添加日志。
可以考虑使用Arthas 简单易用功能强大,可以定位出大多数的 Java 生产问题。
如果是分支复杂的业务逻辑,你需要再借助 stack 命令来查看 wrongMethod 方法的调用栈,并配合 watch 命令查看各方法的入参,就可以很方便地定位到空指针的根源了
**定位到空指针异常后,我们需要探讨这是入参问题还是bug **:
如果是来源于入参,还要进一步分析入参是否合理等;
如果是来源于 Bug,那空指针不一定是纯粹的程序 Bug,可能还涉及业务属性和接口调用规范等。

修复方式,一般都是使用else-if,我们也可以使用 Java 8 的 Optional 类。
使用判空方式或 Optional 方式来避免出现空指针异常,不一定是解决问题的最好方式,空指针没出现可能隐藏了更深的 Bug。
解决空指针异常,还是要真正 case by case 地定位分析案例,然后再去做判空处理,而处理时也并不只是判断非空然后进行正常业务流程这么简单,同样需要考虑为空的时候是应该出异常、设默认值还是记录日志等

POJO 中属性的 null 到底代表了什么?

需要考虑:

  1. DTO 中字段的 null 到底意味着什么?是客户端没有传给我们这个信息吗?
  2. 既然空指针问题很讨厌,那么 DTO 中的字段要设置默认值么?
  3. 如果数据库实体中的字段有 null,那么通过数据访问框架保存数据是否会覆盖数据库中的既有数据?

User 的 POJO,同时扮演 DTO 和数据库 Entity 角色,包含用户 ID、姓名、昵称、年龄、注册时间等属性:

@Data
@Entity
public class User {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;
  private String name;
  private String nickname;
  private Integer age;
  private Date createDate = new Date();
}

有一个 Post 接口用于更新用户数据,根据用户姓名自动设置一个昵称,昵称的规则是“用户类型 + 姓名”

@Autowired
private UserRepository userRepository;
@PostMapping("wrong")
public User wrong(@RequestBody User user) {
  user.setNickname(String.format("guest%s", user.getName()));
  return userRepository.save(user);
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

数据库中初始化一个用户,age=36、name=zhuye、create_date=2020 年 1 月4 日、nickname 是 NULL:
image.png

  1. 调用方只希望重置用户名,但 age 也被设置为了 null;
  2. nickname 是用户类型加姓名,name 重置为 null 的话,访客用户的昵称应该是guest,而不是 guestnull,重现了文首提到的那个笑点;
  3. 用户的创建时间原来是 1 月 4 日,更新了用户信息后变为了 1 月 5 日。

原因

  1. 明确 DTO 种 null 的含义。对于 JSON 到 DTO 的反序列化过程,null 的表达是有歧义的,客户端不传某个属性,或者传 null,这个属性在 DTO 中都是 null。
  2. POJO 中的字段有默认值。如果客户端不传值,就会赋值为默认值,导致创建时间也被更新到了数据库中。
  3. 注意字符串格式化时可能会把 null 值格式化为 null 字符串。比如上面的 guestnull
  4. DTO 和 Entity 共用了一个 POJO。
  5. 数据库字段允许保存 null,会进一步增加出错的可能性和复杂度。

解决:DTO 和 Entity 进行拆分

  1. UserDto 中只保留 id、name 和 age 三个属性,且 name 和 age 使用 Optional 来包装,以区分客户端不传数据还是故意传 null。
  2. 在 UserEntity 的字段上使用 @Column 注解,把数据库字段 name、nickname、age和 createDate 都设置为 NOT NULL,并设置 createDate 的默认值为CURRENT_TIMESTAMP,由数据库来生成创建时间。
  3. 使用 Hibernate 的 @DynamicUpdate 注解实现更新 SQL 的动态生成,实现只更新修改后的字段,不过需要先查询一次实体,让 Hibernate 可以“跟踪”实体属性的当前状态,以确保有效。
@Data
public class UserDto {
  private Long id;
  private Optional<String> name;
  private Optional<Integer> age;
}
@Data
@Entity
@DynamicUpdate
public class UserEntity {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;
  @Column(nullable = false)
  private String name;
  @Column(nullable = false)
  private String nickname;
  @Column(nullable = false)
  private Integer age;
  @Column(nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIM
  private Date createDate;
}

小心 MySQL 中有关 NULL 的三个坑

NULL 字段,和你着重说明 sum 函数、count 函数,以及 NULL 值条件可能踩的坑

@Entity
@Data
public class User {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;
  private Long score;
}

往实体初始化一条数据,其 id 是自增列自动设置的 1,score 是NULL

可能出现的坑:

  1. 通过 sum 函数统计一个只有 NULL 值的列的总和,比如 SUM(score);
    1. SELECT SUM(score) FROM user
  2. select 记录数量,count 使用一个允许 NULL 的字段,比如 COUNT(score);
    1. SELECT COUNT(score) FROM user
  3. 使用 =NULL 条件查询字段值为 NULL 的记录,比如 score=null 条件。
    1. SELECT * FROM user WHERE score=null

得到的结果,分别是 null、0 和空 List
原因是:

  1. MySQL 中 sum 函数没统计到任何记录时,会返回 null 而不是 0,可以使用 IFNULL函数把 null 转换为 0;
  2. MySQL 中 count 字段不统计 null 值。COUNT(*) 才是统计所有记录数量的正确方式。
  3. MySQL 中 =NULL 并不是判断条件而是赋值,对 NULL 进行判断只能使用 IS NULL 或者 IS NOT NULL。

修改sql:

  1. SELECT IFNULL(SUM(score),0) FROM user
  2. SELECT COUNT(*) FROM user
  3. SELECT * FROM user WHERE score IS NULL

得到三个正确结果,分别为 0、1、[User(id=1, score=null)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/96319.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

移动端如何适配不同的屏幕尺寸

在移动端开发中&#xff0c;适配不同的屏幕尺寸是一个重要的考虑因素。以下是一些常用的方法来实现移动端的屏幕适配&#xff1a; 使用响应式布局&#xff1a;使用CSS媒体查询和弹性布局来根据屏幕尺寸调整页面布局和元素大小。通过设置百分比、em或rem单位来实现元素的相对大小…

uniapp结合Canvas+renderjs根据经纬度绘制轨迹(二)

uniapp结合Canvasrenderjs根据经纬度绘制轨迹 文章目录 uniapp结合Canvasrenderjs根据经纬度绘制轨迹效果图templaterenderjsjs数据结构 ​ 根据官方建议要想在 app-vue 流畅使用 Canvas 动画&#xff0c;需要使用 renderjs 技术&#xff0c;把操作canvas的js逻辑放到视图层运…

【pyqt5界面化开发-4】垂直布局/水平布局+‘套娃‘布局

目录 一、垂直布局 二、布局器的组合 三、水平布局垂直布局&#xff08;套娃&#xff09; 一、垂直布局 需要模块&#xff1a;QVBoxLayout # 垂直布局layout QVBoxLayout()………………# 应用设置的布局器self.setLayout(layout) 模块间的伸缩器&#xff08;可以理解为弹簧…

502 bad gateway什么意思502 bad gateway问题解决办法

502 bad gateway是一种常见互联网连接错误&#xff0c;大部分情况就是打不开页面&#xff0c;连接不上网络&#xff0c;访问服务器挂了等问题&#xff0c;下面来看看具体解决方法&#xff0c;希望能够帮助你解决问题。 502 bad gateway什么意思 简单说就是服务器没有收到回应&…

Redis——》如何评估锁过期时间

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

gitee上传本地项目bug

&#x1f92e;这个破bug不知道浪费了多长时间&#xff0c;以前没有记录&#xff0c;每次都忘记&#xff0c;这次记下来 问题描述 gitee创建仓库&#xff0c;然后根据它提示的如下命令&#xff0c;但一直报错 原因分析&#xff1a; 把命令复制出来&#xff0c;粘贴到Sublime …

Python入门自学进阶-Web框架——40、redis、rabbitmq、git——3

git&#xff0c;一个分布式的版本管理工具。主要用处&#xff1a;版本管理、协作开发。 常见版本管理工具&#xff1a; VSS —— Visual Source Safe CVS —— Concurrent Versions System SVN —— CollabNet Subversion GIT GIT安装&#xff1a;下载安装文件&#xff1a;…

学习笔记:Pytorch利用MNIST数据集训练生成对抗网络(GAN)

2023.8.27 在进行深度学习的进阶的时候&#xff0c;我发了生成对抗网络是一个很神奇的东西&#xff0c;为什么它可以“将一堆随机噪声经过生成器变成一张图片”&#xff0c;特此记录一下学习心得。 一、生成对抗网络百科 2014年&#xff0c;还在蒙特利尔读博士的Ian Goodfello…

Java虚拟机内部组成

1、栈区 public class Math {public int compute(){//一个方法对应一块栈帧内存区域int a l;int b 2;int c (a b)*10;return c; } public static void main(String[] args){Math math new, Math() ;math.compute() ;System.out.println("test");}} 栈是先进后出…

centos安装MySQL 解压版完整教程(按步骤傻瓜式安装

一、卸载系统自带的 Mariadb 查看&#xff1a; rpm -qa|grep mariadb 卸载&#xff1a; rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64 二、卸载 etc 目录下的 my.cnf 文件 rm -rf /etc/my.cnf 三、检查MySQL是否存在 有则先删除 #卸载mysql服务以及删除所有mysql目录 #没…

使用CSS的@media screen 规则为不同的屏幕尺寸设置不同的样式(响应式图片布局)

当你想要在不同的屏幕尺寸或设备上应用不同的CSS样式时&#xff0c;可以使用 media 规则&#xff0c;特别是 media screen 规则。这允许你根据不同的屏幕特性&#xff0c;如宽度、高度、方向等&#xff0c;为不同的屏幕尺寸设置不同的样式。 具体来说&#xff0c;media screen…

常见前端面试之VUE面试题汇总十一

31. Vuex 有哪几种属性&#xff1f; 有五种&#xff0c;分别是 State、 Getter、Mutation 、Action、 Module state > 基本数据(数据源存放地) getters > 从基本数据派生出来的数据 mutations > 提交更改数据的方法&#xff0c;同步 actions > 像一个装饰器&a…

微信小程序请求接口返回的二维码(图片),本地工具和真机测试都能显示,上线之后不显示问题

请求后端接口返回的图片&#xff1a; 页面展示&#xff1a; 代码实现&#xff1a; :show-menu-by-longpress"true" 是长按保存图片 base64Code 是转为base64的地址 <image class"code" :src"base64Code" alt"" :show-menu-by-long…

C语言练习4(巩固提升)

C语言练习4 选择题 前言 面对复杂变化的世界&#xff0c;人类社会向何处去&#xff1f;亚洲前途在哪里&#xff1f;我认为&#xff0c;回答这些时代之问&#xff0c;我们要不畏浮云遮望眼&#xff0c;善于拨云见日&#xff0c;把握历史规律&#xff0c;认清世界大势。 选择题 …

考研数据结构:第八章 排序

文章目录 一、排序的基本概念二、插入排序2.1插入排序2.1.1算法思想2.1.2算法实现2.1.3算法效率分析2.1.4算法优化——折半插入排序 2.2希尔排序2.2.1算法思想2.2.2代码实现2.2.3算法性能分析 三、交换排序3.1冒泡排序3.1.1算法思想3.1.2代码实现3.1.3算法性能分析 3.2快速排序…

基于Spring Boot 的 Ext JS 应用框架之coworkee

Ext JS 官方提供了一个人员管理的完整应用框架 - coworkee。该框架的显示如下: 该框架的布局特点如下: 布局方式: 左右布局, 左侧导航栏默认收合特点:左侧导航区占用空间小, 工作区较大, 适合没有二级导航栏,工作区需要显示的内容较多的系统。如果导航栏是横向底部,就…

DataTable扩展 列转行方法(2*2矩阵转换)

源数据 如图所示 // <summary>/// DataTable扩展 列转行方法&#xff08;2*2矩阵转换&#xff09;/// </summary>/// <param name"dtSource">数据源</param>/// <param name"columnFilter">逗号分隔 如SDateTime,PM25,PM10…

民族传统文化分享系统uniapp 微信小程序

管理员、用户可通过Android系统手机打开系统&#xff0c;注册登录后可进行管理员后端&#xff1b;首页、个人中心、用户管理、知识分类管理、知识资源管理、用户分享管理、意见反馈、系统管理&#xff0c;用户前端&#xff1b;首页、知识资源、用户分享、我的等。 本系统的使用…

vue create -p dcloudio/uni-preset-vue my-project创建文件报错443

因为使用vue3viteuniappvant4报错&#xff0c;uniapp暂不支持vant4&#xff0c;所以所用vue2uniappvant2 下载uni-preset-vue-master 放到E:\Auniapp\uni-preset-vue-master 在终端命令行创建uniapp vue create -p E:\Auniapp\uni-preset-vue-master my-project

【广州华锐互动】VR党建多媒体互动展厅:随时随地开展党史教育

随着科技的不断发展&#xff0c;虚拟现实(VR)技术已经逐渐渗透到各个领域&#xff0c;其中党建教育尤为受益。为了更好地传承红色基因&#xff0c;弘扬党的优良传统&#xff0c;广州华锐互动推出了VR党建多媒体互动展厅&#xff0c;让广大党员干部和人民群众通过现代科技手段&a…