通过账户信息操作加深对DTO,VO,BO理解
好的!我来详细说明 DO、DTO、VO、BO、Query 这些概念在分层架构中的使用,并通过具体示例展示它们在各层的流转。
📚 概念定义
| 缩写 | 全称 | 作用域 | 描述 |
|---|---|---|---|
| DO | Domain Object | DAO层 | 数据库实体,与表结构一一对应 |
| DTO | Data Transfer Object | Service层 | 服务间数据传输对象 |
| VO | View Object | Controller层 | 视图对象,返回给前端的数据 |
| BO | Business Object | Service层 | 业务逻辑对象,组合多个DO |
| Query | Query Object | Controller→Service | 查询条件封装对象 |
🏗️ 项目结构示例
controller-AccountController
config-PagingPlugConfig
entity-AccountBO,AccountDO,AccountDTO,AccountQuery,AccountVO
mapper-AccountMapper
service-AccountService,AccountServiceImpl
💻 代码示例
1. DO - 数据库实体 (DAO层)
java
package com.geekmice.passparams.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
/**
* (Account)实体类
* 与数据库表字段一致
* @author pmb
* @since 2025-12-01 17:10:09
*/
@Data
@TableName(value = "account")
public class AccountDO {
private Integer id;
private Integer uid;
private Integer money;
}
2. Query - 查询条件对象
java
package com.geekmice.passparams.entity;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.Data;
@Data
public class AccountQuery {
private Integer id;
private Integer uid;
private Integer money;
private Integer pageNum;
private Integer pageSize;
public LambdaQueryWrapper<AccountDO> buildQueryWrapper(){
LambdaQueryWrapper<AccountDO> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(AccountDO::getMoney,new Integer("60"));
return wrapper;
}
}
3. DTO - 数据传输对象
java
package com.geekmice.passparams.entity;
import lombok.Data;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/**
* (Account)DTO类
*
* @author pmb
* @since 2025-12-01 17:10:11
*/
@Data
public class AccountDTO {
private Integer id;
private Integer uid;
private Integer money;
}
4. BO - 业务对象
java
package com.geekmice.passparams.entity;
import lombok.Data;
/**
* (Account)实体类
* 业务BO,包含多个DTO
* @author pmb
* @since 2025-12-01 17:10:09
*/
@Data
public class AccountBO {
private AccountDTO accountDTO;
}
5. VO - 视图对象
java
package com.geekmice.passparams.entity;
import lombok.Data;
import org.springframework.beans.BeanUtils;
/**
* (Account)VO类
*
* @author pmb
* @since 2025-12-01 17:10:11
*/
@Data
public class AccountVO {
private Integer id;
private Integer uid;
private Integer money;
public static AccountVO fromBO(AccountBO accountBO) {
AccountVO accountVO = new AccountVO();
AccountDTO accountDTO = accountBO.getAccountDTO();
BeanUtils.copyProperties(accountDTO, accountVO);
return accountVO;
}
}
🔄 分层使用示例
Controller 层
java
package com.geekmice.passparams.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.geekmice.passparams.entity.AccountDTO;
import com.geekmice.passparams.entity.AccountQuery;
import com.geekmice.passparams.entity.AccountVO;
import com.geekmice.passparams.service.AccountService;
import com.geekmice.passparams.utils.R;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author pmb
* @Desc (Account)表控制层
* @Date 2025-12-01 17:10:08
*/
@RestController
@RequestMapping("account")
@RequiredArgsConstructor
public class AccountController {
private final AccountService accountService;
@GetMapping(value = "save")
public R<Integer> save(@RequestBody AccountDTO accountDTO) {
int count = accountService.add(accountDTO);
return R.ok(count);
}
@GetMapping(value = "queryById")
public R<AccountVO> queryById(@RequestBody AccountDTO accountDTO) {
AccountVO result = accountService.getAccountById(accountDTO.getId());
return R.ok(result);
}
@GetMapping(value = "pageAccounts")
public R<IPage<AccountVO>> pageAccounts(@RequestBody AccountQuery accountQuery){
IPage<AccountVO> res = accountService.selectPage(accountQuery);
return R.ok(res);
}
}
Service 层接口
package com.geekmice.passparams.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.geekmice.passparams.entity.AccountDO;
import com.geekmice.passparams.entity.AccountDTO;
import com.geekmice.passparams.entity.AccountQuery;
import com.geekmice.passparams.entity.AccountVO;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
/**
* @Author pmb
* @Desc (Account)表服务接口
* @Date 2025-12-01 17:10:11
*/
public interface AccountService extends IService<AccountDO> {
int add(AccountDTO accountDTO);
AccountVO getAccountById(Integer id);
IPage<AccountVO> selectPage(AccountQuery accountQuery);
}
Service 实现层
package com.geekmice.passparams.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.geekmice.passparams.entity.*;
import com.geekmice.passparams.mapper.AccountMapper;
import com.geekmice.passparams.service.AccountService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
/**
* @Author pmb
* @Desc (Account)表服务实现类
* @Date 2025-12-01 17:10:11
*/
@RequiredArgsConstructor
@Service("accountService")
public class AccountServiceImpl extends ServiceImpl<AccountMapper, AccountDO> implements AccountService {
private final AccountMapper accountMapper;
@Override
public int add(AccountDTO accountDTO) {
// DTO->DO
AccountDO accountDO = convertToDO(accountDTO);
// 持久化
int count = accountMapper.insert(accountDO);
// 受影响行数
return count;
}
@Override
public AccountVO getAccountById(Integer id) {
// DO -> DTO
AccountDO accountDO = accountMapper.selectById(id);
assert accountDO != null;
AccountDTO accountDTO = convertToDTO(accountDO);
// 组装业务BO,实际业务为多个DTO
AccountBO accountBO = assemebleAccountBO(accountDTO);
// BO->VO
return AccountVO.fromBO(accountBO);
}
@Override
public IPage<AccountVO> selectPage(AccountQuery accountQuery) {
// 分页
Page<AccountDO> page = this.page(
new Page<>(accountQuery.getPageNum(), accountQuery.getPageSize()),
accountQuery.buildQueryWrapper()
);
// page<do> 转换ipage<vo>
IPage<AccountVO> voPage = page.convert(accountDO -> {
AccountVO accountVO = new AccountVO();
BeanUtils.copyProperties(accountDO, accountVO);
// 如果有日期时间需要格式化
return accountVO;
});
return voPage;
}
private AccountBO assemebleAccountBO(AccountDTO accountDTO) {
AccountBO accountBO = new AccountBO();
accountBO.setAccountDTO(accountDTO);
return accountBO;
}
private AccountDTO convertToDTO(AccountDO accountDO) {
AccountDTO accountDTO = new AccountDTO();
BeanUtils.copyProperties(accountDO, accountDTO);
return accountDTO;
}
private AccountDO convertToDO(AccountDTO accountDTO) {
AccountDO accountDO = new AccountDO();
BeanUtils.copyProperties(accountDTO, accountDO);
return accountDO;
}
}
DAO 层
package com.geekmice.passparams.mapper;
import com.geekmice.passparams.entity.AccountDO;
import com.geekmice.passparams.entity.AccountQuery;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @Author pmb
* @Desc (Account)表数据库访问层
* @Date 2025-12-01 17:10:08
*/
@Mapper
public interface AccountMapper extends BaseMapper<AccountDO> {
}
分页插件配置
java
package com.geekmice.passparams.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PagingPlugConfig {
@Bean
public MybatisPlusInterceptor getMybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
工具类
java
package com.geekmice.passparams.utils;
import lombok.*;
import org.springframework.http.HttpStatus;
import java.io.Serializable;
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
@Getter
@Setter
private int code;
@Getter
@Setter
private String msg;
@Getter
@Setter
private T data;
public static <T> R<T> ok() {
return restResult(null, HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase());
}
public static <T> R<T> ok(T data) {
return restResult(data, HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase());
}
public static <T> R<T> ok(T data, String msg) {
return restResult(data, HttpStatus.OK.value(), msg);
}
public static <T> R<T> failed() {
return restResult(null, HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
}
public static <T> R<T> failed(String msg) {
return restResult(null, HttpStatus.INTERNAL_SERVER_ERROR.value(), msg);
}
public static <T> R<T> failed(T data) {
return restResult(data, HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
}
public static <T> R<T> failed(T data, String msg) {
return restResult(data, HttpStatus.INTERNAL_SERVER_ERROR.value(), msg);
}
public static <T> R<T> restResult(T data, int code, String msg) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
}
🎯 关键流转路径总结
-
创建流程:
AccountDTO→AccountDO→ 数据库 → 返回Integer -
查询流程:
AccountQuery→AccountDO(分页) →AccountDTO→AccountBO→AccountVO→ JSON响应 -
对象转换原则:
- 向下转换(Controller→Service→DAO):逐步添加技术细节
- 向上转换(DAO→Service→Controller):逐步剥离技术细节,增加业务含义
- 每层职责单一:不跨层使用对象
这样的设计保证了各层的独立性和可维护性,是大型企业级应用的常见实践。