首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java业务开发:从"会写代码"到"会做业务"

Java业务开发:从"会写代码"到"会做业务"

原创
作者头像
用户12689597
发布2026-08-17 17:05:01
发布2026-08-17 17:05:01
740
举报

一个容易被忽视的真相

很多Java开发者有一个误解:认为"业务开发"就是"CRUD开发",没什么技术含量。但现实是,真正复杂的不是技术本身,而是业务逻辑的复杂度

一个支付系统的退款流程,涉及订单状态变更、库存回滚、资金流转、消息通知、审计日志、风控校验……背后是几十个状态节点、数百种异常分支。写这种代码,考验的不是你对Stream API有多熟,而是你对业务有多深的理解。

Java业务开发的核心命题是:如何把纷繁复杂的业务规则,转化为清晰、可维护、高可靠的代码

三层架构:业务开发的"骨架"

虽然微服务和DDD(领域驱动设计)已经兴起,但三层架构依然是绝大多数Java业务系统的骨架:

代码语言:javascript
复制
┌─────────────────────────────────────────┐
│          Controller(接入层)            │  ← 接收请求,参数校验,返回响应
├─────────────────────────────────────────┤
│           Service(业务层)              │  ← 核心业务逻辑,事务边界
├─────────────────────────────────────────┤
│            DAO(数据层)                 │  ← 数据库操作,ORM映射
└─────────────────────────────────────────┘

每一层各司其职,界限清晰。理解并遵守分层规范,是业务代码可维护性的第一道防线。

各层的职责边界

Controller层:翻译官的角色。把HTTP请求翻译成Java对象,把业务结果翻译成HTTP响应。它只做三件事:参数校验、调用Service、封装返回。不该在这里写任何业务逻辑。

Service层:真正的业务核心。所有业务规则、流程编排、事务控制都在这里。Service层应该是与技术无关的——换一个Web框架,换一种数据库,Service层的代码不应该受到影响。

DAO层:数据的搬运工。负责与数据库打交道,把SQL结果映射为Java对象。现代的MyBatis、JPA让这一层变得很薄,但复杂的查询应该放在这里,而不是在Service层拼SQL字符串

事务管理:业务代码的"安全网"

业务开发中最常见的技术难点就是事务。一个典型的业务场景:创建订单 → 扣减库存 → 生成支付单。这三个操作必须全部成功或全部失败,否则数据就会不一致。

代码语言:javascript
复制
@Service
public class OrderService {
    
    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(OrderRequest request) {
        // 1. 保存订单
        Order order = orderDao.insert(request);
        
        // 2. 扣减库存(可能抛异常)
        inventoryService.deductStock(request.getProductId(), request.getQuantity());
        
        // 3. 生成支付单(可能抛异常)
        paymentService.createPayment(order.getId(), order.getAmount());
        
        return order;
    }
}

这段代码的核心是@Transactional注解。它保证了:任何方法抛异常,数据库自动回滚,数据保持一致。

事务的常见陷阱

陷阱一:Service内部调用失效 @Transactional基于代理机制,同一个类内部的方法调用不会触发事务。需要将事务方法拆分到不同的Bean中,或使用AopContext.currentProxy()。

陷阱二:异常被吞了 事务回滚依赖异常抛出。如果catch了异常却不抛出,事务就不会回滚。正确做法是:要么不catch,要么catch后抛出运行时异常。

陷阱三:大事务 一个事务中操作太多数据,会长时间持有数据库锁,引发性能问题。解决方案是:非关键操作移出事务,或拆分为多个小事务。

异常处理:让错误"可追溯、可理解"

业务系统的异常处理,核心原则是分层处理、分类响应

代码语言:javascript
复制
// 自定义业务异常
public class BusinessException extends RuntimeException {
    private String errorCode;
    private String errorMessage;
    
    public BusinessException(ErrorCode code) {
        super(code.getMessage());
        this.errorCode = code.getCode();
        this.errorMessage = code.getMessage();
    }
}

// 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public Result handleBusinessException(BusinessException e) {
        // 业务异常:返回友好的错误码和提示
        return Result.error(e.getErrorCode(), e.getErrorMessage());
    }
    
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        // 未知异常:记录详细日志,返回通用错误
        log.error("Unexpected error", e);
        return Result.error("500", "系统繁忙,请稍后重试");
    }
}

这种设计的核心思想是:

  • 业务异常(BusinessException):用户操作导致的错误,需要友好提示,如"库存不足"、"账户余额不够"
  • 系统异常(其他Exception):代码bug或外部系统故障,需要记录完整堆栈,但给用户返回通用提示

永远不要把堆栈信息直接返回给前端。 这不仅不安全,也毫无用户体验可言。

DTO/VO/Entity:数据传输的"身份管理"

在Java业务系统中,数据传输对象的管理是容易被忽视的工程细节:

对象类型

用途

示例场景

Entity

数据库表的映射

OrderEntity对应order表

DTO

服务间或层间传输

OrderDTO在Service和Controller之间传递

VO

前端展示用

OrderVO包含前端需要的所有字段

Request

接收前端参数

CreateOrderRequest

为什么不直接用Entity接收前端参数?两个核心原因:

安全隔离:Entity包含数据库字段,如createTime、updateTime、isDeleted,直接暴露给前端存在被恶意修改的风险。

灵活性:前端需要的数据结构往往和数据库表结构不同。比如前端需要"用户名+订单金额",而Entity里只有userId和amount。专门为前端设计的VO让接口更清晰。

业务设计原则:让代码活得更久

业务代码的生命周期往往比技术框架长得多。一套业务系统可能运行十年,而Spring Boot已经迭代了十几个版本。以下是让代码"长寿"的几个原则:

原则一:业务逻辑与技术框架解耦 Service层不应该出现Spring特有的注解(除了必要的@Transactional),不应该依赖Web层的类。这样即使更换框架,业务逻辑也能复用。

原则二:面向接口编程 Service定义接口,Controller依赖接口而非实现类。这为单元测试Mock、多实现切换提供了便利。

代码语言:javascript
复制
// 面向接口,便于测试和扩展
public interface OrderService {
    Order createOrder(OrderRequest request);
}

@Service
public class OrderServiceImpl implements OrderService {
    // 实现业务逻辑
}

原则三:避免过深的继承 业务代码推荐"组合优于继承"。三层继承让代码难以理解和调试,两层已经是极限。

原则四:防御性编程 不要假设外部输入都是合法的。对一切外部输入做校验,是业务系统的基本素养。

性能考量:业务代码里的"隐形杀手"

业务系统的性能问题,往往不在代码本身,而在"不经意"的细节:

N+1查询问题:循环中执行SQL查询,一次请求产生N+1次数据库往返。使用批量查询(IN子句)或MyBatis的collection标签一次性加载关联数据。

不合理的索引:查询条件没有对应索引,数据量增长后性能急剧下降。建表时就要预估查询模式,建立合理的索引。

慢SQL:复杂的多表关联查询,随数据量增长变成慢查询。核心原则:能拆就拆(代码里多次查询),不能拆就优化(建立中间表、使用缓存)。

大对象:一次查询返回几千条记录,内存暴涨。务必使用分页查询。

代码语言:javascript
复制
// 分页查询示例
public PageResult<OrderVO> queryOrders(OrderQueryRequest request) {
    PageHelper.startPage(request.getPageNum(), request.getPageSize());
    List<Order> orders = orderDao.queryByCondition(request.getConditions());
    PageInfo<Order> pageInfo = new PageInfo<>(orders);
    return convertToPageResult(pageInfo);
}

单元测试:业务代码的"安全网"

很多业务开发不写单元测试,理由是"业务太复杂,不好测"。但正因为业务复杂,才更需要测试覆盖:

代码语言:javascript
复制
@SpringBootTest
public class OrderServiceTest {
    
    @MockBean
    private InventoryService inventoryService;
    
    @Autowired
    private OrderService orderService;
    
    @Test
    public void testCreateOrder_Success() {
        // Given:准备测试数据
        OrderRequest request = buildMockRequest();
        when(inventoryService.deductStock(any(), any())).thenReturn(true);
        
        // When:执行被测方法
        Order order = orderService.createOrder(request);
        
        // Then:验证结果
        assertNotNull(order);
        assertEquals(OrderStatus.PENDING_PAYMENT, order.getStatus());
    }
    
    @Test(expected = BusinessException.class)
    public void testCreateOrder_InventoryInsufficient() {
        // 测试库存不足的异常分支
        OrderRequest request = buildMockRequest();
        when(inventoryService.deductStock(any(), any()))
            .thenThrow(new BusinessException(ErrorCode.INVENTORY_INSUFFICIENT));
        
        orderService.createOrder(request);
    }
}

好的单元测试不是为了写而写,而是为了验证逻辑的正确性、防止未来的修改引入回归Bug

日志:业务系统的"眼睛"

业务代码里的日志,不是给机器看的,是给人看的——尤其是深夜被叫醒的值班工程师:

代码语言:javascript
复制
@Slf4j
@Service
public class OrderService {
    
    public Order createOrder(OrderRequest request) {
        log.info("开始创建订单, userId={}, productId={}", 
            request.getUserId(), request.getProductId());
        
        try {
            Order order = doCreateOrder(request);
            log.info("订单创建成功, orderId={}, amount={}", 
                order.getId(), order.getAmount());
            return order;
        } catch (Exception e) {
            log.error("订单创建失败, userId={}, productId={}", 
                request.getUserId(), request.getProductId(), e);
            throw e;
        }
    }
}

日志的黄金法则:关键路径上的每一步都要有日志,关键字段都要带上(订单号、用户ID、金额等)。没有日志支撑的排查,就像在黑暗中摸索。

写在最后

Java业务开发的真正挑战,不在于技术的高深,而在于如何在复杂多变的业务需求中,维持代码的清晰、稳定和可维护

很多Java工程师工作三五年后,技术上没有太大瓶颈,但业务理解能力却拉开了差距。好的业务开发工程师,是产品经理最信任的技术伙伴,是运维最依赖的故障排查员,是新人最愿意跟随的技术榜样。

技术服务于业务,但业务成就了技术——正是那些复杂的业务场景,才让Java这门语言在企业级开发中历久弥新

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一个容易被忽视的真相
  • 三层架构:业务开发的"骨架"
    • 各层的职责边界
  • 事务管理:业务代码的"安全网"
    • 事务的常见陷阱
  • 异常处理:让错误"可追溯、可理解"
  • DTO/VO/Entity:数据传输的"身份管理"
  • 业务设计原则:让代码活得更久
  • 性能考量:业务代码里的"隐形杀手"
  • 单元测试:业务代码的"安全网"
  • 日志:业务系统的"眼睛"
  • 写在最后
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档