【实体类】分层设计
- 【一】实体类的PO、VO、DO、DAO、BO、DTO、POJO有什么区别
- 【1】PO(Persistent Object)
- 【2】VO(View Object)
- 【3】DO(Domain Object)
- 【4】DAO(Data Access Object)
- 【5】BO(Business Object)
- 【6】DTO(Data Transfer Object)
- 【7】POJO(Plain Old Java Object)
- 【二】领域模型中的实体类
- 【三】设计案例
- 【1】视图层:IndDimensionCreateRequest(VO)
- 【2】服务层:IndDimension(DTO)
- 【3】持久层:IndDimensionPO(PO)
- 【四】mybatisplus持久层的注解解析
- 【1】@TableName(value = "IND_DIMENSION")
- 【2】@TableField("name")
- 【3】@TableId(value = "ID", type = IdType.ASSIGN_ID)
- 【4】@EqualsAndHashCode(of = "id", callSuper = false)
- 【5】@TableField(value = "CRT_DT_TM", fill = FieldFill.INSERT)
- 【6】@TableField(value = "UPD_DT_TM", fill = FieldFill.INSERT_UPDATE)
- 【7】@TableLogic
- 【8】@Version 乐观锁注解
- 【9】综合使用案例
【一】实体类的PO、VO、DO、DAO、BO、DTO、POJO有什么区别
【1】PO(Persistent Object)
(1)定义:持久化对象,通常对应数据库中的一条记录,用于与数据库进行交互,实现数据的持久化操作。
(2)特点
与数据库表对应:其属性与数据库表中的字段一一对应,主要用于在数据库和应用程序之间传递数据。
数据持久化:侧重于数据的持久化存储,通过 ORM(对象关系映射)框架,如 Hibernate、MyBatis 等,实现对象与数据库表之间的映射和数据交互。
【2】VO(View Object)
(1)定义:视图对象,用于将数据呈现给前端视图层,通常用于展示数据给用户。
(2)特点
数据展示:主要用于将数据从后端传递到前端,以满足前端页面展示的需求。它可以根据前端的需要,对数据进行格式化、组合等处理。
与前端交互:与前端页面的展示逻辑紧密相关,可能包含一些前端需要的额外属性或方法,如用于展示的数据格式转换、计算属性等。
【3】DO(Domain Object)
(1)定义:领域对象,是对业务领域中概念的抽象表示,用于封装业务逻辑和业务数据。
(2)特点
业务逻辑封装:它不仅仅是数据的载体,还包含了与该领域相关的业务逻辑和操作方法。这些方法用于处理和操作对象的状态,实现业务规则和流程。
领域模型核心:在领域驱动设计(DDD)中,DO 是领域模型的核心组成部分,它代表了业务领域中的核心概念和实体,与业务流程和规则紧密结合。
【4】DAO(Data Access Object)
(1)定义:数据访问对象,主要用于封装对数据库的访问操作,提供统一的数据访问接口。
(2)特点
数据访问封装:将数据库的增删改查等操作封装在 DAO 类中,隐藏了数据库访问的具体实现细节,如 SQL 语句的编写、数据库连接的管理等。
解耦数据层和业务层:使业务层与数据层之间的耦合度降低,业务层只需要调用 DAO 提供的接口方法,而不需要关心数据是如何存储和获取的,提高了代码的可维护性和可扩展性。
【5】BO(Business Object)
(1)定义:业务对象,是介于业务逻辑层和数据访问层之间的对象,用于处理业务逻辑和协调不同的业务操作。
(2)特点
业务逻辑处理:主要负责处理复杂的业务逻辑,它可以调用多个 DAO 方法,组合和协调不同的数据访问操作,以完成一个完整的业务功能。
事务处理:通常在 BO 中进行事务管理,确保业务操作的完整性和一致性。例如,在一个涉及多个数据库操作的业务流程中,BO 可以保证这些操作要么全部成功,要么全部回滚。
【6】DTO(Data Transfer Object)
(1)定义:数据传输对象,用于在不同层之间传输数据,通常用于跨系统或跨模块的数据传递。
(2)特点
数据传输:主要目的是在不同的系统或模块之间传递数据,它可以将多个相关的数据封装在一个对象中,方便数据的传输和共享。
轻量级对象:通常只包含数据属性,不包含复杂的业务逻辑,具有较小的内存占用和较高的传输效率。
【7】POJO(Plain Old Java Object)
(1)定义:简单的 Java 对象,是一种普通的 Java 类,没有特定的框架或技术限制,仅仅包含一些属性和对应的 getter/setter 方法。
(2)特点
通用性:不依赖于任何特定的框架或技术,具有很高的通用性和可移植性,可以在各种 Java 项目中使用。
基础数据载体:可以作为其他对象的基础,如 PO、VO、DO 等都可以是 POJO 的一种扩展或特殊应用。
【二】领域模型中的实体类
用户发出请求(可能是填写表单),表单的数据在展示层被匹配为VO。
展示层把VO转换为服务层对应方法所要求的DTO,传送给服务层。
服务层首先根据DTO的数据构造(或重建)一个DO,调用DO的业务方法完成具体业务。
服务层把DO转换为持久层对应的PO(可以使用ORM工具,也可以不用),调用持久层的持久化方法,把PO传递给它,完成持久化操作。
对于一个逆向操作,如读取数据,也是用类似的方式转换和传递。
实际工作中,一般使用VO、DTO、DO、PO足以。
领域模型中的实体类分为四种类型:VO、DTO、DO、PO,各种实体类用于不同业务层次间的交互,并会在层次内实现实体类之间的转化。
业务分层为:视图层(VIEW+ACTION),服务层(SERVICE),持久层(DAO)
1、用户发出请求(可能是填写表单),表单的数据在展示层被匹配为VO
2、 展示层把VO转换为服务层对应方法所要求的DTO,传送给服务层。
3、服务层首先根据DTO的数据构造(或重建)一个DO,调用DO的业务方法完成具体业务。
4、 服务层把DO转换为持久层对应的PO(可以使用ORM工具,也可以不用),调用持久层的持久化方法,把PO传递给它,完成持久化操作。
5、对于一个逆向操作,如读取数据,也是用类似的方式转换和传递,略。
【三】设计案例
【1】视图层:IndDimensionCreateRequest(VO)
@Getter
@Setter
@ToString
@Schema(description = "创建请求类")
public class IndDimensionCreateRequest {
/**
* 维度名称
*/
@NotNull
@Length(max = 20, message = "维度名称不允许超过20个字符")
@Schema(description = "维度名称")
private String name;
/**
* 备注
*/
@Length(max = 200, message = "备注不允许超过200个字符")
@Schema(description = "备注")
private String remark;
/**
* 维度英文名称
*/
@Schema(description = "维度英文名称")
private String enName;
/**
* 维度列字段
*/
@Schema(description = "维度列字段")
private String dimColumnName;
/**
* 维度关联维表英文名称
*/
@Schema(description = "维度关联维表英文名称")
private String linkTable;
/**
* 是否开启维度加速:0-未开启、1-开启
*/
@Schema(description = "是否开启维度加速:0-未开启、1-开启")
private Integer speedDimFlag;
/**
* 是否内置维度:0-否、1-是
*/
@Schema(description = "是否内置维度:0-否、1-是")
private Integer builtinFlag;
private Long dirId;
}
【2】服务层:IndDimension(DTO)
@Getter
@Setter
@ToString
public class IndDimension extends BaseEntity<Long> {
/**
* 维度名称
*/
private String name;
/**
* 备注
*/
private String remark;
/**
* 维度英文名称
*/
private String enName;
private List<String> enNames;
/**
* 维度列字段
*/
private String dimColumnName;
/**
* 维度关联维表英文名称
*/
private String linkTable;
/**
* 是否开启维度加速:0-未开启、1-开启
*/
private Integer speedDimFlag;
/**
* 维度关联维表英文名称
*/
private List<String> linkTableList;
/**
* 数据种类(0: 数字 1:字符串 2:日期)
*/
private Integer dataCategory;
/**
* 关联库表维值的上次同步时间
*/
private LocalDateTime lastUpdateTime;
/**
* 是否内置维度:0-否、1-是
*/
private Integer builtinFlag;
private Long dirId;
/**
* 维度类型:0:普通维度;1衍生维度
*/
private Integer dimType = 0;
private Long parentId;
/**
* 核心层级维度
*/
private IndDimLevel coreDimLevel;
/**
* 检查租户和当前用户租户是否一致
*
* @return true:一致;false:不一致
*/
public boolean checkTenant() {
return Objects.equals(this.getTenantId(), RequestContext.getTenantId());
}
}
实体类父类
@Getter
@Setter
@ToString
public class BaseEntity<T extends Serializable> {
/**
* 主键
*/
private T id;
/**
* 租户ID
*/
private Long tenantId;
/**
* 创建时间
*/
private LocalDateTime createDateTime;
/**
* 创建用户id
*/
private Long createUserId;
/**
* 更新时间
*/
private LocalDateTime updateDateTime;
/**
* 更新用户id
*/
private Long updateUserId;
}
【3】持久层:IndDimensionPO(PO)
@Getter
@Setter
@TableName(value = "IND_DIMENSION")
public class IndDimensionPO extends SnowflakeBasePO {
/**
* 维度名称
*/
@TableField("name")
private String name;
/**
* 备注
*/
@TableField("remark")
private String remark;
/**
* 维度英文名称
*/
@TableField("en_name")
private String enName;
/**
* 维度列字段
*/
@TableField(value = "dim_column_name",fill = FieldFill.UPDATE)
private String dimColumnName;
/**
* 维度关联维表英文名称
*/
@TableField(value = "link_table",fill = FieldFill.UPDATE)
private String linkTable;
/**
* 是否开启维度加速:0-未开启、1-开启
*/
@TableField("speed_dim_flag")
private Integer speedDimFlag;
/**
* 是否内置维度:0-否、1-是
*/
@TableField("builtin_flag")
private Integer builtinFlag;
/**
* 关联库表的维值上次更新时间
*/
@TableField("last_update_time")
private LocalDateTime lastUpdateTime;
@TableField("dir_id")
private Long dirId;
}
父级类
/**
* 持久化对象基类
* 主键采用雪花算法生成
*
* @author wl
* @since 2022-03-08 17:40
*/
@Getter
@Setter
@ToString
@EqualsAndHashCode(of = "id", callSuper = false)
public class SnowflakeBasePO extends BasePO {
/**
* 主键
*/
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private Long id;
}
/**
* 基础持久化对象
*
* @author wl
* @since 2022-02-23 8:47
*/
@Getter
@Setter
@ToString
public class BasePO {
/**
* 租户ID
*/
@TableField(value = "TENANT_ID", fill = FieldFill.INSERT)
private Long tenantId;
/**
* 创建日期时间
*/
@TableField(value = "CRT_DT_TM", fill = FieldFill.INSERT)
private LocalDateTime createDateTime;
/**
* 创建用户ID
*/
@TableField(value = "CRT_USER_ID", fill = FieldFill.INSERT)
private Long createUserId;
/**
* 更新日期时间
*/
@TableField(value = "UPD_DT_TM", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateDateTime;
/**
* 更新用户ID
*/
@TableField(value = "UPD_USER_ID", fill = FieldFill.INSERT_UPDATE)
private Long updateUserId;
}
【四】mybatisplus持久层的注解解析
【1】@TableName(value = “IND_DIMENSION”)
(1)@TableName 用于指定实体类对应的数据库表名。如果实体类名与表名不一致,则需要使用该注解进行映射。
(2)常用属性
value:指定表名。
schema:指定数据库架构名称(可选)。
autoResultMap:是否启用自动结果映射,默认 false
(3)案例
@TableName(value = “user”, schema = “mydb”, autoResultMap = true)
【2】@TableField(“name”)
@TableField 用于标注非主键字段的映射。该注解可以配置数据库表中的列名、是否进行自动填充、是否是查询字段等。
【3】@TableId(value = “ID”, type = IdType.ASSIGN_ID)
(1)@TableId 用于指定表的主键列,并可以设置主键的生成策略。常见的生成策略有自动递增(AUTO)、手动分配(INPUT)等。
(2)常用属性
value:指定主键字段名称。
type:指定主键生成策略,常见的生成策略有:
IdType.AUTO:数据库自动生成(通常是自增主键)。
IdType.INPUT:手动输入主键。
IdType.ASSIGN_ID:MyBatis-Plus 自动生成 ID(分布式 ID 算法)。
IdType.UUID:生成 UUID 作为主键。
【4】@EqualsAndHashCode(of = “id”, callSuper = false)
(1)作用
@EqualsAndHashCode注解的作用就是自动实现model类的equals方法和hashcode方法。
对父类和子类分别new一个对象,然后使用equals判断,得到的结果是true,原因是它没有调用父类的中属性,而是直接使用的子类中的属性来生成hashcode,所以当子类中的属性参数一样时,使用equals比较结果的为true。
(2)案例
@EqualsAndHashCode(callSuper = true),就是用自己的属性和从父类继承的属性来生成hashcode;
@EqualsAndHashCode(callSuper = false),就是只用自己的属性来生成hashcode;
@Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集, 和@EqualsAndHashCode默认是false。
【5】@TableField(value = “CRT_DT_TM”, fill = FieldFill.INSERT)
(1)@TableField 用于标注非主键字段的映射。该注解可以配置数据库表中的列名、是否进行自动填充、是否是查询字段等。
(2)常用属性
value:指定数据库列名。
exist:是否是数据库表中的字段,默认 true。如果设置为 false,表示该字段不参与数据库映射。
fill:指定字段的自动填充策略,支持 INSERT、UPDATE、INSERT_UPDATE。自动填充是指在插入或更新数据时,MyBatis-Plus 自动为某些字段赋值。常见的场景包括:创建时间、更新时间、操作人等。
(3)案例
创建自动填充处理器:实现 MetaObjectHandler 接口,重写 insertFill 和 updateFill 方法。
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "CRT_DT_TM", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "UPD_DT_TM", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "CRT_USER_ID", String.class,getCurrentUserId());
this.strictInsertFill(metaObject, "UPD_USER_ID", String.class,getCurrentUserId());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "UPD_DT_TM", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "UPD_USER_ID", String.class, getCurrentUserId());
}
private String getCurrentUserId() {
// 这里应该根据你的业务逻辑获取当前用户的ID
// 这一部分我是从我登录的时候在线程存放了登录用户的id,然后在这里来拿到登录用户的id的
// 你们这里需要根据自己的方法得到自己登录用户的id或者你们想加入的数据
// Long currentId = BaseContext.getCurrentId();
String currentUserId = "1086";
return currentUserId;
}
}
【6】@TableField(value = “UPD_DT_TM”, fill = FieldFill.INSERT_UPDATE)
【7】@TableLogic
@TableLogic 用于实现逻辑删除功能。逻辑删除是在数据表中不真正删除记录,而是通过标记某个字段(如 is_deleted)来表示数据是否被删除。
import com.baomidou.mybatisplus.annotation.TableLogic;
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
@TableLogic
private Integer isDeleted;
}
在上面的示例中,isDeleted 字段用于标记该记录是否被删除。当使用 MyBatis-Plus 提供的删除方法(如 deleteById)时,实际上会更新 isDeleted 字段的值,而不是物理删除该记录。
默认行为
未删除:isDeleted = 0(默认为 0)。
已删除:isDeleted = 1(默认为 1)。
【8】@Version 乐观锁注解
@Version 注解用于实现乐观锁。在并发环境下,乐观锁可以防止数据的“脏写”,通过版本号(version)字段来保证数据的一致性。
import com.baomidou.mybatisplus.annotation.Version;
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
@Version
private Integer version; // 乐观锁版本号
}
当更新 User 表时,MyBatis-Plus 会检查 version 字段。如果版本号匹配,则进行更新,同时版本号自增;否则更新失败,防止数据冲突。
【9】综合使用案例
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField("user_name")
private String username;
private String email;
@TableLogic
private Integer isDeleted;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@Version
private Integer version;
}