news 2026/9/6 8:36:14

20-BaseEntity基类

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
20-BaseEntity基类

20-BaseEntity:333行的ActiveRecord基类

Row是Map行,BaseEntity是类型化实体——同一个脏标记协议的两种载体。save()一个方法按_t分发insert/update/delete到MyBatis Mapper、反射字段读写、自动类型转换、fromMap/toMap协议序列化。这篇拆完333行,重点讲它和Row的协议同构与载体差异。

文章目录

  • 20-BaseEntity:333行的ActiveRecord基类
    • 一、协议字段从Map变成注解字段
    • 二、save():18行的ActiveRecord
    • 三、setItemValue:反射版脏标记
    • 四、findField/getAllFields:沿继承链爬
    • 五、fromMap/toMap:协议的序列化边界
    • 六、BaseEntity vs Row:一张对照表

源码:browise-data/src/main/java/com/browise/data/BaseEntity.java(333行)


一、协议字段从Map变成注解字段

publicabstractclassBaseEntity{@TableField(exist=false)// MyBatis-Plus:这不是数据库列protectedRowStatus_t=RowStatus.NONE;@TableField(exist=false)protectedfinalMap<String,Object>_o=newLinkedHashMap<>();@TableField(exist=false)protectedStringinsertMethod="insert";@TableField(exist=false)protectedStringupdateMethod="updateById";@TableField(exist=false)protectedStringdeleteMethod="deleteById";@TableField(exist=false)protectedLongbaz001;// 社保标准主键列名}

三个设计点:

@TableField(exist=false)——_t/_o/方法名/主键都不是数据库列,必须告诉MyBatis-Plus别把它们拼进SQL。协议字段寄生在实体里又不污染持久化——注解是隔离层。

三个method名可覆盖——默认insert/updateById/deleteById是MyBatis-Plus BaseMapper的标准方法名;实体有自定义SQL时setUpdateMethod(“updateLabel”)换名字(Aa10字典实体就这么干——它没有@TableId,MP的updateById不可用,换成自定义@Update SQL)。

getMapperClass()是唯一的抽象方法——子类告诉基类"我的Mapper是谁":

@TableName("SYS_USER")publicclassSysUserextendsBaseEntity{privateStringpsnName;// getter/setter...@OverridepublicClass<?>getMapperClass(){returnSysUserMapper.class;}}

二、save():18行的ActiveRecord

publicvoidsave(){MyBatisHelperhelper=BrowiseContext.getHelper();// ①静态上下文取helperif(helper==null)thrownewRuntimeException("MyBatisHelper 未注入");Class<?>mapperClass=getMapperClass();if(mapperClass==null)thrownewRuntimeException("getMapperClass() 返回 null");if(isInsert())helper.insert(mapperClass,insertMethod,this);// ②按_t分发elseif(isUpdate())helper.update(mapperClass,updateMethod,this);elseif(isDelete())helper.delete(mapperClass,deleteMethod,this);resetUpdate();// ③成功后归零}

这就是ActiveRecord的全部——user.save()一行完成三种持久化,按_t自动选路。对比传统写法:

// 传统——调用方判断状态、选Mapper方法if(user.isNew())userMapper.insert(user);elseif(user.isDirty())userMapper.updateById(user);

状态判断的知识被吸收进基类——调用方只说"保存它",不说"怎么保存"。

注意③resetUpdate()无条件执行——save()内部没有事务。事务在调用方(Spring @Transactional的Service方法)。如果外层事务最终回滚,实体的_t已经归零但数据库没变——内存和数据库短暂失配。约定:事务边界内不要再读实体状态做判断,以数据库为准。


三、setItemValue:反射版脏标记

publicvoidsetItemValue(Stringfield,Objectvalue){Fieldf=findField(field);if(f==null)return;f.setAccessible(true);if(_t==RowStatus.NONE){Objectold=f.get(this);if(old!=null&&!old.equals(value)){_o.put(field,old);// 旧值非null且真的变了才记}_t=RowStatus.UPDATE;}Objectconverted=convertType(f.getType(),value);// 自动类型转换f.set(this,converted);}

和Row.setItemValue的两处差异:

①旧值记录多一个条件——Row版只要data.containsKey(key)就记;BaseEntity版要求old != null && !old.equals(value)字段原值是null的不记进_o——reject恢复时_o里没有的字段保持data值,null字段恢复后还是null(原值就是null),跳过记录省内存。值没变(equals)的也不记——没变化的赋值不算脏,避免"set了同值导致行莫名变UPDATE"。

②convertType自动转换——HTTP来的值全是字符串,实体字段是Integer/Date:

privateObjectconvertType(Class<?>type,Objectvalue){if(type==Integer.class||type==int.class)returnInteger.parseInt(s);if(type==Boolean.class){if("1".equals(s)||"true".equalsIgnoreCase(s))returntrue;// 政务码值习惯if("0".equals(s)||"false".equalsIgnoreCase(s))returnfalse;}if(type==Date.class&&valueinstanceofLong)returnnewDate((Long)value);// ...}

“1”/"0"当boolean——政务系统数据库里状态列就是VARCHAR存的0和1,这层转换是业务习惯的适配。

异常处理是catch (Exception ignored) {}——反射失败/类型转换失败静默吞掉。这个取舍有争议:吞异常保证批量fromMap不会因一个坏字段崩,但坏字段静默丢值难排查。实测的折中——开发期看日志(convertType失败前有debug日志),生产期保批量导入不中断。


四、findField/getAllFields:沿继承链爬

privateFieldfindField(Stringname){Class<?>cls=this.getClass();while(cls!=null&&cls!=BaseEntity.class){// 到BaseEntity为止try{returncls.getDeclaredField(name);}catch(NoSuchFieldExceptione){cls=cls.getSuperclass();}}returnnull;}

停在中断点BaseEntity.class——子类可以有中间父类(Person→SysUser),反射沿链向上找到BaseEntity为止。协议字段(_t/_o)不在搜索范围——它们属于BaseEntity,getItemValue(“_t”)返回null——协议字段只能通过getRowStatus()/getOriginals()读,不混入业务字段通道。

getAllFields同构——收集全部非静态字段(同样不含BaseEntity的),供toMap序列化。


五、fromMap/toMap:协议的序列化边界

publicvoidfromMap(Map<String,Object>map){for(Map.Entry<String,Object>entry:map.entrySet()){Stringkey=entry.getKey();if("_t".equals(key)){// 协议字段特殊处理_t=RowStatus.fromCode(((Number)val).intValue());continue;}if("_o".equals(key)){_o.putAll((Map)val);continue;}setItemValue(key,val);// 业务字段走反射+类型转换+脏标记}}

fromMap是HTTP JSON→实体的入口——前端提交的{_t:3, psnName:"李四", _o:{...}}反序列化:_t/_o按协议还原,业务字段经setItemValue(自动类型转换在这里发生)。

一个隐蔽点:fromMap走setItemValue会触发脏标记——如果实体的_t先被设为UPDATE再fromMap,字段赋值不会二次记_o(状态已是UPDATE,第17篇状态机);如果_t最后才设——前面的字段赋值会触发NONE→UPDATE转并记_o!key的遍历顺序影响_o内容(LinkedHashMap保序,JSON解析器按出现序)。约定:协议JSON里_t放最前面——前端useCenter的toPayload保证这一点。


六、BaseEntity vs Row:一张对照表

RowBaseEntity
字段载体LinkedHashMap类型化Java字段
类型转换无(全是Object)convertType自动转
_o记录条件containsKey即记非null且值变了才记
持久化无(靠commonSave)save()直接调Mapper
适用元数据通道/动态表低代码模式/固定实体
旧值null记录null不记录

两套载体一个协议——前端永远只发_t/_o,后端按场景选载体:动态查询(EA引擎)落Map走Row,固定业务实体继承BaseEntity。转换器是fromMap/toMap——Row.getMap()灌进BaseEntity.fromMap(),协议无损过桥。


✅ 亮点:333行ActiveRecord基类拆成协议字段寄生(@TableField exist=false)、save()18行按_t分发、反射版脏标记与Row版的两处差异(null不记/同值不算脏)、convertType的政务0/1布尔适配、fromMap的key顺序影响_o的隐蔽约定。适合做实体基类设计的人。扩展方向:第24篇MyBatisHelper、第30篇CryptoAspect对BaseEntity的加解密拦截、第53篇审计监听。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 9:09:39

校园大数据平台搭建实录:实时数仓与数据服务的工程实现

校园大数据平台搭建实录&#xff1a;实时数仓与数据服务的工程实现校园大数据平台的技术选型争议很多&#xff1a;要不要上 Hadoop&#xff1f;实时数仓怎么建&#xff1f;API 服务怎么做&#xff1f;本文从一个实际项目出发&#xff0c;详解校园大数据平台从数据采集到数据服务…

作者头像 李华
网站建设 2026/9/6 3:04:25

【信息科学与工程学】计算机科学与自动化——第一百三十五篇 开发框架篇 系列三 Hibernate框架 01

一级缓存(Session缓存) 二级缓存(SessionFactory缓存) 延迟加载(Lazy Loading) 脏检查(Dirty Checking) 乐观锁(Optimistic Locking) 悲观锁(Pessimistic Locking) HQL查询 级联操作(Cascade) 继承映射策略 批量处理 一级缓存的缺陷:内存占用,可用公式表示缓存…

作者头像 李华
网站建设 2026/9/5 3:04:39

制造业物料控制实战:三张核心管理表构建数据驱动决策系统

这次我们来看一个在制造业和供应链领域被称为“物控必杀技”的实战方法&#xff1a;三张核心管理表。这不是一个软件工具&#xff0c;而是一套经过验证的、用于物料控制&#xff08;Material Control&#xff09;的表格化管理系统。对于物料计划员、仓库主管、生产调度等岗位而…

作者头像 李华
网站建设 2026/9/6 11:34:59

Windows 搭建本地 AI 智能体 OpenClaw,告别复杂环境配置

Windows 部署 OpenClaw 3.1.0&#xff5c;搭建属于自己的桌面 AI 数字员工 前言 很多人体验过各类对话大模型&#xff0c;但大多只能做问答&#xff0c;没办法直接操作本地电脑文件、浏览器。OpenClaw&#xff0c;圈内俗称小龙虾 AI&#xff0c;是一款可以直接接管电脑执行任…

作者头像 李华