在大型 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

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务,一站式满足开发者需求,帮助企业提升开发效率并创新开发模式。

更多推荐