DTOs vs Entities in Spring Boot
What are DTOs and Entities?
Entity: A Java class that maps directly to a database table. It represents your data persistence layer.
DTO (Data Transfer Object): A simple Java class used to transfer data between layers (typically from server to client). It contains only the data you want to expose.
| Aspect | Entity | DTO |
|---|---|---|
| Purpose | Database mapping (JPA/Hibernate) | Data transfer between layers/API |
| Exposure | Internal, kept in backend | Exposed to frontend via REST API |
| Lazy Loading | Supports lazy-loaded collections | Always fully initialized |
| Circular References | Can cause serialization issues | Prevents circular reference problems |
| Size | May contain unnecessary fields | Contains only required fields |
Why Use DTOs?
1. Security
Never expose your database entities directly to the frontend. Your entity might contain sensitive fields you don't want to reveal.
// ❌ BAD: Exposing entity directly
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElse(null);
}
}
// ✅ GOOD: Using DTO
@RestController
public class UserController {
@GetMapping("/users/{id}")
public UserDTO getUser(@PathVariable Long id) {
User user = userRepository.findById(id).orElse(null);
return mapToDTO(user);
}
}
2. Performance
DTOs allow you to fetch only the fields you need, reducing database load and network bandwidth.
// Entity might have:
@Entity
public class User {
private Long id;
private String username;
private String email;
private String passwordHash; // Don't expose!
private List<Address> addresses; // Lazy loaded
private List<Order> orders; // Lazy loaded
}
// DTO exposes only what's needed:
public class UserDTO {
private Long id;
private String username;
private String email;
// No password, addresses, or orders
}
3. Flexibility
DTOs decouple your API contract from your internal entity structure. You can change entities without breaking the API.
// Backend entity changes:
@Entity
public class User {
private Long id;
private String username;
private String email;
private LocalDateTime createdAt; // New field
// ... other fields
}
// DTO remains the same—no API breakage
public class UserDTO {
private Long id;
private String username;
private String email;
}
Angular Perspective
In Angular, you have a similar pattern: - Models (Interfaces/Classes): Similar to DTOs, define the shape of data you work with - HTTP Response: Exactly like a DTO—only contains what the server sends - Component State: Like an entity—can have extra properties not exposed to the view
// Angular DTO equivalent
export interface UserDTO {
id: number;
username: string;
email: string;
}
// In service
this.http.get<UserDTO>('/api/users/1').subscribe(user => {
// Type-safe data
});
Practical Example: User Management
Entity
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue
private Long id;
private String username;
private String email;
@Column(name = "password_hash")
private String passwordHash;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
DTO
public class UserDTO {
private Long id;
private String username;
private String email;
// Constructor for mapping
public UserDTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.email = user.getEmail();
}
}
Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(new UserDTO(user));
}
}
Best Practices
- Create DTOs for API responses — Never expose raw entities
- Use separate DTOs for request and response — Request DTO for POST/PUT, Response DTO for GET
- Keep DTOs lightweight — Include only necessary fields
- Map entities to DTOs early — Do it in the controller or service layer
- Avoid lazy loading issues — DTOs help prevent "N+1" query problems
Request vs Response DTOs
// Request DTO (for POST/PUT)
public class CreateUserRequest {
@NotNull
private String username;
@NotNull
@Email
private String email;
@NotNull
@Size(min = 8)
private String password;
}
// Response DTO (for GET)
public class UserResponse {
private Long id;
private String username;
private String email;
private LocalDateTime createdAt;
}
Lab Instructions
- Create a
Bookentity with fields: id, title, author, isbn, price, createdAt - Create a
BookDTOthat exposes only: id, title, author, price - Create a
CreateBookRequestDTO for POST requests with validation - Implement a
BookControllerthat converts entities to DTOs before responding - Test with Postman or cURL to verify DTOs are being used