后端疯了吧?每次做国际化都要写几十行代码,这个注解直接封神!
各位老铁后端做国际化有多折磨人你知道吗产品经理这个APP要出海版本说明要支持英语、日语、韩语...你打开项目一看数据库结构好家伙主表国际化表分开存的然后你开始写代码...传统的后端国际化有多恶心看看你现在的代码是不是这样写的GetMapping(/appVersion) public AppVersion getAppVersion(String appCode) { // 第一步查主表 AppVersion appVersion appVersionMapper.selectLastByAppCode(appCode); // 第二步查国际化表还要自己拼接条件 String locale LocaleContextHolder.getLocale().toLanguageTag(); AppVersionI18n i18n appVersionI18nMapper.selectByAppVersionIdAndLocale( appVersion.getId(), locale); // 第三步手动赋值20行代码就为了翻译一个字段 if (i18n ! null i18n.getReleaseNotes() ! null) { appVersion.setReleaseNotes(i18n.getReleaseNotes()); } return appVersion; }好家伙一个字段就要写3步10个字段就是30行如果是列表查询那更是灾难public ListAppVersion listAppVersions(String appCode) { // 查主表 ListAppVersion list appVersionMapper.selectList(appCode); // 收集所有ID ListLong ids list.stream() .map(AppVersion::getId) .collect(Collectors.toList()); // 批量查国际化表 MapLong, AppVersionI18n i18nMap appVersionI18nMapper.selectMapByIds(ids, locale); // 逐个赋值这是人写的代码 for (AppVersion av : list) { AppVersionI18n i18n i18nMap.get(av.getId()); if (i18n ! null) { av.setReleaseNotes(i18n.getReleaseNotes()); av.setRemark(i18n.getRemark()); // 还要继续赋值吗10个字段呢... } } return list; }看完这段代码我整个人都emo了...突然发现这个神仙注解今天在GitHub上冲浪偶然发现了一个叫 mybatis-i18n 的开源项目用了之后我直接跪了它的核心理念用注解告诉MyBatis这个字段需要国际化替换其他的全自动看看这代码你品你细品引入依赖一行dependency groupIdio.github.jqdi/groupId artifactIdmybatis-i18n-spring-boot-starter/artifactId version{latest.version}/version /dependency实体类加个注解齐活儿Data Accessors(chain true) public class AppVersion { private Integer id; private String appCode; private String version; // 重点来了就这一个注解搞定国际化替换 I18nField( i18nTable app_version_i18n, // 国际化表名 i18nColumn release_notes, // 国际化字段名 i18nRelatedColumn app_version_id, // 国际化表关联字段 relatedValueFromField id // 主表关联值来源 ) private String releaseNotes; // 查询结果自动被替换成对应语言 // 还有一个字段也要国际化继续加注解 I18nField( i18nTable app_version_i18n, i18nColumn remark, i18nRelatedColumn app_version_id, relatedValueFromField id ) private String remark; }然后你的Service代码变成了这样GetMapping(/appVersion) public AppVersion getAppVersion(String appCode) { // 就一行就一行 return appVersionMapper.selectLastByAppCode(appCode); // MyBatis自动拦截查询结果自动查国际化表自动赋值 // 你什么都不用管 } GetMapping(/listAppVersions) public ListAppVersion listAppVersions(String appCode) { // 列表查询也是一行 return appVersionMapper.selectList(appCode); // 自动批量处理100条数据也是一次查询搞定 }没有赋值代码没有手动翻译就问你香不香核心原理大揭秘后端程序员必须懂的这个插件的精髓在于 MyBatis拦截器 我扒了源码给大家讲解Intercepts({Signature(type StatementHandler.class, method query, args {Statement.class, ResultHandler.class})}) public class I18nFieldValueReplaceInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { // 第一步正常执行SQL查询查主表 Object result invocation.proceed(); // 第二步解析实体类上的I18nField注解 ListI18nFieldInfo i18nFieldInfoList i18nReplaceHandler.parserI18nMetadata(entityClass); // 第三步收集所有需要翻译的关联字段值比如所有app_version的id SetObject relatedFieldValueSet i18nReplaceHandler.collectRelatedFieldValue(list, i18nFieldInfo); // 第四步一次性批量查询国际化表重要避免N1 ListRelatedI18nValueMapping valueMappingList i18nDataProvider.getValueMapping(i18nFieldInfo, relatedFieldValueSet); // 第五步用查询到的翻译替换原始值 i18nReplaceHandler.replaceI18nFieldValue(list, i18nFieldInfo, valueMappingList); return result; // 返回已经替换好的结果 } }说人话就是步骤干了啥1执行原SQL查主表2看看哪些字段标了I18nField3收集所有需要翻译的ID4一次SQL查完所有翻译5自动赋值替换数据库设计经典主表国际化表-- 主表存原始数据一般是中文 CREATE TABLE app_version ( id bigint NOT NULL AUTO_INCREMENT, app_code varchar(32), version varchar(32), -- 版本号1.0.0 release_notes text, -- 发布说明存中文原文 PRIMARY KEY (id) ); -- 国际化表存各种语言翻译 CREATE TABLE app_version_i18n ( id int NOT NULL AUTO_INCREMENT, app_version_id int, -- 关联主表ID locale varchar(8), -- 语言编码zh-CN、en-US、ja-JP release_notes text, -- 翻译后的发布说明 UNIQUE KEY uniq (app_version_id,locale) );查询时根据请求头的 Accept-Language 自动匹配比如请求头 Accept-Language: en-US → 自动替换成英文请求头 Accept-Language: ja-JP → 自动替换成日文没翻译→ 保持原值不报错性能对比后端最关心的问题传统方案的问题// 列表查询100条数据 for (AppVersion av : list) { // 循环查国际化表 AppVersionI18n i18n i18nMapper.selectById(av.getId()); // 100次SQL }mybatis-i18n的方案// 100条数据 只有1次额外的国际化表查询 ListRelatedI18nValueMapping valueMappingList i18nDataProvider.getValueMapping(i18nFieldInfo, relatedFieldValueSet); // 1次SQL批量查性能差距100次SQL vs 1次SQL还想自定义没问题如果默认逻辑不满足可以实现自己的数据提供者Component public class CustomI18nDataProvider implements I18nDataProvider { Override public ListRelatedI18nValueMapping getValueMapping( I18nFieldInfo i18nFieldInfo, SetObject relatedFieldValueSet ) { // 比如从Redis缓存读取 return redisTemplate.opsForHash().multiGet( i18n: i18nFieldInfo.getI18nTable(), relatedFieldValueSet ); } }然后在注解里指定I18nField( i18nTable app_version_i18n, i18nColumn release_notes, i18nRelatedColumn app_version_id, relatedValueFromField id, i18nDataProvider CustomI18nDataProvider.class // 用我的自定义Provider ) private String releaseNotes;总结一下后端视角对比项传统做法mybatis-i18n代码量每个字段20行赋值一个注解列表查询N1次SQL最多N次每个注解1次修改字段改3处代码改1个注解出错率容易漏赋值自动处理可维护性维护困难清晰简洁用了这玩意儿之后我的Service代码瘦了90斤彩蛋GitHub地址 https://github.com/jqdi/mybatis-i18nGitee地址 https://gitee.com/jq_di/mybatis-i18n觉得好用的话Star走一波各位后端老铁你们项目中国际化是怎么做的有没有写过让人窒息的赋值代码评论区见