当前位置: 首页 > news >正文

mysql8.0.17以下驱动导致mybatis blob映射String乱码问题分析与解决

mysql8.0.17以下驱动导致blob映射String乱码问题分析与解决

  • 一、问题复现
  • 二、问题深究
  • 三、解决方法
    • 方法1
    • 方法2

一、问题复现

1、docker安装mysql8.0,并创建测试数据库及测试数据表

CREATE DATABASE `test` DEFAULT CHARACTER SET utf8mb4;
use test;
CREATE TABLE `test_content` (`id` int NOT NULL,`content` varchar(255) DEFAULT NULL,`b_content` blob,`lb_content` longblob,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2、使用mysql8.0.16驱动、mybatis、spring-boot搭建测试项目

2.1 实体类

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class TestContent {private int id;private String content;private String bContent;private String lbContent;
}

2.2 mapper

@Mapper
public interface TestContentMapper {@Select("select * from test_content where id = #{id}")TestContent selectOne(@Param("id") int id);@Select("select * from test_content where id = #{id}")Map<String, Object> selectOneMap(@Param("id") int id);@Update("update test_content set content=#{p.content}, b_content=#{p.bContent}, lb_content=#{p.lbContent} where id=#{p.id}")int update(@Param("p") TestContent p);@Insert("insert into test_content(id, content, b_content, lb_content) values (#{p.id}, #{p.content}, #{p.bContent}, #{p.lbContent})")int insert(@Param("p") TestContent p);}

2.3 配置文件

spring:datasource:type: com.zaxxer.hikari.HikariDataSourceurl: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTCusername: rootpassword: YOUR_PASSWORDdriver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:map-underscore-to-camel-case: true

2.4 单元测试类

@SpringBootTest(classes = Application.class)
public class TestBlobConvert {@Resourceprivate TestContentMapper testContentMapper;@Testpublic void testBlobConvert() {int insert = testContentMapper.insert(TestContent.builder().id(1).content("红红火火恍恍惚惚aaa").bContent("红红火火恍恍惚惚aaa").lbContent("红红火火恍恍惚惚aaa").build());System.out.println("insert = " + insert);Map<String, Object> map = testContentMapper.selectOneMap(1);map.forEach((key, value) -> System.out.println("key = " + key+ ", value = " + value));TestContent testContent = testContentMapper.selectOne(1);System.out.println("testContent = " + testContent);testContent.setContent(testContent.getContent() + "q");int update = testContentMapper.update(testContent);System.out.println("update = " + update);TestContent testContent1 = testContentMapper.selectOne(1);System.out.println("testContent1 = " + testContent1);}
}

执行单元测试发现复现成功;控制台打印结果为:

insert = 1
key = b_content, value = [B@b5c6a30
key = lb_content, value = [B@3bfae028
key = id, value = 1
key = content, value = 红红火火恍恍惚惚aaa
testContent = TestContent(id=1, content=红红火火恍恍惚惚aaa, bContent=红红火火恍恍惚惚aaa, lbContent=红红火火恍恍惚惚aaa)
update = 1
testContent1 = TestContent(id=1, content=红红火火恍恍惚惚aaaq, bContent=红红火火恍恍惚惚aaa, lbContent=红红火火恍恍惚惚aaa)

二、问题深究

起初想法是直接自定义typeHandler一把梭,后来决定debug看一看发现,在驱动com.mysql.cj.jdbc.result.ResultSetImpl中public String getString(int columnIndex)时乱码就已经产生;
继续往下com.mysql.cj.protocol.a.result.TextBufferRow#getValue
然后是com.mysql.cj.protocol.result.AbstractResultsetRow#decodeAndCreateReturnValue
再然后com.mysql.cj.protocol.a.MysqlTextValueDecoder#decodeByteArray
最后com.mysql.cj.result.StringValueFactory#createFromBytes

public String createFromBytes(byte[] bytes, int offset, int length, Field f) {return StringUtils.toString(bytes, offset, length, f.getEncoding());
}

一看这个f.getEncoding()是ISO-8859-1
随后看了下8.0.17版本这个位置的代码,做了处理,使用url上的characterEncoding编码进行解码

public String createFromBytes(byte[] bytes, int offset, int length, Field f) {return StringUtils.toString(bytes, offset, length,f.getCollationIndex() == CharsetMapping.MYSQL_COLLATION_INDEX_binary ? this.pset.getStringProperty(PropertyKey.characterEncoding).getValue(): f.getEncoding());}

驱动改成8.0.17后清理数据执行一下看看

insert = 1
key = b_content, value = [B@5042e3d0
key = lb_content, value = [B@1c34365c
key = id, value = 1
key = content, value = 红红火火恍恍惚惚aaa
testContent = TestContent(id=1, content=红红火火恍恍惚惚aaa, bContent=红红火火恍恍惚惚aaa, lbContent=红红火火恍恍惚惚aaa)
update = 1
testContent1 = TestContent(id=1, content=红红火火恍恍惚惚aaaq, bContent=红红火火恍恍惚惚aaa, lbContent=红红火火恍恍惚惚aaa)

好的,这样我们就有了一种不用动代码的解决方法

三、解决方法

方法1

升级mysql驱动至8.0.17或以上版本

方法2

驱动不想或不能升级,那自定义typehandler是一个好的解决方法,在mybatis中debug发现,数据字段为blob、longblob、tinyblob时,JDBCType为JdbcType.LONGVARBINARY
以下是自定义TypeHandler

@MappedJdbcTypes(JdbcType.LONGVARBINARY)
@MappedTypes(String.class)
public class BlobToStringTypeHandler extends BaseTypeHandler<String> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setBytes(i, parameter.getBytes(StandardCharsets.UTF_8));}@Overridepublic String getNullableResult(ResultSet rs, String columnName) throws SQLException {return getStringFromBlob(rs.getBlob(columnName));}@Overridepublic String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return getStringFromBlob(rs.getBlob(columnIndex));}@Overridepublic String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return getStringFromBlob(cs.getBlob(columnIndex));}private String getStringFromBlob(Blob blob) throws SQLException {if (blob == null) return "";byte[] bytes = blob.getBytes(1, (int) blob.length());return new String(bytes, StandardCharsets.UTF_8);}
}

自定义后需要增加配置使其全局生效

mybatis:type-handlers-package: typeHandler所在包名

如果不想改配置文件,想通过代码的方式全局生效可以改一下sqlSessionFactory

@Configuration
public class MybatisConfig implements InitializingBean {@Autowiredprivate SqlSessionFactory sqlSessionFactory;@Overridepublic void afterPropertiesSet() {sqlSessionFactory.getConfiguration().getTypeHandlerRegistry().register("typeHandler所在包名");}
}

当然也可以在某个字段上单独生效,在查询的方法上增加

@Results(@Result(column = "b_content", property = "bContent",jdbcType = JdbcType.LONGVARBINARY, javaType = String.class,typeHandler = BlobToStringTypeHandler.class)
)







来点涩话
花点时间找找是否有更好的方法,结果会让你惊喜

相关文章:

  • gis系统中如何提高shp大文件加载效率
  • B端可视化像企业数据的透视镜,看清关键信息
  • C 语 言 --- 指 针 3
  • jangow靶机笔记(Vulnhub)
  • 深度学习数据预处理:Dataset类的全面解析与实战指南
  • 在Windows创建虚拟环境如何在pycharm中配置使用
  • 【滑动窗口】最⼤连续 1 的个数 III(medium)
  • MLA(多头潜在注意力)原理概述
  • leetcode 2563. 统计公平数对的数目 中等
  • turtle库绘制进阶图形
  • 【Canvas与旗帜】标准英国米字旗
  • 深入解析进程与线程:区别、联系及Java实现
  • 【大模型框架】LLAMA-FACTORY使用总结
  • 【工控基础】工业相机设置中,增益和数字增益有什么区别?
  • 网络爬虫和前端相关知识
  • 数据结构——栈以及相应的操作
  • 健康养生:拥抱美好生活的基石
  • 9 C 语言变量详解:声明与定于、初始化与赋值、printf 输出与 scanf 输入、关键字、标识符命名规范
  • 嵌入式exfat-nofuse文件系统移植和使用
  • Java核心技术卷第三章
  • 一中国公民在日本滑雪场意外死亡,我领馆发布提醒
  • 白宫慌了!将设工作组紧急处理对中国加征关税危机
  • 云南昆明市副市长戴惠明已任市委常委、秘书长
  • 中国正在俄罗斯国内生产武器?外交部:坚决反对无端指责和政治操弄
  • 辽宁一季度GDP为7606.9亿元,同比增长5.2%
  • 地铁口被吐槽像棺材?杭州地铁公司回应:是一个标志性出入口