skills/chasepassion/skills/best-java-structure

best-java-structure

SKILL.md

Java Layered Architecture Design Pattern (Spring Boot + MyBatis-Plus)

A practical guide to implementing a classic five-layer architecture with Spring Boot and MyBatis-Plus.

When to Apply

Use this skill in the following scenarios:

  • Creating a new Java Web project
  • Refactoring an existing project architecture
  • Designing a multi-module Java system
  • Checking architectural compliance during code reviews
  • Defining architectural standards for team development
  • Database migration or technology stack migration

Assumptions

  • Spring Boot 3.x
  • MyBatis-Plus for data access
  • DTO/VO separation at API boundaries

Quick Reference

Priority Category Impact Area Prefix
1 Separation of Concerns Architectural clarity -principle-
2 Unidirectional Dependency Dependency management -dependency-
3 Interface Abstraction Replaceability -abstraction-
4 Data Transfer API consistency -data-
5 Exception Handling Error handling -exception-
6 Cross-Layer Invocation Architectural compliance -crosslayer-
7 Reverse Dependency Architectural compliance -reverse-
8 Entity Exposure Security -entity-exposure-

Core Principles

1. Separation of Concerns (SoC)

Each layer should solve one category of problems and must not take on responsibilities belonging to other layers.

// Correct: clear responsibilities per layer
// Presentation layer: HTTP handling only
@RestController
public class ProjectController {
    private final ProjectService projectService;
    public ProjectController(ProjectService projectService) {
        this.projectService = projectService;
    }
    @GetMapping("/{id}")
    public ApiReturn<ProjectVo> getProject(@PathVariable Long id) {
        Project project = projectService.getProject(id);
        ProjectVo vo = new ProjectVo();
        BeanUtils.copyProperties(project, vo);
        return ApiReturn.of(vo);
    }
}
// Business logic layer: business rules only
@Service
public class ProjectService {
    private final ProjectMapper projectMapper;
    public ProjectService(ProjectMapper projectMapper) {
        this.projectMapper = projectMapper;
    }
    public Project getProject(Long id) {
        Project project = projectMapper.selectById(id);
        if (project == null) {
            throw new BusinessException(404, "Project does not exist");
        }
        return project;
    }
}
// Data access layer: data interfaces only
public interface ProjectMapper extends BaseMapper<Project> {
}

2. Unidirectional Dependency

Upper layers may call lower layers, but lower layers must not depend on upper layers.

// Correct: depend on a lower layer abstraction
@Service
public class ProjectService {
    private final ProjectMapper projectMapper;
    public ProjectService(ProjectMapper projectMapper) {
        this.projectMapper = projectMapper;
    }
}
// Incorrect: cross-layer invocation
@RestController
public class ProjectController {
    private final ProjectMapper projectMapper;
    public ProjectController(ProjectMapper projectMapper) {
        this.projectMapper = projectMapper;
    }
}

3. Interface Abstraction

Layers should interact via interfaces, not concrete implementations.

// Correct: mapper interface managed by MyBatis-Plus
public interface ProjectMapper extends BaseMapper<Project> {
}
// Incorrect: depending on a non-existent implementation class
public class ProjectService {
    private ProjectMapperImpl projectMapperImpl;
}

Architecture Layers

Classic Five-Layer Architecture

Presentation Layer → Business Logic Layer → Data Access Layer → Persistence Layer → Database Layer
Layer Spring Stack Common Names Core Responsibilities
Presentation Layer @Controller Controller, API Handle HTTP requests and responses
Business Logic Layer @Service Service Implement business rules and workflows
Data Access Layer @Mapper Mapper, DAO Define DB access interfaces
Persistence Layer MyBatis XML, Entity Mapper XML, Entity SQL mapping and entity definitions
Database Layer MySQL Database Store and retrieve data

Layer Definitions

Presentation Layer

Responsibilities:

  • Handle HTTP requests and responses
  • Receive and validate parameters
  • Routing and dispatching
  • Data format transformation (DTO ↔ Entity ↔ VO)
  • Exception handling and HTTP status mapping Template:
@RestController
@RequestMapping("/api/projects")
public class ProjectController {
    private final ProjectService projectService;
    public ProjectController(ProjectService projectService) {
        this.projectService = projectService;
    }
    @GetMapping("/{id}")
    public ApiReturn<ProjectVo> getProject(@PathVariable Long id) {
        Project project = projectService.getProject(id);
        ProjectVo vo = new ProjectVo();
        BeanUtils.copyProperties(project, vo);
        return ApiReturn.of(vo);
    }
    @PostMapping
    public ApiReturn<ProjectVo> createProject(@Valid @RequestBody ProjectCreateRequest request) {
        Project project = new Project();
        BeanUtils.copyProperties(request, project);
        Project created = projectService.createProject(project);
        ProjectVo vo = new ProjectVo();
        BeanUtils.copyProperties(created, vo);
        return ApiReturn.of(vo);
    }
}

Design notes:

  • Keep it thin: HTTP-related logic only
  • Use DTO/VO for data transfer
  • Centralize validation with Bean Validation and global exception handling
  • No business logic
  • No direct database access

Business Logic Layer

Responsibilities:

  • Implement business rules and workflows
  • Data validation and business checks
  • Authorization and access control
  • Transaction management (boundary here)
  • Coordinate multiple services and mappers
  • Call the data access layer Template:
@Service
public class ProjectService {
    private final ProjectMapper projectMapper;
    public ProjectService(ProjectMapper projectMapper) {
        this.projectMapper = projectMapper;
    }
    public Project getProject(Long id) {
        if (id == null || id <= 0) {
            throw new BusinessException(400, "Invalid project ID");
        }
        Project project = projectMapper.selectById(id);
        if (project == null) {
            throw new BusinessException(404, "Project does not exist");
        }
        return project;
    }
    @Transactional
    public Project createProject(Project project) {
        if (project.getCustomerId() == null) {
            throw new BusinessException(400, "Customer ID must not be null");
        }
        project.setCreatedTime(LocalDateTime.now());
        project.setStatus(ProjectStatus.CREATED);
        projectMapper.insert(project);
        return project;
    }
}

Design notes:

  • Contains core business logic
  • Use @Transactional for transactions
  • Perform business validations
  • Orchestrate multiple services and mappers
  • No HTTP concerns
  • No direct DB connection handling
  • Service should not return DTO (DTO is an input model). Return Entity/Domain or VO consistently.

Data Access Layer (MyBatis-Plus)

Responsibilities:

  • Define DB access interfaces
  • Provide CRUD abstractions
  • Encapsulate persistence details
  • Provide aggregate query interfaces when needed Template:
public interface ProjectMapper extends BaseMapper<Project> {
}

Design notes:

  • Interfaces only; no concrete implementations
  • Method names clearly express intent
  • No SQL statements here (use XML for complex SQL)
  • No business logic
  • No transaction management
  • Must not depend on the Service layer

Persistence Layer

Responsibilities:

  • SQL mapping and result mapping
  • Entity definitions
  • Database-specific SQL fragments (when needed) Template (MyBatis-Plus XML):
<mapper namespace="com.example.mapper.ProjectMapper">
    <select id="selectActive" resultType="com.example.entity.Project">
        SELECT * FROM project WHERE status = 'CREATED'
    </select>
</mapper>

Design notes:

  • Focus on SQL authoring and optimization
  • Result mapping and type conversion
  • Use SQL fragments for reusability
  • No business logic
  • No business validations
  • Connection management and transaction boundaries are handled by Spring and DataSource

Database Layer

Responsibilities:

  • Persistent storage
  • Data integrity constraints
  • Indexes and performance optimization
  • Transaction support Template (MySQL):
CREATE TABLE project (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    customer_id BIGINT NOT NULL,
    status VARCHAR(50) DEFAULT 'CREATED'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Data Transfer Patterns

Using DTO, VO, and Entity

Frontend → Controller → Service → Mapper → Database
           DTO        Entity    Entity
           VO         Entity

Conversion Example

@PostMapping("/projects")
public ApiReturn<ProjectVo> createProject(@Valid @RequestBody ProjectCreateRequest request) {
    Project project = new Project();
    BeanUtils.copyProperties(request, project);
    Project created = projectService.createProject(project);
    ProjectVo vo = new ProjectVo();
    BeanUtils.copyProperties(created, vo);
    return ApiReturn.of(vo);
}

Exception Handling

Centralized Exception Handling

public class BusinessException extends RuntimeException {
    private final int code;
    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }
    public int getCode() {
        return code;
    }
}

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    public ApiReturn<Void> handleBusinessException(BusinessException e) {
        return ApiReturn.error(e.getCode(), e.getMessage());
    }
    @ExceptionHandler(Exception.class)
    public ApiReturn<Void> handleException(Exception e) {
        return ApiReturn.error(500, "Internal server error");
    }
}

Transaction Guidelines

  • Transaction boundaries belong to Service methods.
  • Use @Transactional(readOnly = true) for read-only queries.
  • Rollback on RuntimeException by default; document checked-exception rollbacks if needed.
  • Avoid transactions in Controller, Mapper, or XML.

Common Pitfalls

Pitfall 1: Cross-Layer Calls

// Incorrect: Controller directly calls Mapper
@RestController
public class ProjectController {
    private final ProjectMapper projectMapper;
    public ProjectController(ProjectMapper projectMapper) {
        this.projectMapper = projectMapper;
    }
    public List<Project> getProjects() {
        return projectMapper.selectList(null);
    }
}

Pitfall 2: Reverse Dependency

// Incorrect: Service depends on Controller
@Service
public class ProjectService {
    private ProjectController projectController;
}

Pitfall 3: Business Logic in SQL

<!-- Incorrect: business logic embedded in SQL -->
<select id="getById" resultMap="BaseResultMap">
    SELECT * FROM project
    WHERE id = #{id}
      AND is_deleted = 0
      AND has_permission(#{currentUserId}, id) = 1
</select>

Pitfall 4: Returning Entity Directly from Controller

// Incorrect: Controller returns Entity directly
@GetMapping("/projects/{id}")
public Project getProject(@PathVariable Long id) {
    return projectService.getProject(id);
}

Project Structure Template (Recommended)

src/main/java/com/xdyai/backend/
  controller/
    internal/
    request/
  service/
    common/
    context/
    integration/
  mapper/
  entity/
    enumeration/

src/main/resources/
  application.yml
  application-dev.yml
  mapper/
  db/
  validation/
  integration/

Best Practices Summary

Presentation Layer

  • Keep it thin: HTTP logic only
  • Use DTO/VO for data transfer
  • Centralized exception handling
  • No business logic
  • No direct Mapper access

Business Logic Layer

  • Contains core business logic
  • Use @Transactional to manage transactions
  • Orchestrate multiple services
  • No HTTP concerns
  • No direct database connection handling

Data Access Layer

  • Interfaces only
  • Clear method naming
  • CRUD via MyBatis-Plus
  • No SQL statements here
  • No business logic

Persistence Layer

  • Focus on SQL authoring
  • Use SQL fragments
  • Use dynamic SQL for complex queries
  • No business logic
  • No business validations

Cross-Layer Communication

  • Controller ← DTO
  • Service ← Entity
  • Mapper ← Entity
  • Controller ← VO
  • Avoid cross-layer calls
  • Avoid reverse dependencies
Weekly Installs
19
GitHub Stars
1
First Seen
Jan 30, 2026
Installed on
codex18
cursor15
github-copilot14
opencode14
gemini-cli13
amp12