Skip to content

纯手工开发方式完整指南

⚙️ 精细控制:适用于复杂业务逻辑、性能敏感场景、特殊接口设计

📋 目录


核心概念

什么是纯手工开发?

纯手工开发方式是完全自定义实现Controller、Service、Mapper的开发模式,提供最大的灵活性和控制力。

适用场景

  • 🔧 复杂业务逻辑 - 需要精细控制每个步骤
  • 性能优化 - 需要特殊的查询优化
  • 🎯 特殊接口设计 - 非标准CRUD操作
  • 🔌 第三方集成 - 需要调用外部API
  • 📊 复杂查询 - 多表关联、统计分析

纯手工开发强制规范

选择纯手工开发后,必须遵循以下规范:

  1. 不继承Basic系列基类 - 完全自定义实现
  2. 使用ServiceImpl继承MyBatis-Flex的ServiceImpl - 获得基础的CRUD能力
  3. 手动实现所有CRUD方法 - 根据业务需求自定义实现
  4. 严格遵循项目的编码规范 - 包括异常处理、日志记录、返回格式等

核心依赖

java
// MyBatis-Flex
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.annotation.UseDataSource;

// Spring
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

// 项目基础
import jpwise.base.ActionResult;
import jpwise.annotation.BuildQuery;
import jpwise.base.query.mode.QueryBuilder;

基础架构

三层架构标准

Controller (接口层)
    ↓ 调用
Service (业务层) 
    ↓ 调用  
Mapper (数据层)

包结构规范

jpwise/extend/
├── controller/         # REST API控制器
├── service/           # 业务逻辑接口
├── service/impl/      # 业务逻辑实现
├── base/
│   ├── entity/        # 实体类
│   └── mapper/        # Mapper接口
└── model/             # DTO/VO对象(可选)

Controller层开发

基础Controller结构

java
@RestController
@RequestMapping("/api/extend/Business")
public class BusinessController {
    
    @Autowired
    private BusinessService businessService;
    
    // 分页查询列表
    @BuildQuery  // 注解在方法上,不是参数上
    @GetMapping
    public ActionResult<PageListVO<BusinessEntity>> getList(QueryBuilder pagination) {
        List<BusinessEntity> data = businessService.getList(pagination);
        PaginationVO paginationVO = JsonUtil.getJsonToBean(pagination, PaginationVO.class);
        return ActionResult.page(data, paginationVO);
    }
    
    // 获取详情
    @GetMapping("/{id}")
    public ActionResult<BusinessEntity> getInfo(@PathVariable String id) {
        BusinessEntity entity = businessService.getInfo(id);
        if (entity == null) {
            return ActionResult.fail(MsgCode.FA002.get());
        }
        return ActionResult.success(entity);
    }
    
    // 创建数据
    @PostMapping
    public ActionResult<String> create(@Valid @RequestBody BusinessEntity entity) {
        businessService.create(entity);
        return ActionResult.success(MsgCode.SU001.get());
    }
    
    // 更新数据(使用POST,不用PUT)
    @PostMapping("/update/{id}")
    public ActionResult<String> update(@PathVariable String id, 
                                      @Valid @RequestBody BusinessEntity entity) {
        businessService.update(id, entity);
        return ActionResult.success(MsgCode.SU001.get());
    }
    
    // 删除数据(使用POST,不用DELETE)
    @PostMapping("/{id}")
    public ActionResult<String> delete(@PathVariable String id) {
        businessService.delete(id);
        return ActionResult.success(MsgCode.SU001.get());
    }
}

复杂查询接口

java
@RestController
@RequestMapping("/api/extend/Statistics")
public class StatisticsController {
    
    @Autowired
    private StatisticsService statisticsService;
    
    // 统计分析接口
    @BuildQuery
    @GetMapping("/summary")
    public ActionResult<Map<String, Object>> getSummary(QueryBuilder query) {
        // 解析查询参数
        String startDate = ConvertUtil.toStr(query.getCondition().get("startDate"));
        String endDate = ConvertUtil.toStr(query.getCondition().get("endDate"));
        String department = ConvertUtil.toStr(query.getCondition().get("department"));
        
        Map<String, Object> result = statisticsService.getSummary(startDate, endDate, department);
        return ActionResult.success(result);
    }
    
    // 导出Excel
    @PostMapping("/export")
    public void export(@RequestBody Map<String, Object> params, HttpServletResponse response) {
        statisticsService.exportExcel(params, response);
    }
    
    // 批量操作
    @PostMapping("/batch")
    public ActionResult<String> batchOperation(@RequestBody BatchOperationDTO dto) {
        statisticsService.batchProcess(dto);
        return ActionResult.success("批量操作成功");
    }
}

Service层开发

Service接口定义

java
public interface BusinessService {
    // 基础CRUD
    List<BusinessEntity> getList(QueryBuilder pagination);
    BusinessEntity getInfo(String id);
    void create(BusinessEntity entity);
    void update(String id, BusinessEntity entity);
    void delete(String id);
    
    // 业务方法
    void approve(String id, ApprovalDTO approval);
    List<BusinessEntity> getMyTasks(String userId);
    Map<String, Object> getStatistics(StatisticsQuery query);
}

Service实现

java
import lombok.extern.slf4j.Slf4j;

@Slf4j  // 必须添加日志注解
@Service
@UseDataSource(DbNameConst.JPWISE_DEMO)  // 必须指定数据源
public class BusinessServiceImpl extends ServiceImpl<BusinessMapper, BusinessEntity> 
                                implements BusinessService {
    
    @Autowired
    private UserProvider userProvider;
    
    @Autowired
    private RedisUtil redisUtil;
    
    @Autowired
    private MessageService messageService;
    
    @Override
    public List<BusinessEntity> getList(QueryBuilder pagination) {
        // 构建查询条件
        QueryWrapper queryWrapper = new QueryWrapper();
        
        // 基础过滤
        queryWrapper.eq(BusinessEntity::getENABLEDMARK, "1")
                   .isNull(BusinessEntity::getDELETEUSER);
        
        // 关键字搜索(多字段OR)
        if (StringUtil.isNotEmpty(pagination.getKeyword())) {
            queryWrapper.and((Consumer<QueryWrapper>) w -> {
                w.like(BusinessEntity::getTITLE, pagination.getKeyword())
                 .or((Consumer<QueryWrapper>) ww -> ww.like(BusinessEntity::getCODE, pagination.getKeyword()))
                 .or((Consumer<QueryWrapper>) ww -> ww.like(BusinessEntity::getNAME, pagination.getKeyword()));
            });
        }
        
        // 条件过滤
        Map<String, Object> condition = pagination.getCondition();
        if (condition != null) {
            // 日期范围
            String startDate = ConvertUtil.toStr(condition.get("startDate"));
            String endDate = ConvertUtil.toStr(condition.get("endDate"));
            if (StringUtil.isNotEmpty(startDate) && StringUtil.isNotEmpty(endDate)) {
                Date start = DateUtil.stringToDate(startDate + " 00:00:00");
                Date end = DateUtil.stringToDate(endDate + " 23:59:59");
                queryWrapper.between(BusinessEntity::getCREATORTIME, start, end);
            }
            
            // 状态过滤
            String status = ConvertUtil.toStr(condition.get("status"));
            if (StringUtil.isNotEmpty(status)) {
                queryWrapper.eq(BusinessEntity::getSTATUS, status);
            }
        }
        
        // 排序(false = DESC, true = ASC)
        queryWrapper.orderBy(BusinessEntity::getCREATORTIME, false);
        
        // 执行分页查询
        Page<BusinessEntity> page = new Page<>(pagination.getCurrentPage(), pagination.getPageSize());
        Page<BusinessEntity> result = this.page(page, queryWrapper);
        
        // 设置分页数据
        return pagination.setData(result.getRecords(), result.getTotalRow());
    }
    
    @Override
    public BusinessEntity getInfo(String id) {
        // 从缓存获取
        String cacheKey = "business:info:" + id;
        BusinessEntity cached = (BusinessEntity) redisUtil.getString(cacheKey);
        if (cached != null) {
            return cached;
        }
        
        // 从数据库查询
        BusinessEntity entity = this.getById(id);
        if (entity != null) {
            // 加载关联数据
            loadRelatedData(entity);
            
            // 存入缓存
            redisUtil.insert(cacheKey, entity, RedisUtil.CACHE_HOUR);
        }
        
        return entity;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(BusinessEntity entity) {
        // 设置基础信息
        UserInfo userInfo = userProvider.get();
        entity.setID(RandomUtil.uuId());
        entity.setCREATORUSER(userInfo.getUserId());
        entity.setCREATORTIME(DateUtil.getNowDate());
        entity.setENABLEDMARK("1");
        entity.setORGANIZE(userInfo.getOrganizeId());
        
        // 生成业务编号
        if (StringUtil.isEmpty(entity.getCODE())) {
            entity.setCODE(generateBusinessCode());
        }
        
        // 业务验证
        validateBusiness(entity);
        
        // 保存数据
        this.save(entity);
        
        // 发送通知
        sendCreateNotification(entity);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(String id, BusinessEntity entity) {
        // 获取原数据
        BusinessEntity original = this.getById(id);
        if (original == null) {
            throw new DataException("数据不存在");
        }
        
        // 权限验证
        if (!hasUpdatePermission(original)) {
            throw new BusinessException("无权限修改");
        }
        
        // 设置更新信息
        entity.setID(id);
        entity.setLASTMODIFYUSER(userProvider.get().getUserId());
        entity.setLASTMODIFYTIME(DateUtil.getNowDate());
        
        // 更新数据
        this.updateById(entity);
        
        // 清除缓存
        redisUtil.remove("business:info:" + id);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String id) {
        BusinessEntity entity = this.getById(id);
        if (entity == null) {
            throw new DataException("数据不存在");
        }
        
        // 软删除
        entity.setDELETEUSER(userProvider.get().getUserId());
        entity.setDELETETIME(DateUtil.getNowDate());
        this.updateById(entity);
        
        // 清除缓存
        redisUtil.remove("business:info:" + id);
    }
    
    // 业务方法示例
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approve(String id, ApprovalDTO approval) {
        BusinessEntity entity = this.getById(id);
        if (entity == null) {
            throw new DataException("数据不存在");
        }
        
        // 更新状态
        entity.setSTATUS("APPROVED");
        entity.setAPPROVALTIME(DateUtil.getNowDate());
        entity.setAPPROVALUSER(userProvider.get().getUserId());
        entity.setAPPROVALREMARK(approval.getRemark());
        
        this.updateById(entity);
        
        // 发送通知
        String title = "审批通过通知";
        String content = "您的申请已审批通过";
        messageService.sentMessage(Arrays.asList(entity.getCREATORUSER()), title, content, 1);
    }
    
    // 辅助方法
    private String generateBusinessCode() {
        String date = DateUtil.dateNow("yyyyMMdd");
        String random = RandomUtil.enUuid().substring(0, 4).toUpperCase();
        return "BUS" + date + random;
    }
    
    private void validateBusiness(BusinessEntity entity) {
        // 业务验证逻辑
        if (entity.getAMOUNT() != null && entity.getAMOUNT().compareTo(new BigDecimal("1000000")) > 0) {
            throw new BusinessException("金额不能超过100万");
        }
    }
    
    private boolean hasUpdatePermission(BusinessEntity entity) {
        UserInfo user = userProvider.get();
        // 创建者或管理员可以修改
        return user.getUserId().equals(entity.getCREATORUSER()) || user.isAdmin();
    }
    
    private void loadRelatedData(BusinessEntity entity) {
        // 加载关联数据的逻辑
    }
    
    private void sendCreateNotification(BusinessEntity entity) {
        // 发送创建通知的逻辑
    }
}

Mapper层开发

基础Mapper接口

java
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper  // 必须添加@Mapper注解
public interface BusinessMapper extends BaseMapper<BusinessEntity> {
    // 继承BaseMapper获得基础操作
    // 可添加自定义SQL方法
}

Service实现中使用JdbcUtil处理复杂SQL

在Service实现类中处理复杂查询和数据库操作:

java
import jpwise.database.util.JdbcUtil;
import jpwise.database.util.ConnUtil;
import jpwise.database.constant.DbNameConst;
import jpwise.database.model.dto.PrepareSqlDTO;
import jpwise.database.model.page.JdbcPageMod;
import lombok.extern.slf4j.Slf4j;

@Slf4j  // 必须添加日志注解
@Service
@UseDataSource(DbNameConst.JPWISE_DEMO)
public class BusinessServiceImpl extends ServiceImpl<BusinessMapper, BusinessEntity> 
                                implements BusinessService {
    
    @Autowired
    private UserProvider userProvider;
    
    // 复杂关联查询 - 在Service中使用JdbcUtil
    public List<Map<String, Object>> getDetailWithRelations(String id) {
        Connection conn = ConnUtil.getConn(DbNameConst.JPWISE_DEMO);
        
        String sql = """
            SELECT 
                b.*,
                u.real_name as creator_name,
                d.name as department_name
            FROM business_table b
            LEFT JOIN sys_user u ON b.creator_user = u.id
            LEFT JOIN sys_department d ON b.organize = d.id
            WHERE b.id = ?
        """;
        
        PrepareSqlDTO sqlDTO = new PrepareSqlDTO();
        sqlDTO.setConn(conn);
        sqlDTO.setPrepareSql(sql);
        sqlDTO.setDataList(Arrays.asList(id));
        
        return JdbcUtil.queryList(sqlDTO);
    }
    
    // 动态条件查询 - 在Service中使用JdbcUtil
    public List<Map<String, Object>> dynamicQuery(String status, String keyword, 
                                                   Date startDate, Date endDate) {
        Connection conn = ConnUtil.getConn(DbNameConst.JPWISE_DEMO);
        
        StringBuilder sql = new StringBuilder("SELECT * FROM business_table WHERE 1=1");
        List<Object> params = new ArrayList<>();
        
        if (StringUtil.isNotEmpty(status)) {
            sql.append(" AND status = ?");
            params.add(status);
        }
        
        if (StringUtil.isNotEmpty(keyword)) {
            sql.append(" AND (title LIKE ? OR code LIKE ?)");
            params.add("%" + keyword + "%");
            params.add("%" + keyword + "%");
        }
        
        if (startDate != null) {
            sql.append(" AND creator_time >= ?");
            params.add(startDate);
        }
        
        if (endDate != null) {
            sql.append(" AND creator_time <= ?");
            params.add(endDate);
        }
        
        sql.append(" ORDER BY creator_time DESC");
        
        PrepareSqlDTO sqlDTO = new PrepareSqlDTO();
        sqlDTO.setConn(conn);
        sqlDTO.setPrepareSql(sql.toString());
        sqlDTO.setDataList(params);
        
        return JdbcUtil.queryList(sqlDTO);
    }
    
    // 分页查询 - 在Service中使用JdbcUtil
    public JdbcPageMod<Map<String, Object>> getPageDataWithJdbc(QueryBuilder queryBuilder) {
        Connection conn = ConnUtil.getConn(DbNameConst.JPWISE_DEMO);
        
        String sql = """
            SELECT 
                b.*,
                u.real_name as creator_name,
                d.name as department_name
            FROM business_table b
            LEFT JOIN sys_user u ON b.creator_user = u.id
            LEFT JOIN sys_department d ON b.organize = d.id
            WHERE b.enabled_mark = '1' AND b.delete_user IS NULL
        """;
        
        PrepareSqlDTO sqlDTO = new PrepareSqlDTO();
        sqlDTO.setConn(conn);
        sqlDTO.setPrepareSql(sql);
        sqlDTO.setDataList(new ArrayList<>());
        
        return JdbcUtil.queryPage(sqlDTO, queryBuilder);
    }
    
    // 统计查询 - 在Service中使用JdbcUtil
    public List<Map<String, Object>> getMonthlyStatistics(Date startDate, Date endDate) {
        Connection conn = ConnUtil.getConn(DbNameConst.JPWISE_DEMO);
        
        String sql = """
            SELECT 
                COUNT(*) as total,
                SUM(amount) as totalAmount,
                AVG(amount) as avgAmount,
                DATE_FORMAT(creator_time, '%Y-%m') as month
            FROM business_table
            WHERE enabled_mark = '1' 
                AND delete_user IS NULL
                AND creator_time BETWEEN ? AND ?
            GROUP BY DATE_FORMAT(creator_time, '%Y-%m')
            ORDER BY month DESC
        """;
        
        PrepareSqlDTO sqlDTO = new PrepareSqlDTO();
        sqlDTO.setConn(conn);
        sqlDTO.setPrepareSql(sql);
        sqlDTO.setDataList(Arrays.asList(startDate, endDate));
        
        return JdbcUtil.queryList(sqlDTO);
    }
    
    // 批量更新 - 在Service中使用JdbcUtil
    @Transactional(rollbackFor = Exception.class)
    public int batchUpdateStatus(List<String> ids, String status) {
        Connection conn = ConnUtil.getConn(DbNameConst.JPWISE_DEMO);
        
        // 构建IN子句
        String inClause = ids.stream().map(id -> "?").collect(Collectors.joining(","));
        String sql = String.format("""
            UPDATE business_table 
            SET status = ?, 
                last_modify_user = ?,
                last_modify_time = NOW()
            WHERE id IN (%s)
        """, inClause);
        
        List<Object> params = new ArrayList<>();
        params.add(status);
        params.add(userProvider.get().getUserId());
        params.addAll(ids);
        
        PrepareSqlDTO sqlDTO = new PrepareSqlDTO();
        sqlDTO.setConn(conn);
        sqlDTO.setPrepareSql(sql);
        sqlDTO.setDataList(params);
        
        return JdbcUtil.executeUpdate(sqlDTO);
    }
}

---

## 完整示例

### 费用报销功能实现

#### 1. 实体类
```java
@Data
@Table("EXPENSE_CLAIM")
@Schema(description = "费用报销")
public class ExpenseClaimEntity extends BaseEntity {
    
    @Schema(description = "报销单号")
    @Column("EXPENSENO")
    @JsonProperty("EXPENSENO")
    private String EXPENSENO;
    
    @Schema(description = "报销标题")
    @Column("TITLE")
    @JsonProperty("TITLE")
    private String TITLE;
    
    @Schema(description = "报销金额")
    @Column("AMOUNT")
    @JsonProperty("AMOUNT")
    private BigDecimal AMOUNT;
    
    @Schema(description = "报销类型")
    @Column("EXPENSETYPE")
    @JsonProperty("EXPENSETYPE")
    private String EXPENSETYPE;
    
    @Schema(description = "审批状态")
    @Column("APPROVALSTATUS")
    @JsonProperty("APPROVALSTATUS")
    private String APPROVALSTATUS;
}

2. Controller层

java
@RestController
@RequestMapping("/api/extend/ExpenseClaim")
public class ExpenseClaimController {
    
    @Autowired
    private ExpenseClaimService expenseClaimService;
    
    @BuildQuery
    @GetMapping
    public ActionResult<PageListVO<ExpenseClaimEntity>> getList(QueryBuilder pagination) {
        List<ExpenseClaimEntity> data = expenseClaimService.getList(pagination);
        PaginationVO paginationVO = JsonUtil.getJsonToBean(pagination, PaginationVO.class);
        return ActionResult.page(data, paginationVO);
    }
    
    @GetMapping("/{id}")
    public ActionResult<ExpenseClaimEntity> getInfo(@PathVariable String id) {
        ExpenseClaimEntity entity = expenseClaimService.getInfo(id);
        return ActionResult.success(entity);
    }
    
    @PostMapping
    public ActionResult<String> create(@Valid @RequestBody ExpenseClaimEntity entity) {
        expenseClaimService.create(entity);
        return ActionResult.success(MsgCode.SU001.get());
    }
    
    @PostMapping("/update/{id}")
    public ActionResult<String> update(@PathVariable String id, 
                                      @Valid @RequestBody ExpenseClaimEntity entity) {
        expenseClaimService.update(id, entity);
        return ActionResult.success(MsgCode.SU001.get());
    }
    
    // 自定义业务接口
    @PostMapping("/{id}/submit")
    public ActionResult<String> submit(@PathVariable String id) {
        expenseClaimService.submit(id);
        return ActionResult.success("提交成功");
    }
    
    @GetMapping("/statistics")
    public ActionResult<Map<String, Object>> getStatistics() {
        Map<String, Object> stats = expenseClaimService.getUserStatistics();
        return ActionResult.success(stats);
    }
}

3. Service实现

java
import lombok.extern.slf4j.Slf4j;

@Slf4j  // 必须添加日志注解
@Service
@UseDataSource(DbNameConst.JPWISE_DEMO)
public class ExpenseClaimServiceImpl extends ServiceImpl<ExpenseClaimMapper, ExpenseClaimEntity> 
                                    implements ExpenseClaimService {
    
    @Autowired
    private UserProvider userProvider;
    
    @Autowired
    private MessageService messageService;
    
    @Override
    public List<ExpenseClaimEntity> getList(QueryBuilder pagination) {
        QueryWrapper queryWrapper = new QueryWrapper();
        
        // 基础过滤
        queryWrapper.eq(ExpenseClaimEntity::getENABLEDMARK, "1")
                   .isNull(ExpenseClaimEntity::getDELETEUSER);
        
        // 只查看自己的报销单
        UserInfo user = userProvider.get();
        if (!user.isAdmin()) {
            queryWrapper.eq(ExpenseClaimEntity::getCREATORUSER, user.getUserId());
        }
        
        // 关键字搜索
        if (StringUtil.isNotEmpty(pagination.getKeyword())) {
            queryWrapper.and((Consumer<QueryWrapper>) w -> {
                w.like(ExpenseClaimEntity::getTITLE, pagination.getKeyword())
                 .or((Consumer<QueryWrapper>) ww -> ww.like(ExpenseClaimEntity::getEXPENSENO, pagination.getKeyword()));
            });
        }
        
        // 状态过滤
        Map<String, Object> condition = pagination.getCondition();
        if (condition != null && condition.get("status") != null) {
            queryWrapper.eq(ExpenseClaimEntity::getAPPROVALSTATUS, condition.get("status"));
        }
        
        queryWrapper.orderBy(ExpenseClaimEntity::getCREATORTIME, false);
        
        Page<ExpenseClaimEntity> page = new Page<>(pagination.getCurrentPage(), pagination.getPageSize());
        Page<ExpenseClaimEntity> result = this.page(page, queryWrapper);
        
        return pagination.setData(result.getRecords(), result.getTotalRow());
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(ExpenseClaimEntity entity) {
        // 设置基础信息
        entity.setID(RandomUtil.uuId());
        entity.setCREATORUSER(userProvider.get().getUserId());
        entity.setCREATORTIME(DateUtil.getNowDate());
        entity.setENABLEDMARK("1");
        entity.setAPPROVALSTATUS("DRAFT");
        
        // 生成报销单号
        if (StringUtil.isEmpty(entity.getEXPENSENO())) {
            String date = DateUtil.dateNow("yyyyMMdd");
            String random = RandomUtil.enUuid().substring(0, 4).toUpperCase();
            entity.setEXPENSENO("EXP" + date + random);
        }
        
        // 验证金额
        if (entity.getAMOUNT() == null || entity.getAMOUNT().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException("报销金额必须大于0");
        }
        
        this.save(entity);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submit(String id) {
        ExpenseClaimEntity entity = this.getById(id);
        if (entity == null) {
            throw new DataException("报销单不存在");
        }
        
        if (!"DRAFT".equals(entity.getAPPROVALSTATUS())) {
            throw new BusinessException("只能提交草稿状态的报销单");
        }
        
        // 更新状态
        entity.setAPPROVALSTATUS("PENDING");
        entity.setSUBMITTIME(DateUtil.getNowDate());
        this.updateById(entity);
        
        // 发送通知给审批人
        List<String> approvers = getApprovers(entity);
        String title = "报销审批提醒";
        String content = String.format("您有新的报销单【%s】待审批,金额:%s元", 
            entity.getTITLE(), entity.getAMOUNT());
        messageService.sentMessage(approvers, title, content, 1);
    }
    
    @Override
    public Map<String, Object> getUserStatistics() {
        String userId = userProvider.get().getUserId();
        
        Map<String, Object> stats = new HashMap<>();
        
        // 统计各状态数量
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq(ExpenseClaimEntity::getCREATORUSER, userId)
                   .eq(ExpenseClaimEntity::getENABLEDMARK, "1")
                   .isNull(ExpenseClaimEntity::getDELETEUSER);
        
        List<ExpenseClaimEntity> all = this.list(queryWrapper);
        
        long draftCount = all.stream().filter(e -> "DRAFT".equals(e.getAPPROVALSTATUS())).count();
        long pendingCount = all.stream().filter(e -> "PENDING".equals(e.getAPPROVALSTATUS())).count();
        long approvedCount = all.stream().filter(e -> "APPROVED".equals(e.getAPPROVALSTATUS())).count();
        
        BigDecimal totalAmount = all.stream()
            .filter(e -> "APPROVED".equals(e.getAPPROVALSTATUS()))
            .map(ExpenseClaimEntity::getAMOUNT)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        stats.put("draftCount", draftCount);
        stats.put("pendingCount", pendingCount);
        stats.put("approvedCount", approvedCount);
        stats.put("totalAmount", totalAmount);
        
        return stats;
    }
    
    private List<String> getApprovers(ExpenseClaimEntity entity) {
        // 获取审批人逻辑
        return Arrays.asList("approver1", "approver2");
    }
}

高级特性

1. 复杂查询优化

java
// 使用QueryWrapper的高级特性
QueryWrapper queryWrapper = new QueryWrapper();

// 子查询
queryWrapper.inSql(Entity::getDEPARTMENT, 
    "SELECT id FROM department WHERE type = 'SALES'");

// EXISTS查询
queryWrapper.exists("SELECT 1 FROM related_table WHERE related_id = " + Entity.TABLE + ".id");

// 自定义SQL片段
queryWrapper.apply("DATE_FORMAT(creator_time, '%Y-%m') = {0}", "2024-08");

// 分组和聚合
queryWrapper.select("department", "COUNT(*) as count", "SUM(amount) as total")
           .groupBy(Entity::getDEPARTMENT)
           .having("COUNT(*) > 10");

2. 事务管理

java
@Service
public class ComplexBusinessService {
    
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void complexOperation() {
        // 多个数据库操作
        mainTableService.save(mainEntity);
        detailTableService.saveBatch(detailList);
        
        // 如果出现异常,所有操作都会回滚
        if (validateFails) {
            throw new BusinessException("验证失败");
        }
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void independentOperation() {
        // 独立事务,不受外部事务影响
    }
}

3. 批量操作优化

java
@Override
public void batchImport(List<BusinessEntity> entities) {
    // 批量插入(分批处理避免内存溢出)
    int batchSize = 1000;
    for (int i = 0; i < entities.size(); i += batchSize) {
        int end = Math.min(i + batchSize, entities.size());
        List<BusinessEntity> batch = entities.subList(i, end);
        
        // 设置基础信息
        batch.forEach(entity -> {
            entity.setID(RandomUtil.uuId());
            entity.setCREATORUSER(userProvider.get().getUserId());
            entity.setCREATORTIME(DateUtil.getNowDate());
        });
        
        this.saveBatch(batch);
    }
}

4. 缓存策略

java
@Service
public class CachedBusinessService {
    
    @Autowired
    private RedisUtil redisUtil;
    
    public BusinessEntity getWithCache(String id) {
        // 多级缓存策略
        String l1Key = "business:l1:" + id;
        String l2Key = "business:l2:" + id;
        
        // L1缓存(短期)
        Object l1Cache = redisUtil.getString(l1Key);
        if (l1Cache != null) {
            return (BusinessEntity) l1Cache;
        }
        
        // L2缓存(长期)
        Object l2Cache = redisUtil.getString(l2Key);
        if (l2Cache != null) {
            redisUtil.insert(l1Key, l2Cache, RedisUtil.CACHE_MINUTE * 10);
            return (BusinessEntity) l2Cache;
        }
        
        // 数据库查询
        BusinessEntity entity = this.getById(id);
        if (entity != null) {
            redisUtil.insert(l1Key, entity, RedisUtil.CACHE_MINUTE * 10);
            redisUtil.insert(l2Key, entity, RedisUtil.CACHE_DAY);
        }
        
        return entity;
    }
}

最佳实践

1. 统一返回格式

java
// 始终使用ActionResult包装返回值
return ActionResult.success(data);
return ActionResult.fail(MsgCode.FA001.get());
return ActionResult.page(list, pagination);

2. 异常处理

java
// 使用项目定义的异常类型
throw new BusinessException("业务异常");
throw new DataException("数据异常");

// Service层捕获并转换异常
try {
    // 业务逻辑
} catch (Exception e) {
    log.error("操作失败", e);
    throw new BusinessException("操作失败:" + e.getMessage());
}

3. 参数验证

java
// Controller层使用@Valid注解
@PostMapping
public ActionResult create(@Valid @RequestBody BusinessEntity entity) {
    // @Valid会自动验证实体类的注解约束
}

// Service层手动验证
if (StringUtil.isEmpty(entity.getCODE())) {
    throw new BusinessException("编号不能为空");
}

4. 日志记录

java
@Slf4j
@Service
public class BusinessServiceImpl {
    
    public void businessMethod() {
        log.info("开始执行业务方法,参数:{}", params);
        
        try {
            // 业务逻辑
            log.debug("中间处理步骤");
            
        } catch (Exception e) {
            log.error("业务方法执行失败", e);
            throw e;
        }
        
        log.info("业务方法执行成功");
    }
}

5. 性能优化建议

  • 使用分页查询避免一次加载大量数据
  • 合理使用缓存减少数据库访问
  • 批量操作使用saveBatch/updateBatchById
  • 复杂查询考虑使用原生SQL
  • 大事务拆分为小事务

常见问题

Q: 什么时候选择纯手工开发? A: 当需要精细控制业务逻辑、优化性能、实现复杂查询或集成第三方服务时。

Q: 如何处理多表关联查询? A: 可以使用QueryWrapper的join方法,或在Mapper中编写自定义SQL。

Q: 如何实现动态查询条件? A: 使用QueryWrapper的条件判断,或在XML中使用MyBatis的动态SQL标签。

Q: 事务如何管理? A: 使用@Transactional注解,注意设置rollbackFor = Exception.class。


💡 提示:纯手工开发方式提供最大的灵活性,适合复杂业务场景。合理使用项目提供的工具类和规范,可以在保持灵活性的同时提高开发效率。