Mastering PO, VO, BO, DTO, DAO: When to Use Each Java Object
This article explains the definitions, responsibilities, and differences of six common Java objects—PO, VO, BO, DTO, DAO, and POJO—illustrates their typical usage in layered architectures, presents conversion models and tools like MapStruct and Dozer, and offers practical guidelines to avoid common pitfalls.
Preface
Recently a friend asked me: PO, VO, BO, DTO, DAO, POJO – what are the differences? At first glance they can be confusing, and their concepts are easy to mix up. This article discusses the meaning, responsibilities, distinctions, and common pitfalls of these six objects to help you understand them better.
1. Responsibilities of the 6 Objects
Object design’s essence is separation of concerns—each object does one thing and does it well!
1.1 PO
PO stands for Persistent Object.
Responsibility: Strict 1:1 mapping with a database table, only carries data storage structure.
Feature:
Attributes correspond exactly to table fields.
No business logic methods (only getters/setters).
Code Example:
public class UserPO {
private Long id; // corresponds to primary key
private String name; // corresponds to name column
}1.2 DAO
DAO stands for Data Access Object.
Responsibility: Encapsulate all database operations (CRUD), isolate business logic from storage details.
Feature:
Interface methods correspond to SQL operations.
Return PO or collections of PO.
Code Example:
public interface UserDao {
// Query PO by ID
UserPO findById(Long id);
// Pagination query
List<UserPO> findPage(@Param("offset") int offset, @Param("limit") int limit);
}Underlying principle: DAO pattern = interface + implementation + PO.
1.3 BO
BO stands for Business Object.
Responsibility: Encapsulate core business logic, aggregate multiple PO to complete complex operations.
Feature:
Contains business state machine, validation rules.
Can hold references to multiple PO.
Code Example: Order refund BO
public class OrderBO {
// Main order data
private OrderPO orderPO;
// Sub order items
private List<OrderItemPO> items;
// Business method: execute refund
public RefundResult refund(String reason) {
if (!"PAID".equals(orderPO.getStatus())) {
throw new IllegalStateException("Unpaid order cannot be refunded");
}
// calculate refund amount, call payment gateway, etc.
}
}1.4 DTO
DTO stands for Data Transfer Object.
Responsibility: Transfer data across layers/services, hide sensitive fields.
Feature:
Attribute set is a subset of PO (e.g., excludes password).
Supports serialization (implements Serializable).
Code Example: User information DTO
public class UserDTO implements Serializable {
private Long id;
private String name;
}1.5 VO
VO stands for View Object.
Responsibility: Adapt data for front‑end display, may contain rendering logic.
Feature:
Attributes can include formatted data (e.g., yyyy‑MM‑dd).
Aggregates data from multiple tables (e.g., order VO contains user name).
Code Example:
public class OrderVO {
private String orderNo;
private String createTime; // formatted date
private String userName; // from user table
public String getStatusText() {
return OrderStatus.of(this.status).getDesc();
}
}1.6 POJO
POJO stands for Plain Old Java Object.
Responsibility: Basic data container, can play the role of PO/DTO/VO.
Feature:
Only fields + getters/setters.
No framework dependencies (does not extend Spring classes).
Typical implementation: Lombok simplifies code.
@Data
public class UserPOJO {
private Long id;
private String name;
}2. Main Object Flow Models
Scenario 1
Traditional three‑layer architecture (DAO → DTO → VO). Suitable for back‑office systems and utility applications.
Code Example: User query service
// Service layer
public UserDTO getUserById(Long id) {
UserPO userPO = userDao.findById(id); // fetch PO
UserDTO dto = new UserDTO();
dto.setId(userPO.getId());
dto.setName(userPO.getName()); // filter sensitive fields
return dto;
}
// Controller layer
public UserVO getUser(Long id) {
UserDTO dto = userService.getUserById(id);
UserVO vo = new UserVO();
vo.setUserId(dto.getId());
vo.setUserName(dto.getName());
vo.setRegisterTime(formatDate(dto.getCreateTime())); // format date
return vo;
}Pros: Simple and direct, suitable for CRUD scenarios.
Cons: Business logic can leak into the Service layer.
Scenario 2
DDD architecture (PO → DO → DTO → VO). Suitable for complex domains such as e‑commerce and finance.
Key role: DO (Domain Object) replaces BO.
Code Example: Order payment domain
// Domain layer: order domain object
public class OrderDO {
private OrderPO orderPO;
private PaymentPO paymentPO;
// Business method: payment validation
public void validatePayment() {
if (paymentPO.getAmount() < orderPO.getTotalAmount()) {
throw new PaymentException("Insufficient payment amount");
}
}
}
// Application layer: coordinate domain objects
public OrderPaymentDTO pay(OrderPayCmd cmd) {
OrderDO order = orderRepo.findById(cmd.getOrderId());
order.validatePayment(); // invoke domain method
return OrderConverter.toDTO(order); // convert to DTO
}Pros: High cohesion of business logic, suitable for complex rule systems.
Cons: More conversion layers increase development cost.
3. Efficient Conversion Tools
3.1 MapStruct – compile‑time code generation
Principle: Annotation processor generates mapping code.
Example: PO to DTO conversion
@Mapper
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
@Mapping(source = "createTime", target = "registerDate")
UserDTO poToDto(UserPO po);
}
// Generated implementation
public class UserConverterImpl implements UserConverter {
public UserDTO poToDto(UserPO po) {
UserDTO dto = new UserDTO();
dto.setRegisterDate(po.getCreateTime()); // automatic assignment
return dto;
}
}Pros: No reflection overhead, performance close to hand‑written code.
Open‑source: https://github.com/mapstruct/mapstruct
3.2 Dozer + Lombok – annotation‑driven conversion
Combination:
Lombok – auto‑generate getters/setters.
Dozer – XML/annotation based field mapping.
// Lombok annotation
@Data
public class UserVO {
private String userId;
private String userName;
}
// Mapping configuration
<field>
<a>userId</a>
<b>id</b>
</field>Applicable scenario: Complex conversions where field names differ.
3.3 Manual Builder Pattern – fine‑grained control
Applicable scenario: Dynamically constructed VO.
public class OrderVOBuilder {
public OrderVO build(OrderDTO dto) {
return OrderVO.builder()
.orderNo(dto.getOrderNo())
.amount(dto.getAmount() + "元") // dynamic concatenation
.statusText(convertStatus(dto.getStatus()))
.build();
}
}4. Pitfall Guide
Pitfall 1: Returning PO directly to the front‑end
// Fatal error: exposing DB sensitive fields!
public UserPO getUser(Long id) {
// PO contains password
return userDao.findById(id);
}Solution:
Use DTO to filter fields.
Apply @JsonIgnore on sensitive properties.
Pitfall 2: Embedding business logic inside DTO
public class OrderDTO {
// Wrong! DTO should not contain business methods
public void validate() {
if (amount < 0) {
throw new Exception();
}
}
}Root cause: Confusing responsibilities of DTO and BO.
Pitfall 3: Circular nested conversion causing N+1 queries
public class OrderVO {
private List<ProductVO> products; // nested objects
}
orderVO.setProducts(order.getProducts()
.stream()
.map(p -> convertToVO(p)) // triggers N+1 DB queries
.collect(toList()));Optimization: Batch queries + parallel conversion.
5. How to Choose an Object Model?
Summary
Single Responsibility: PO stores data, BO handles business, VO handles presentation – never cross boundaries.
Safety Isolation:
PO never leaves DAO layer (prevent DB leakage).
VO never leaves Controller (prevent front‑end logic pollution).
Performance First:
Use MapStruct for large‑scale object conversion (compile‑time generated code).
Use batch queries for nested collections to avoid N+1.
Moderate Design:
Systems with ≤10 tables can use POJO throughout.
Core systems with >100 tables must enforce strict layering.
There is no silver bullet for object design; understanding the business is more important than blindly applying patterns.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
