目录
[一、DTO 与 Domain 领域模型核心分层设计](#一、DTO 与 Domain 领域模型核心分层设计)
[1. DTO 数据传输对象](#1. DTO 数据传输对象)
[2. Domain 领域模型](#2. Domain 领域模型)
[3. 核心区别总结](#3. 核心区别总结)
[二、Java 8 Optional 彻底解决空指针异常](#二、Java 8 Optional 彻底解决空指针异常)
[1. 核心语义与定位](#1. 核心语义与定位)
[2. 常用核心方法](#2. 常用核心方法)
[3. 传统 null 与 Optional 对比](#3. 传统 null 与 Optional 对比)
[4. 空值包装规则](#4. 空值包装规则)
[三、@Transactional (readOnly = true) 只读事务优化](#三、@Transactional (readOnly = true) 只读事务优化)
[1. 注解核心作用](#1. 注解核心作用)
[2. 实际业务用法](#2. 实际业务用法)
[3. 使用注意事项](#3. 使用注意事项)
[1. 模块职责划分](#1. 模块职责划分)
[2. 完整调用链路](#2. 完整调用链路)
前言
在 Java 微服务与 DDD 业务开发中,用户模块是系统基础核心底座。开发中常遇到前后端数据传输冗余、领域模型与传输对象混淆、空指针泛滥、数据库事务性能浪费 等问题。本文基于实际项目用户模块源码,详解DTO 与 Domain 领域模型分层设计 、Java Optional 空值安全处理 、只读事务注解优化,同时拆解模块化调用架构,帮你规范企业级用户模块开发规范。
一、DTO 与 Domain 领域模型核心分层设计
在后端分层开发中,DTO 和 Domain 是极易混淆的两类实体对象,职责边界完全不同,合理划分是代码规范的基础。
1. DTO 数据传输对象
DTO(Data Transfer Object) 即数据传输对象,用于各层、前后端之间做数据传输。 存放路径通常为 xxx.api.dto,主要承载接口请求体、响应体。
特点:
-
只保留接口需要的字段,精简不冗余;
-
可添加参数校验注解,做请求参数合法性校验;
-
可组合多个领域模型数据,适配前端展示;
-
不关联数据库表结构,只负责数据流转。
package com.tongji.counter.api.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;/**
- 行为请求DTO:点赞/收藏操作通用请求体
*/
@Data
public class ActionRequest {
// 实体类型:如knowpost文章类型
@NotBlank(message = "实体类型不能为空")
private String entityType;
// 业务内容ID
@NotBlank(message = "业务ID不能为空")
private String entityId;
}
- 行为请求DTO:点赞/收藏操作通用请求体
2. Domain 领域模型
Domain 是业务领域核心模型 ,对应数据库实体表结构。 存放路径为 xxx.user.domain,是业务逻辑层的核心载体。
特点:
-
包含数据库完整业务字段,与表结构一一对应;
-
封装业务属性,专供 Service、Mapper 层使用;
-
禁止直接返回给前端,避免敏感字段泄露;
-
承载业务规则与实体完整属性。
package com.tongji.user.domain;
import lombok.*;
import java.time.Instant;
import java.time.LocalDate;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id; // 用户主键
private String phone; // 手机号
private String email; // 邮箱
private String passwordHash; // 密码哈希
private String nickname; // 昵称
private String avatar; // 头像
private String bio; // 个人简介
private String gender; // 性别
private LocalDate birthday; // 生日
private String school; // 学校
private Instant createdAt; // 创建时间
private Instant updatedAt; // 更新时间
}
3. 核心区别总结
Domain 贴近数据库与业务内核,DTO 贴近接口与前后端交互。 严禁直接把 Domain 实体传给前端,必须通过 DTO 做字段脱敏、裁剪、组装,保证接口安全与优雅性。
二、Java 8 Optional 彻底解决空指针异常
开发中查询用户信息常会返回null,直接调用属性极易引发NPE 空指针异常 。 Java 8 引入的Optional是包装空值的容器对象,从语法层面强制开发者处理空值。
1. 核心语义与定位
Optional 语义:容器内最多存放 0 个或 1 个元素。 0 个代表查询无数据,1 个代表查询到有效用户。 区别于 List 集合,List 可容纳多条数据,二者使用场景完全不同。
// Optional:只能存0个或1个
Optional<User> emptyOpt = Optional.empty();
Optional<User> userOpt = Optional.ofNullable(new User());
// 错误写法:不能传入多个对象
// Optional<User> errOpt = Optional.of(user1, user2);
// List:可存放多个对象
List<User> userList = new ArrayList<>();
userList.add(new User());
userList.add(new User());
2. 常用核心方法
ofNullable():包装可为 null 的对象,null 则转为Optional.empty();orElse():无值返回默认对象,有值返回原值;orElseThrow():无值直接抛出业务异常;map():对容器内对象做属性转换;ifPresent():有值才执行后续逻辑。
3. 传统 null 与 Optional 对比
传统写法不做强制空校验,极易遗漏判断导致线上崩溃。
// 传统写法:暗藏空指针风险
User user = userMapper.findById(123L);
// 一旦user为null,直接NPE
String nickName = user.getNickname();
Optional 优雅写法,强制处理空值,杜绝 NPE:
// Optional安全写法
Optional<User> userOpt = userService.findById(123L);
// 无值给默认昵称,有值取真实昵称
String nickName = userOpt
.map(User::getNickname)
.orElse("匿名用户");
4. 空值包装规则
- 对象为
null:Optional.ofNullable()返回Optional.empty(); - 对象非空:自动包装为包含实例的 Optional 容器。 通过
isEmpty()、isPresent()可快速判断是否存在有效数据。
三、@Transactional (readOnly = true) 只读事务优化
在用户模块查询场景中,大量接口只做查询不做增删改,合理使用只读事务可显著提升数据库性能。
1. 注解核心作用
@Transactional(readOnly = true) 用于标注查询类业务方法。 向数据库声明当前事务只读、无数据修改,数据库会做特殊优化:
- 不开启写事务日志,减少 IO 开销;
- 不加行锁、表锁,提升并发查询能力;
- 事务提交流程简化,降低连接占用时间。
2. 实际业务用法
用户信息查询、列表查询等只读接口,统一加上该注解。
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
public class UserService {
// 只读事务:仅查询,无写操作
@Transactional(readOnly = true)
public Optional<User> getUserById(Long userId) {
return userMapper.selectById(userId);
}
}
3. 使用注意事项
- 仅用于纯查询方法,不能包含新增、修改、删除逻辑;
- 必须作用在 public 方法上,Spring 事务基于 AOP 代理;
- 读写分离架构下,可配合该注解自动走从库查询。
四、用户模块模块化架构与调用流程
本项目采用模块拆分解耦设计,用户模块无独立 Controller,职责单一化。
1. 模块职责划分
- auth 认证模块 :包含
AuthController,对外暴露所有前端 API 入口; - user 用户模块 :只提供 Service、Mapper、Domain,无 Controller,仅对内提供业务能力。
2. 完整调用链路
前端请求 → AuthController → AuthService → UserService → UserMapper → 数据库
这样设计实现了职责解耦:用户模块只专注用户数据 CRUD,认证模块专注登录、鉴权、接口转发。 避免多个模块重复写 Controller,统一收口接口入口,便于权限管控和接口维护。
结语
本文完整梳理了企业级用户模块三大核心知识点:DTO 与 Domain 分层隔离规范 、Java Optional 空指针安全处理 、只读事务性能优化,同时拆解了无 Controller 的模块化分层调用架构。 掌握 DTO 和 Domain 的边界划分,可以让代码结构更规范;用好 Optional 能从根源消灭空指针异常;只读事务注解可低成本提升查询接口并发性能。