在大型 Java 项目开发中,Service 层之间的循环依赖是一个常见且棘手的问题。当两个或多个 Service 互相引用时,Spring 容器在初始化时会抛出BeanCurrentlyInCreationException异常,导致应用启动失败。本文将深入探讨 Service 循环依赖问题,并介绍 MyBatis-Plus 提供的 Db 工具类如何优雅解决这一问题。

一、Service 循环依赖问题场景再现

1. 循环依赖问题产生的原因

在面向对象设计中,循环依赖指的是两个或多个类彼此持有对方的引用,形成一个闭合的依赖环。在 Spring 框架中,这种情况会导致:

  • Bean 初始化过程中出现状态不一致
  • 容器无法正确构建对象图
  • 应用启动时抛出初始化异常

2.模拟循环依赖场景

我们通过一个电商项目中的简单场景来模拟循环依赖问题:

// UserService.java
@Service
public class UserService {
    
    @Autowired
    private OrderService orderService;
    
    public User getById(Long id) {
        // 查询用户信息
        User user = userMapper.selectById(id);
        // 查询用户订单信息
        List<Order> orders = orderService.getByUserId(id);
        user.setOrders(orders);
        return user;
    }
}

// OrderService.java
@Service
public class OrderService {
    
    @Autowired
    private UserService userService;
    
    public List<Order> getByUserId(Long userId) {
        // 查询订单信息
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        List<Order> orders = orderMapper.selectList(wrapper);
        // 填充订单中的用户信息
        for (Order order : orders) {
            User user = userService.getById(order.getUserId());
            order.setUser(user);
        }
        return orders;
    }
}

当 Spring 容器尝试初始化UserServiceOrderService时,会发现它们互相依赖,导致典型的循环依赖异常:

org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'userService': 
Requested bean is currently in creation: 
Is there an unresolvable circular reference?

二、MyBatis-Plus Db 工具类介绍

1.Db 工具类的设计理念

MyBatis-Plus 的Db工具类是一个静态工具类,它提供了与IService接口相似的 CRUD 方法。其核心设计理念是:

  • 避免通过 Spring 依赖注入获取 Service 实例
  • 提供静态方法直接操作数据库
  • 打破 Service 层之间的直接依赖关系

2. Db 工具类的主要功能方法

Db 工具类提供了丰富的数据库操作方法,以下是常用方法列表:

方法名称 功能描述
insert(T entity) 插入单条记录
deleteById(Serializable id) 根据 ID 删除记录
delete(Wrapper<T> wrapper) 根据条件删除记录
updateById(T entity) 根据 ID 更新记录
update(T entity, Wrapper<T> updateWrapper) 根据条件更新记录
selectById(Serializable id) 根据 ID 查询记录
selectOne(Wrapper<T> queryWrapper) 查询单条记录
selectList(Wrapper<T> queryWrapper) 查询列表
selectCount(Wrapper<T> queryWrapper) 查询数量

三、使用 Db 工具类解决循环依赖问题

1.重构 Service 代码

下面我们使用 Db 工具类重构之前的循环依赖场景:

// UserService.java
@Service
public class UserService {
    
    // 不再注入OrderService
    // @Autowired
    // private OrderService orderService;
    
    public User getById(Long id) {
        // 使用Db工具类查询用户信息
        User user = Db.selectById(User.class, id);
        
        // 使用Db工具类查询用户订单信息,避免依赖OrderService
        QueryWrapper<Order> orderWrapper = new QueryWrapper<>();
        orderWrapper.eq("user_id", id);
        List<Order> orders = Db.selectList(Order.class, orderWrapper);
        
        user.setOrders(orders);
        return user;
    }
}

// OrderService.java
@Service
public class OrderService {
    
    // 不再注入UserService
    // @Autowired
    // private UserService userService;
    
    public List<Order> getByUserId(Long userId) {
        // 使用Db工具类查询订单信息
        QueryWrapper<Order> orderWrapper = new QueryWrapper<>();
        orderWrapper.eq("user_id", userId);
        List<Order> orders = Db.selectList(Order.class, orderWrapper);
        
        // 使用Db工具类查询用户信息,避免依赖UserService
        for (Order order : orders) {
            User user = Db.selectById(User.class, order.getUserId());
            order.setUser(user);
        }
        return orders;
    }
}

2. 配置 Db 工具类

为了使 Db 工具类正常工作,需要在项目中进行简单配置:

// MyBatisPlusConfig.java
@Configuration
public class MyBatisPlusConfig {
    
    @Bean
    public Db db(ApplicationContext applicationContext) {
        return new Db(applicationContext);
    }
}

3.依赖注入配置

在 pom.xml 中添加 MyBatis-Plus 依赖(如果尚未添加):

<dependencies>
    <!-- MyBatis-Plus核心依赖 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    
    <!-- 其他依赖... -->
</dependencies>

四、Db 工具类的高级应用场景

1.批量操作优化

Db 工具类提供了高效的批量操作方法,适用于大数据量处理场景:

// 批量插入
List<User> userList = new ArrayList<>();
// 填充userList数据...
Db.batchInsert(userList, 100); // 批量插入,每次100条

// 批量更新
List<User> updateList = new ArrayList<>();
// 填充updateList数据...
Db.batchUpdate(updateList, 100); // 批量更新,每次100条

2. 复杂条件查询

结合 MyBatis-Plus 的 Wrapper API,可以实现复杂的数据库查询:

// 复杂条件查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("username", "admin")
       .between("create_time", startDate, endDate)
       .orderByDesc("score")
       .last("LIMIT 10");

List<User> adminUsers = Db.selectList(User.class, wrapper);

3.事务管理

虽然 Db 工具类是静态工具类,但仍然可以参与 Spring 的事务管理:

@Service
public class UserService {
    
    @Transactional
    public void batchProcess() {
        // 在事务中使用Db工具类
        Db.insert(new User("user1", "123456"));
        
        // 模拟异常回滚
        if (true) {
            throw new RuntimeException("模拟异常");
        }
        
        Db.insert(new User("user2", "123456"));
    }
}

五、使用 Db 工具类的注意事项

1.性能考虑

虽然 Db 工具类使用方便,但在高并发场景下需要注意:

  • 避免在循环中频繁调用 Db 工具类方法
  • 对于批量操作,尽量使用batchInsertbatchUpdate等批量方法
  • 合理设置批量操作的批次大小,避免内存溢出

2.事务边界

使用 Db 工具类时,事务管理与普通 Service 一致:

  • 可以通过@Transactional注解声明事务
  • 确保 Db 工具类的操作在同一个事务上下文中
  • 注意事务传播行为的设置

3.代码可读性

虽然 Db 工具类可以解决循环依赖问题,但也需要注意:

  • 不要过度使用 Db 工具类,避免 Service 层业务逻辑混乱
  • 对于简单的 CRUD 操作,可以使用 Db 工具类
  • 复杂的业务逻辑仍然应该封装在 Service 层

六、总结与最佳实践

1. 循环依赖解决方案对比

解决方案 优点 缺点
构造器注入 保证依赖的不可变性 无法解决循环依赖
Setter 注入 支持依赖的延迟注入 可能导致空指针异常
接口注入 松耦合设计 实现复杂
Db 工具类 彻底打破依赖关系 可能降低代码可读性

2.最佳实践建议

  1. 优先使用构造器注入,保证依赖的不可变性
  2. 当遇到循环依赖时,考虑使用 Db 工具类解决
  3. 对于复杂的业务逻辑,仍然应该保持 Service 层的封装
  4. 合理使用 MyBatis-Plus 的 Wrapper API,避免原生 SQL
  5. 对于批量操作,使用 Db 工具类的批量方法提高性能

通过 MyBatis-Plus 的 Db 工具类,我们可以优雅地解决 Service 层的循环依赖问题,同时保持代码的简洁和高效。在实际项目中,应根据具体场景选择合适的解决方案,平衡代码可读性和架构设计的合理性。

Logo

全面兼容主流 AI 模型,支持本地及云端双模式

更多推荐