MyBatis Flex Active Record 模式下公共字段抽离与自动填充的一种实践
最近在使用Mybatis Flex开发自己的新项目,其中基础功能的Active Record模式我十分喜欢。使用这个模式我们保存数据就可以这样子做
▼java复制代码Account.create() .setUserName("张三") .setAge(18) .setBirthday(new Date()) .save();
简单来说,就是你可以直接在实体类调用方法去操控数据库。也就是对于一些简单的CRUD,你可以不用去引入service和mapper。就可以直接对数据进行操作。
实现方式就是让Entity去继承Model即可
@Table标记了实体类对应的数据表。@Data为我们生成了 setter/getter、toString、equals、hashCode 等方法, 其中staticConstructor = "create"为我们创建了一个create()静态方法用于链式调用。@Accessors(chain = true)为我们开启了return this;这样既可以被序列化,又可以链式调用。
▼java复制代码@Table("tb_account") @Accessors(chain = true) @Data(staticConstructor = "create") public class Account extends Model<Account> { @Id(keyType = KeyType.Auto) private Long id; private String userName; private Integer age; private Date birthday; }
但是有一个问题啊,大部分实体类都有createTime、updateTime之类的公共字段,有时候为了方便管理。需要建立一个BaseEntity让所有的实体类去继承这个类。继承了Model还怎么继承BaseEntity。
如果只是属性无法继承还好,而Mybatis Flex的公共字段填充是非常依赖这些实体类要有一个公共的实体父类的。不然要多写非常多的代码,就比如配置全局自动填充的时候:
官方示例代码:
▼java复制代码MyInsertListener insertListener = new MyInsertListener(); MyUpdateListener updateListener = new MyUpdateListener(); MySetListener setListener = new MySetListener(); FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig(); //为 Entity1 和 Entity2 注册 insertListner config.registerInsertListener(insertListener, Entity1.class, Entity2.class); //为 Entity1 和 Entity2 注册 updateListener config.registerUpdateListener(updateListener, Entity1.class, Entity2.class); //为 Entity1 和 Entity2 注册 setListener config.registerSetListener(setListener, Entity1.class, Entity2.class);
如果有公共的父类只要传一个类就可以了。没有只能每建一个实体加一个,太麻烦了。
解决方案
因为Java不能多继承,那改造为单继承不就好了。让BaseEntity去继承Model
代码如下
▼java复制代码import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Id; import com.mybatisflex.annotation.KeyType; import com.mybatisflex.core.activerecord.Model; import com.mybatisflex.core.keygen.KeyGenerators; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; /** * 自定义基础Model(继承MyBatis Flex原生Model + 封装公共属性) * @author 八月-千 * @param <T> 业务实体类型,保持和原生Model一致的泛型约束 */ @Data @Accessors(chain = true) @EqualsAndHashCode(callSuper = false) public class BaseModel<T extends Model<T>> extends Model<T> implements Serializable { @Serial private static final long serialVersionUID = 1L; /** * 主键ID(雪花算法) */ @Id(keyType = KeyType.Generator, value = KeyGenerators.snowFlakeId) private Long id; /** * 创建时间 */ private LocalDateTime createTime; /** * 创建人 */ private Long createBy; /** * 更新时间 */ private LocalDateTime updateTime; /** * 修改人 */ private Long updateBy; /** * 逻辑删除标识(1=删除,0=未删除) */ @Column(isLogicDelete = true) private Integer isDelete; // ========== 手动重写setter,返回子类类型 T 避免链式调用问题 ========== public T setId(Long id) { this.id = id; // 强制转换为子类类型(因为T是BaseModel的子类) return (T) this; } public T setCreateTime(LocalDateTime createTime) { this.createTime = createTime; return (T) this; } public T setCreateBy(Long createBy) { this.createBy = createBy; return (T) this; } public T setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; return (T) this; } public T setUpdateBy(Long updateBy) { this.updateBy = updateBy; return (T) this; } public T setIsDelete(Integer isDelete) { this.isDelete = isDelete; return (T) this; } }
此时,实体类只需要这样继承即可:
▼java复制代码import com.mybatisflex.annotation.Table; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import top.yoojia.aiscriptgenerate.model.entity.base.BaseModel; import java.time.LocalDateTime; /** * 用户 实体类。 * * @author 八月-千 */ @Accessors(chain = true) @Data(staticConstructor = "create") @EqualsAndHashCode(callSuper = true) @Table("user") public class User extends BaseModel<User> { /** * 账号 */ private String account; /** * 密码 */ private String password; /** * 用户昵称 */ private String name; /** * 用户头像 */ private String avatar; /** * 用户简介 */ private String profile; /** * 用户角色:user/admin */ private String role; /** * 编辑时间 */ private LocalDateTime editTime; }
全局填充配置就可以这样写
▼java复制代码@Configuration public class MybatisFlexConfig extends FlexGlobalConfig { @PostConstruct public void initFlexGlobalConfig() { FlexGlobalConfig defaultConfig = FlexGlobalConfig.getDefaultConfig(); MybatisInsertListener mybatisInsertListener = new MybatisInsertListener(); MybatisUpdateListener mybatisUpdateListener = new MybatisUpdateListener(); defaultConfig.registerInsertListener(mybatisInsertListener, BaseModel.class); defaultConfig.registerUpdateListener(mybatisUpdateListener, BaseModel.class); } }
为什么要重写set方法?
因为使用链式调用的情况下,set方法会返回类自己,如果实体类调用了父类的set方法,就会返回父类,之后你就无法调用实体类的其他方法了,比如:

总结
不过我还是觉得这个解决方案不够优雅,如果有更好的欢迎交流。
相关官方文档地址:
Active Record模式:https://mybatis-flex.com/zh/base/active-record.html
数据自动填充:https://mybatis-flex.com/zh/core/table.html#oninsert
代码生成器(需要把EntityConfig的setWithActiveRecord以及GlobalConfig的enableTableDef配置为true):https://mybatis-flex.com/zh/others/codegen.html#entity-%E7%94%9F%E6%88%90%E9%85%8D%E7%BD%AE-entityconfig

