Java Web 项目团队分层规范文档(MyBatis-Plus)

1. 总体分层原则

企业级 Java Web 项目推荐 分层 + 业务域组织结构

Controller → Service → Mapper → Database

各层职责:

对象 主要职责
Controller DTO / VO 接收请求,返回响应
Service Entity / BO / Result 业务处理、事务、规则校验
Mapper Entity / Result 数据持久化、SQL 查询
Database Table 数据存储
Convert / MapperUtil MapStruct 或手写映射 对象转换,VO ↔ Result / DTO ↔ Query

2. 对象模型定义与职责

2.1 DTO(Data Transfer Object)

  • 位置controller/dto
  • 用途:接口请求参数模型
  • 特点
    • 与前端交互字段对应
    • 只用于 Controller → Service
    • 不下沉到 Mapper
  • 示例
@Data
public class OrderQueryDTO {
    private Long userId;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
}

2.2 Entity / DO(Domain Object)

  • 位置domain/entity
  • 用途:数据库表映射
  • 特点
    • 对应单表
    • 可持久化(MyBatis-Plus CRUD)
    • 不包含聚合字段
  • 示例
@TableName("`order`")
@Data
public class OrderEntity {
    private Long id;
    private Long userId;
    private String orderNo;
    private LocalDateTime createTime;
}

2.3 BO(Business Object,可选)

  • 位置domain/bo
  • 用途:Service 内部业务计算模型
  • 特点
    • 不映射数据库
    • 用于 Service 内部传递、聚合或组合 Entity
  • 示例
@Data
public class OrderBO {
    private OrderEntity order;
    private BigDecimal totalAmount;
}

2.4 Result(SQL 查询结果模型)

  • 位置mapper/result
  • 用途
    • 连表查询 / 聚合查询的结果
    • 对应 SQL 查询结果,不属于单表
  • 特点
    • 不继承 Entity
    • 可用 字段复制(field copy)组合(composition)
    • 只读
  • 示例

2.4.1 字段复制(field copy)

@Data
public class OrderSummaryResult {
    private Long orderId;
    private String orderNo;
    private String userNickname;
    private Integer productTypeCount;
    private Integer totalQuantity;
    private BigDecimal totalAmount;
    private LocalDateTime createTime;
}

2.4.2 组合(composition)

@Data
public class OrderSummaryResult2 {
    private OrderEntity order;       // 内嵌 Entity
    private String userNickname;
    private Integer productTypeCount;
    private Integer totalQuantity;
    private BigDecimal totalAmount;
}

2.5 VO(View Object)

  • 位置controller/vo
  • 用途:返回前端展示
  • 特点
    • 与前端字段对齐
    • 可格式化时间或字段
  • 示例
@Data
public class OrderSummaryVO {
    private String orderNo;
    private String userNickname;
    private Integer productTypeCount;
    private Integer totalQuantity;
    private BigDecimal totalAmount;
    private String createTime;
}

3. Mapper 层规范(MyBatis-Plus)

3.1 Mapper 接口

  • 位置mapper
  • 用途
    • 单表 CRUD 使用 MP 内置方法
    • 连表 / 聚合 SQL 使用 XML 或 @Select 注解
  • 示例
public interface OrderMapper extends BaseMapper<OrderEntity> {
    List<OrderSummaryResult> selectOrderSummary(@Param("query") OrderQuery query);
    List<OrderSummaryResult2> selectOrderSummary2(@Param("query") OrderQuery query);
}

3.2 SQL 示例(XML + ResultMap)

<resultMap id="orderSummaryMap" type="com.xxx.mapper.result.OrderSummaryResult2">
    <association property="order" javaType="com.xxx.domain.entity.OrderEntity">
        <id column="orderId" property="id"/>
        <result column="orderNo" property="orderNo"/>
        <result column="createTime" property="createTime"/>
        <result column="userId" property="userId"/>
    </association>
    <result column="userNickname" property="userNickname"/>
    <result column="productTypeCount" property="productTypeCount"/>
    <result column="totalQuantity" property="totalQuantity"/>
    <result column="totalAmount" property="totalAmount"/>
</resultMap>

<select id="selectOrderSummary2" resultMap="orderSummaryMap">
    SELECT
        o.id AS orderId,
        o.order_no AS orderNo,
        u.nickname AS userNickname,
        COUNT(DISTINCT oi.product_id) AS productTypeCount,
        SUM(oi.quantity) AS totalQuantity,
        SUM(oi.quantity * oi.price) AS totalAmount,
        o.create_time AS createTime,
        o.user_id AS userId
    FROM `order` o
    JOIN user u ON o.user_id = u.id
    JOIN order_item oi ON o.id = oi.order_id
    <where>
        <if test="query.userId != null">
            AND o.user_id = #{query.userId}
        </if>
        <if test="query.startTime != null">
            AND o.create_time &gt;= #{query.startTime}
        </if>
        <if test="query.endTime != null">
            AND o.create_time &lt;= #{query.endTime}
        </if>
    </where>
    GROUP BY o.id, o.order_no, u.nickname, o.create_time
    ORDER BY o.create_time DESC
</select>

4. Service 层规范

  • 位置service + service/impl
  • 职责
    • 接收 DTO → 转 Query / BO
    • 调用 Mapper → 返回 Result
    • 转换 Result → VO
  • 示例
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderMapper orderMapper;

    public List<OrderSummaryVO> getOrderSummary(OrderQueryDTO dto) {
        OrderQuery query = new OrderQuery();
        query.setUserId(dto.getUserId());
        query.setStartTime(dto.getStartTime());
        query.setEndTime(dto.getEndTime());

        List<OrderSummaryResult> results = orderMapper.selectOrderSummary(query);

        return results.stream().map(r -> {
            OrderSummaryVO vo = new OrderSummaryVO();
            vo.setOrderNo(r.getOrderNo());
            vo.setUserNickname(r.getUserNickname());
            vo.setProductTypeCount(r.getProductTypeCount());
            vo.setTotalQuantity(r.getTotalQuantity());
            vo.setTotalAmount(r.getTotalAmount());
            vo.setCreateTime(r.getCreateTime().toString());
            return vo;
        }).toList();
    }

    public List<OrderSummaryVO> getOrderSummary2(OrderQueryDTO dto) {
        OrderQuery query = new OrderQuery();
        query.setUserId(dto.getUserId());
        query.setStartTime(dto.getStartTime());
        query.setEndTime(dto.getEndTime());

        List<OrderSummaryResult2> results = orderMapper.selectOrderSummary2(query);

        return results.stream().map(r -> {
            OrderSummaryVO vo = new OrderSummaryVO();
            vo.setOrderNo(r.getOrder().getOrderNo());
            vo.setUserNickname(r.getUserNickname());
            vo.setProductTypeCount(r.getProductTypeCount());
            vo.setTotalQuantity(r.getTotalQuantity());
            vo.setTotalAmount(r.getTotalAmount());
            vo.setCreateTime(r.getOrder().getCreateTime().toString());
            return vo;
        }).toList();
    }
}

5. Controller 层规范

@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @PostMapping("/summary")
    public List<OrderSummaryVO> summary(@RequestBody OrderQueryDTO dto){
        return orderService.getOrderSummary(dto);      // field copy
        // return orderService.getOrderSummary2(dto);  // composition
    }
}

6. 对象流转图(MP + 聚合 + 连表)

Frontend
   ↓ JSON
OrderQueryDTO
   ↓
Service
   ↓ DTO → Query
OrderQuery
   ↓
Mapper (连表 + 聚合)
   ↓
OrderSummaryResult / OrderSummaryResult2
   ↓ Service 转换
OrderSummaryVO
   ↓
Frontend

7. 目录结构规范

src/main/java/com/xxx/project
├─ controller
│  ├─ OrderController.java
│  ├─ dto
│  │  └─ OrderQueryDTO.java
│  └─ vo
│     └─ OrderSummaryVO.java
├─ service
│  ├─ OrderService.java
│  └─ impl
│     └─ OrderServiceImpl.java
├─ domain
│  ├─ entity
│  │  ├─ OrderEntity.java
│  │  ├─ UserEntity.java
│  │  └─ OrderItemEntity.java
│  └─ bo
│     └─ OrderBO.java
├─ mapper
│  ├─ OrderMapper.java
│  └─ result
│     ├─ OrderSummaryResult.java
│     └─ OrderSummaryResult2.java
└─ convert
   └─ OrderConvert.java

8. 重要团队规范总结

  1. DTO 永远不下沉到 Mapper
  2. Entity / DO 对应表结构
  3. Result 对应 SQL 查询(连表/聚合)
  4. Result 不继承 Entity
  5. VO 只面向前端
  6. Service 做转换 / 聚合逻辑
  7. Mapper SQL 字段尽量和 Result 一一对应
  8. 复杂 Result 可采用组合(内嵌 Entity),简单 Result 用字段复制
  9. 所有层都明确职责,任何对象跨层使用都需审查

Java Web 项目团队分层规范文档(MyBatis-Plus)
正文完
 0
评论(没有评论)
验证码