一、发现经历
事情是这样的,我今天本来要演示系统,就去前端同学的页面上点一点。不小心点到了其他同事编写的服务,然后界面就报错了。这给我吓得,这还能演示吗这。然后,我就去服务器查看了一下日志,发现了如下景象:
看到这景象啊,我第一件事情就是查看堆栈,也没找到自己写的代码啊,好好的咋就报错了。
于是,我第一件事情,复制报错信息找到百度网站。复制粘贴,往上一怼!好家伙,竟然找不到一个和我症状一样的。
那我只能自己处理了。
二、问题定位
从堆栈信息定位一个问题的位置其实是很简单的,但是要明白为什么错误,还是得多看一会儿的。
我定睛看了一阵子,发现了
at org.apache.ibatis.type.LocalDateTimeTypeHandler.getNullableResult(LocalDateTimeTypeHandler.java:38)
这一行报错,于是我就跟进源码查看。这个源码,写的也简单。
1 /**
2 * Copyright 2009-2019 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.apache.ibatis.type;
17
18 import java.sql.CallableStatement;
19 import java.sql.PreparedStatement;
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22 import java.time.LocalDateTime;
23
24 /**
25 * @since 3.4.5
26 * @author Tomas Rohovsky
27 */
28 public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
29
30 @Override
31 public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
32 throws SQLException {
33 ps.setObject(i, parameter);
34 }
35
36 @Override
37 public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
38 return rs.getObject(columnName, LocalDateTime.class);
39 }
40
41 @Override
42 public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
43 return rs.getObject(columnIndex, LocalDateTime.class);
44 }
45
46 @Override
47 public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
48 return cs.getObject(columnIndex, LocalDateTime.class);
49 }
50 }
一共也就这么几行。报错的是:
public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getObject(columnName, LocalDateTime.class);
}
这个方法。这里面发生了空指针。
这时候,我发现还是最细的堆栈信息。于是,我到最细的那一层。
也就是这一行。
看到这里,我明白了。实际上就是
getTimestamp(columnIndex)
这个获取时间的时候,数据库的submit_time字段没有值,然后cast的时候,发生了空指针。
三、问题解决
弄清了病症,也就好下药治疗了。我脑中浮现出两个想法:
1、让submit_time有值
这不简单吗,没值的时候有问题,我让你有值不就完事了。哈哈。开个玩笑,要结合业务处理的哈。这种方法明显不可取。
2、重写LocalDateTimeTypeHandler这个类,然后添加空指针判断。
这个方法不错,可以解决实际问题。
于是,我查看了其他类型的处理。
1 /**
2 * Copyright 2009-2018 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.apache.ibatis.type;
17
18 import java.sql.CallableStatement;
19 import java.sql.PreparedStatement;
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22
23 /**
24 * @author Clinton Begin
25 */
26 public class DoubleTypeHandler extends BaseTypeHandler<Double> {
27
28 @Override
29 public void setNonNullParameter(PreparedStatement ps, int i, Double parameter, JdbcType jdbcType)
30 throws SQLException {
31 ps.setDouble(i, parameter);
32 }
33
34 @Override
35 public Double getNullableResult(ResultSet rs, String columnName)
36 throws SQLException {
37 double result = rs.getDouble(columnName);
38 return result == 0 && rs.wasNull() ? null : result;
39 }
40
41 @Override
42 public Double getNullableResult(ResultSet rs, int columnIndex)
43 throws SQLException {
44 double result = rs.getDouble(columnIndex);
45 return result == 0 && rs.wasNull() ? null : result;
46 }
47
48 @Override
49 public Double getNullableResult(CallableStatement cs, int columnIndex)
50 throws SQLException {
51 double result = cs.getDouble(columnIndex);
52 return result == 0 && cs.wasNull() ? null : result;
53 }
54
55 }
看了Double的类型处理【1、wasNull设置标志位 2、获取的外面通过标志位判空】,我又看了Integer的类型处理。。。
1 /**
2 * Copyright 2009-2018 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.apache.ibatis.type;
17
18 import java.sql.CallableStatement;
19 import java.sql.PreparedStatement;
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22
23 /**
24 * @author Clinton Begin
25 */
26 public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
27
28 @Override
29 public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
30 throws SQLException {
31 ps.setInt(i, parameter);
32 }
33
34 @Override
35 public Integer getNullableResult(ResultSet rs, String columnName)
36 throws SQLException {
37 int result = rs.getInt(columnName);
38 return result == 0 && rs.wasNull() ? null : result;
39 }
40
41 @Override
42 public Integer getNullableResult(ResultSet rs, int columnIndex)
43 throws SQLException {
44 int result = rs.getInt(columnIndex);
45 return result == 0 && rs.wasNull() ? null : result;
46 }
47
48 @Override
49 public Integer getNullableResult(CallableStatement cs, int columnIndex)
50 throws SQLException {
51 int result = cs.getInt(columnIndex);
52 return result == 0 && cs.wasNull() ? null : result;
53 }
54 }
Integer的类型处理也是【1、wasNull设置标志位 2、获取的外面通过标志位判空】。
这真是好家伙,坐不住了。其他的类型都处理了,合着就我用的LocalDateTime没做空指针处理。这哪能行。重写,必须重写。
1 package com.vsofo;
2
3 import org.apache.ibatis.type.LocalDateTimeTypeHandler;
4 import org.springframework.stereotype.Component;
5
6 import java.sql.ResultSet;
7 import java.sql.SQLException;
8 import java.time.LocalDateTime;
9
10 @Component
11 public class LocalDateTimeTypeHandlerPlus extends LocalDateTimeTypeHandler {
12
13 @Override
14 public LocalDateTime getResult(ResultSet rs, String columnName) throws SQLException {
15 Object object = rs.getObject(columnName);
16 if(object==null){
17 return null;
18 }
19 return super.getResult(rs, columnName);
20 }
21 }
我直接继承,然后重写获取逻辑。
1、先获取数据库的值
2、判空,如果值为空,直接返回。
3、如果,值不为空,进行原来的强转逻辑。
重写之后,问题完美解决了。
=============================================================================
2021-08-18 最近在做另外一个项目的时候,也发现了这个问题,然而一样的步骤失效了,原因了,没有使用到这个自定义的LocalDateTime处理器。后面,我使用类替换的形式代替,解决了该问题。复制出原来的LocalDateTimeTypeHandler放在同一个包下 ,依旧是重写getResult(ResultSet rs, String columnName)方法解决了该问题,这使用的方式是直接替换了源码中的类。特此记录!
1 /**
2 * Copyright 2009-2019 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.apache.ibatis.type;
17
18 import java.sql.CallableStatement;
19 import java.sql.PreparedStatement;
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22 import java.time.LocalDateTime;
23
24 /**
25 * @since 3.4.5
26 * @author Tomas Rohovsky
27 */
28 public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
29
30 @Override
31 public LocalDateTime getResult(ResultSet rs, String columnName) throws SQLException {
32 Object object = rs.getObject(columnName);
33 if(object==null){
34 return null;
35 }
36 return super.getResult(rs, columnName);
37 }
38
39 @Override
40 public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
41 throws SQLException {
42 ps.setObject(i, parameter);
43 }
44
45 @Override
46 public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
47 return rs.getObject(columnName, LocalDateTime.class);
48 }
49
50 @Override
51 public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
52 return rs.getObject(columnIndex, LocalDateTime.class);
53 }
54
55 @Override
56 public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
57 return cs.getObject(columnIndex, LocalDateTime.class);
58 }
59 }
或者,是在字段上的注解@TableField设置该字段的类型转换器
/**
* 下发时间
*/
@TableField(value = "oper_time",typeHandler =LocalDateTimeTypeHandlerPlus.class)
private LocalDateTime operTime;
如果是xml的话,也有该配置
<resultMap id="myParam" type="com.base.slave.entity.UserStaticUserinfo" >
<result column="plat_oper_time" property="platOperTime" typeHandler="cn.lxw.LocalDateTimeTypeHandlerPlus"/>
</resultMap>
最后,还有一种方式,那就是把对应映射的字段类型改成java.util.Date类型,因为Date类型的源码已经做了特殊处理,只是这样要动到一些涉及的代码而增加一些风险。
四、心得体会
我觉着吧,框架是个好东西,能让我们开发项目事半功倍。但是如果给你埋了一个大雷,也可能让你事倍功半。
所以,我们用框架开发的过程中,应该多用多测多实践。找出影响项目的问题,然后解决它。
分享结束,谢谢大家观看哈!