MySQL字段类型和Java实体类型的转换问题

1,715 阅读6分钟

1. MySQL的字段类型和Java类型的对应关系

MySQL Connector/J( MySQL官方JDBC驱动程序)在处理MySQL数据类型和Java数据类型之间的转换方面非常灵活。

  • 通常,任何MySQL数据类型都可以转换为java.lang.String。
  • 可以将字符串和任何数字类型转换为任何Java数字类型,尽管可能会发生舍入、溢出或精度损失。

从Connector/J 3.1.0开始,JDBC驱动程序会发出警告或引发JDBC规范所要求的DataTruncation异常(数据截断异常),除非通过使用jdbcCompliantTruncation属性并将其设置为false来将连接配置为不这样做。

MySQL和Java类型之间始终保证有效的转换

MySQL类型可转换的Java类型
char, varchar, blob, text, enum, setjava.lang.String ,
java.io.InputStream,
java.io.Reader,
java.sql.Blob,
java.sql.Clob
float, real, double, precision, numeric, decimal , tinyint, smallint, mediumint, integer, bigintjava.lang.String ,
java.lang.Short,
java.lang.Integer,
java.lang.Long,
java.lang.Double, java.math.BigDecimal
date, time, datetime, timestampjava.lang.String ,
java.sql.Date,
java.sql.Timestamp

注意 如果选择的Java数字数据类型的精度或容量低于要转换的MySQL数据类型,则可能会发生舍入、溢出或精度损失。

案例 image-20220719000520264

2. 二者的对应关系(仅仅是推荐)

MySQL类型对应Java类型用途
tinyint如果配置属性 tinyInt1isBit 设置为 true (默认)并且存储大小为1,则为 java.lang.Boolean,否则是 java.lang.Integer。
intjava.lang.Integer, 如果是 unsigned 则是 java.lang.Long
integerjava.lang.Integer, 如果是 unsigned 则是 java.lang.Long
bigintjava.lang.Long, 如果是 unsigned 则是 java.math.BigInteger
doublejava.lang.Double
floatjava.lang.Float
decimaljava.math.BigDecimal金额
numericJava
charjava.lang.String (除非列的字符集是BINARY,否则返回byte[]。)
varcharjava.lang.String (除非列的字符集是BINARY,否则返回byte[]。)
datejava.sql.Date日期
timejava.sql.Time时间 HH:MM:SS
timestampjava.sql.Timestamp时间戳 YYYY-MM-DD HH:MM:SS
datetimejava.sql.TimestampYYYY-MM-DD HH:MM:SS
blobbyte[]
textjava.lang.String
longtextjava.lang.String
enumjava.lang.String

表格中的对应关系仅仅是推荐的,具体如何映射可以根据业务需求进行变更 ,并不是定死就这样,如:Mysql中字段类型是Decimal类型,在满足业务需求的情况下,可以在Java类型中可以使用Double在对应的数据接收。

3. Sql中decimal、float、double类型的区别与用法

3.1 三者的区别介绍

float:浮点型,含字节数为4,32bit,数值范围为-3.4E383.4E38(7个有效位) double:双精度实型,含字节数为8,64bit数值范围-1.7E3081.7E308(15个有效位) decimal:数字型,128bit,不存在精度损失,常用于银行帐目计算。(28个有效位)

3.2 decimal的详细介绍

定义

decimal(a,b)

参数说明

  • a 指定指定小数点左边和右边可以存储的十进制数字的最大个数,最大精度38。
  • b 指定小数点右边可以存储的十进制数字的最大个数。小数位数必须是从 0 到 a之间的值。默认小数位数是 0。

3.3 对比案例


float f = 345.98756f; --结果显示为345.9876,只显示7个有效位,对最后一位数四舍五入。
 
double d=345.975423578631442d; --结果显示为345.975423578631,只显示15个有效位,对最后一位四舍五入。
 
--注:floatdouble的相乘操作,数字溢出不会报错,会有精度的损失。
 
decimal dd=345.545454879..--可以支持28位,对最后一位四舍五入。
 
--:当对decimal类型进行操作时,数值会因溢出而报错。

4. Mybatis中JdbcType的作用

个人疑问的由来:今天在学习公司项目代码的时候惊奇的发现Mybatis中的映射类型和Java中的映射类型不一致,Mysql中设置的字段类型是Decimal类型,但是在Java中实体对应属性的数据类型缺选择使用的是Double作为对应,并且在查看代码的时候发现属性映射中有使用到JdbcType这个字段,一开始怀疑是JdbcType的作用,查阅了资料有了以下的答案

对于如下一条insert语句(这里只是做测试,实际中肯定不会这么写),如果我们的age传的空,那么对于mysql数据库可以正常插入,对于oracle数据库,会报错“无效的列类型”。

也就是说对于mysql数据库的插入来说,jdbcType是没用的,oracle数据库是有用的(值为null时有用)。

insert into test(id, name, age)
values (7, #{name,jdbcType=VARCHAR},#{age})

我没有测试更新的情况,但是我猜测结论应该是一样的。**对于查询操作,在mybatis配置时也可以指定jdbcType,这里的jdbcType至少在我测试时也没发现有什么作用(mysql, oracle都是),**当然可能只是我没发现,不代表没有作用,不作为编码建议。

4.1 问题产生

insert操作时jdbcType的作用测试这里只分析insert操作时,为什么mysql和oracle表现会不同。

我们用如下代码来进行测试。

mybatis mapper中的sql如下,因为oracle自增主键比较麻烦,这里为了方便写死了id,仅测试用。

<insert id="testJdbcType">
    insert into t_user(id, name, age)
    values (7, #{name,jdbcType=VARCHAR}, #{age})
</insert>

java测试代码如下,这里就不贴全部代码了,用过mybatis应该都能看懂。

User user = new User();
user.setName("zzj");
user.setAge(null);
mapper.testJdbcType(user);

然后我们分别连接mysql和oracle

查看执行结果

  • mysql可以正常插入
  • 但是oracle数据库会报如下错误(Error setting null for parameter #2 with JdbcType OTHER . java.sql.SQLException: 无效的列类型) img

4.2 原因分析

mybatis中的增删改查基本都是用PreparedStatement来实现的,PreparedStatement有个setNull方法 ,方法签名如下:

void setNull(int parameterIndex, int sqlType) throws SQLException;

当我们要插入的值是null时,mybatis就会调用PreparedStatement的setNull来设置对应的参数。

我们跟踪下mybatis执行过程,可以发现是在org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters这个方法中进行sql的参数设置。我们断点执行到这个方法,如下图 img

  • 从上图可以发现,由于我们age参数没有设置jdbcType,JdbcType jdbcType = parameterMapping.getJdbcType(); ——> 这一行代码获取到的jdbcType为null,这时就会进入到if里,把jdbcType赋值为默认值,默认值是JdbcType.OTHER。
  • 然后接着执行第87行typeHandler.setParameter(ps, i + 1, value, jdbcType); ——> 这一行就是对sql中的第二个参数进行设置,继续向下调试会发现 ——> 最终会调用PreparedStatement的setNull方法去设置null值,由于java.sql.PreparedStatement是个接口,具体怎么setNull,是由不同的数据库驱动程序去实现的。

我们先来看mysql的setNull方法,如下图。 img

可以看到,尽管第2个参数为sqlType,但是mysql实现中并没有用到这个参数,所以jdbcType的值对mysql是没有什么用的。

我们再来看oracle是如何实现setNull的

如下图,由于下载不到源码,所以oracle的代码看起来没那么直观,不过对于分析完全够用了。

img

img

  • 从上图可以看出,oracle的setNull会调用setNullCritical,而setNullCritical中又会用jdbcType这个参数调用getInternalType ——> 这个方法内部是一长串的switch case判断,可以发现对于找不到的jdbcType,这个方法会抛出异常,这个异常就是上面我们做测试时的异常来源。
  • 而我们知道,上面mybatis把jdbcType默认设置成了JdbcType.OTHER,这个值oracle是不支持的,所以报错了。

4.3 总结

有了上面的分析,我们就知道mybatis在插入数据时是怎么使用jdbcType的了,由此也可得出结论,对于插入操作,如果某个字段值为null,jdbcType的设置对mysql数据库没什么用,对oracle数据库有用。

资料来源

MySQL字段类型与Java实体类类型对应转换关系

mybatis jdbcType的作用,无效的列类型错误解决办法

mybatis jdbcType的作用,无效的列类型错误解决办法