💡 开篇故事:一次“找不到Bean”引发的深夜加班
程序员小赵正赶工一个电商项目,信心满满地写好了用户注册逻辑:
public class UserService {
private UserMapper userMapper; // 注入MyBatis的Mapper接口
public void register(User user) {
userMapper.insert(user); // 调用数据库插入操作
}
}
然而,一运行就报错:NullPointerException: userMapper is null!小赵抓狂:“明明UserMapper定义了@Mapper注解啊?!” 直到他给UserService加上 @Service,并在启动类添加 @MapperScan,代码终于跑通了…… 原来,这两个注解是“共生关系”!
🔍 第一章:@Service——业务逻辑的“指挥官”
1. 什么是@Service?
- 身份标识:Spring的组件扫描注解之一,专用于标记**业务逻辑层(Service层)**的类。
- 核心职责:
- 被Spring容器管理,支持依赖注入(如注入Mapper、Repository等)。
- 通过事务管理(@Transactional)保障业务操作的原子性。
2. 为什么不用@Component?
- 语义化差异:
- @Component 是通用注解,而 @Service 明确表示“这是业务服务”!
- 代码可读性更强,分层架构更清晰。
3. 实战代码
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.selectById(id);
}
}
关键点:
- 类上标注 @Service → Spring自动创建单例Bean。
- 通过@Autowired 注入其他Bean(如Mapper)。
🔍 第二章:@Mapper——MyBatis的“SQL翻译官”
1. 什么是@Mapper?
- MyBatis的核心注解:标记一个接口为**数据访问层(Mapper层)**组件。
- 核心职责:
- MyBatis动态生成接口实现类,无需手动写SQL实现!
- 与XML映射文件或注解SQL配合,执行数据库操作。
2. 为什么不用@Repository?
- 框架差异:
- @Repository 是Spring对数据访问层的通用注解。
- @Mapper 是MyBatis的特有注解,专为MyBatis接口设计。
- 关键区别:
- @Mapper 接口由MyBatis动态代理实现,而非Spring容器。
- 需配合 @MapperScan 指定扫描路径。
3. 实战代码
接口定义:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
int insert(User user);
}
XML映射文件(可选):
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
🎬 第三章:@Service + @Mapper 协同作战
1. 依赖注入流程
- Spring容器扫描 @Service → 创建 UserService Bean。
- MyBatis扫描 @Mapper → 生成 UserMapper 代理类。
- Spring将 UserMapper 代理对象注入到 UserService 中。
2. 完整调用链示例
@RestController
public class UserController {
@Autowired
private UserService userService; // 注入Service
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id); // Service调用Mapper
}
}
❗ 第四章:避坑指南 & 高频问题
1. Q:为什么Mapper接口无法注入?
- 检查项:
- 是否添加了 @Mapper 注解?
- 启动类是否配置了 @MapperScan("com.example.mapper")?
- Mapper接口与XML文件是否在同一包下且命名一致?
2. Q:Service层需要加事务吗?
- 推荐做法:在Service方法上加 @Transactional!
@Service public class OrderService { @Transactional // 事务管理 public void createOrder(Order order) { orderMapper.insert(order); inventoryMapper.deductStock(order.getProductId()); } }
3. Q:@MapperScan有什么用?
- 作用:指定MyBatis扫描Mapper接口的包路径。
- 示例:
@SpringBootApplication @MapperScan("com.example.mapper") // 指定Mapper接口所在包 public class Application { ... }
4. Q:Service和Mapper能否跨模块调用?
- 可以!确保:
- Mapper接口在 @MapperScan 指定的扫描范围内。
- Service类所在包被Spring组件扫描覆盖(默认扫描启动类同级目录)。
🚀 第五章:进阶技巧——更高效地使用Service与Mapper
1. 使用MyBatis-Plus简化Mapper开发
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 继承BaseMapper即拥有CRUD方法,无需写SQL!
}
2. 服务层接口与实现分离
public interface UserService {
User getUserById(Long id);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Long id) { ... }
}
优势:面向接口编程,方便扩展和Mock测试。
3. 批量操作优化
@Mapper
public interface BatchMapper {
void batchInsert(@Param("list") List<User> users);
}
<!-- XML中批量插入 -->
<insert id="batchInsert">
INSERT INTO user (name, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>
🌟 总结:分工明确,协作无间
- @Service:业务逻辑的“大脑”,负责调度和事务管理。
- @Mapper:数据操作的“手”,专注SQL执行与结果映射。
- 最佳实践:
- Service层处理业务规则,Mapper层只做数据访问。
- 结合事务注解保障数据一致性。
📢 互动话题:你在Service和Mapper的配合使用中踩过哪些坑?或者有什么高效的使用技巧?评论区一起聊聊吧!👇