Spring Boot整合MyBatis案例

wen java案例 1

Spring Boot整合MyBatis案例:从零搭建高效数据访问层(附完整代码)

目录导读

  1. 整合前的准备:环境要求与项目初始化
  2. 核心依赖配置:Maven坐标与yml配置详解
  3. 实体类与Mapper接口:领域模型与数据访问层设计
  4. XML映射文件:SQL语句与结果映射最佳实践
  5. Service与Controller:业务逻辑与接口暴露
  6. 分页与动态SQL:实战中高频使用的进阶技巧
  7. 事务管理:@Transactional的正确打开方式
  8. 常见问题排查:报错解决与性能优化建议
  9. QA问答:整合过程中的高频疑问解答

整合前的准备:环境要求与项目初始化

在开始整合案例之前,请确保你的开发环境满足以下条件:

Spring Boot整合MyBatis案例

  • JDK 8+
  • Maven 3.6+
  • MySQL 5.7+(或MariaDB)
  • IDEA/Eclipse(推荐使用IDEA,社区版即可)

项目初始化:使用Spring Initializr(https://start.spring.io)快速创建基础工程,选择Spring Web、MySQL Driver依赖,这里我们以Maven项目为例,最终目录结构如图1所示。

注意:案例中的域名统一替换为 https://example.com,实际开发中请替换为你的服务地址。


核心依赖配置:Maven坐标与yml配置详解

pom.xml必需添加以下依赖:

<!-- MyBatis Spring Boot Starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.1</version>
</dependency>
<!-- 可选:分页插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>

application.yml配置如下(关键位置已做注释):

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC
    username: root
    password: root
mybatis:
  mapper-locations: classpath:mapper/*.xml   # XML映射文件位置
  type-aliases-package: com.example.demo.entity  # 实体类包名
  configuration:
    map-underscore-to-camel-case: true        # 驼峰命名自动映射
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL日志
# 分页插件配置
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true

实体类与Mapper接口:领域模型与数据访问层设计

以用户表t_user为例,实体类User.java代码如下:

public class User {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    // getter/setter 省略
}

Mapper接口(核心方法示例):

public interface UserMapper {
    // 根据ID查询用户
    User selectById(@Param("id") Long id);
    // 查询所有用户
    List<User> selectAll();
    // 新增用户,返回受影响行数
    int insert(User user);
    // 更新用户信息
    int update(User user);
    // 删除用户
    int deleteById(@Param("id") Long id);
}

XML映射文件:SQL语句与结果映射最佳实践

resources/mapper/UserMapper.xml中编写SQL映射:

<mapper namespace="com.example.demo.mapper.UserMapper">
    <!-- 结果映射:数据库列名转驼峰 -->
    <resultMap id="UserResultMap" type="User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="email" column="email"/>
        <result property="age" column="age"/>
    </resultMap>
    <!-- 查询:按主键 -->
    <select id="selectById" resultMap="UserResultMap">
        SELECT * FROM t_user WHERE id = #{id}
    </select>
    <!-- 动态SQL示例:条件查询 -->
    <select id="selectByCondition" resultMap="UserResultMap">
        SELECT * FROM t_user
        <where>
            <if test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="age != null">
                AND age &gt;= #{age}
            </if>
        </where>
    </select>
    <!-- 新增 -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO t_user(username, email, age)
        VALUES(#{username}, #{email}, #{age})
    </insert>
</mapper>

最佳实践useGeneratedKeys="true" 可以自动回填自增主键,避免二次查询。


Service与Controller:业务逻辑与接口暴露

Service层(事务边界放在这里):

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    // 查询用户
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
    // 新增用户(示例中演示事务回滚)
    @Transactional(rollbackFor = Exception.class)
    public int createUser(User user) {
        int result = userMapper.insert(user);
        // 模拟异常,验证事务是否回滚
        if (user.getAge() != null && user.getAge() > 120) {
            throw new RuntimeException("年龄不合法,事务回滚");
        }
        return result;
    }
}

Controller层

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getUserById(id));
    }
    @PostMapping
    public ResponseEntity<Integer> addUser(@RequestBody User user) {
        return ResponseEntity.ok(userService.createUser(user));
    }
}

分页与动态SQL:实战中高频使用的进阶技巧

分页查询(使用PageHelper):

public PageInfo<User> getUserPage(int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.selectAll();
    return new PageInfo<>(users);
}

动态SQL除了<if><where>,还有<choose><foreach>等,遍历集合是极高频率场景:

<select id="selectByIds" resultMap="UserResultMap">
    SELECT * FROM t_user WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

事务管理:@Transactional的正确打开方式

Spring Boot中,只需在方法上添加@Transactional即可,重要注意点:

  • 默认只回滚RuntimeException,若需捕获受检异常,请设置rollbackFor = Exception.class
  • 事务应放在Service实现类方法上,不要放在Controller层
  • 同一类内部调用事务方法,事务会失效(代理机制),应通过注入自身或拆分类解决

常见问题排查:报错解决与性能优化建议

问题1:Mapper接口无法注入(No qualifying bean of type

解决:在启动类上添加@MapperScan("com.example.demo.mapper"),或者为每个Mapper接口加@Mapper注解。

问题2:数据库列名和实体属性不对应,查询结果为null

解决:开启map-underscore-to-camel-case: true,或使用@Results注解/XML中的resultMap手动映射。

问题3:SQL日志不打印

解决:检查log-impl配置是否被注释;确认mapper-locations路径是否正确(如classpath*:mapper/**/*.xml)。

性能优化建议

  • 避免SELECT *,只查必要字段
  • 大表查询务必使用分页,永远不要用LIMIT 100000,10,改用游标或延迟关联
  • 批量插入使用<foreach>拼接,或使用ExecutorType.BATCH

QA问答:整合过程中的高频疑问解答

Q1:MyBatis-Plus与MyBatis如何选择?

答:MyBatis-Plus是在MyBatis基础上的增强工具,内置CRUD方法、分页插件、乐观锁等,适合快速开发,但如果你需要高度定制SQL映射,原生MyBatis更灵活,本项目以原生MyBatis为例,展示清晰的数据访问层原理。

Q2:为什么使用@Param注解?

答:当Mapper方法有多个参数时,MyBatis需要绑定参数名称,使用@Param("id")显式指定,避免参数顺序错误,也方便在XML中用#{id}引用。

Q3:整合后,启动报错“Invalid bound statement (not found)”

答:说明项目未加载到XML文件,检查mapper-locations路径,且保证target/classes/mapper下确实编译出了XML文件,若使用Kotlin/多模块结构,请使用classpath*通配。

Q4:事务回滚不生效,为什么?

答:最常见的两个原因:一是Spring Boot 2.x默认不使用CGLIB代理,如果ServiceImpl方法被final修饰,事务失效;二是内部调用(this调用)时,代理实例并不会拦截方法,请确保在外部调用Service方法。

Q5:分页插件PageHelper失效?

答:确认pagehelper依赖正确,且没有同时引入多个版本,在Spring Boot项目中,如果有多数据源,需要单独为每个数据源配置分页拦截器。


写在最后:本文通过一个完整的用户管理功能,展示了Spring Boot集成MyBatis的标准流程,从依赖配置、实体映射、动态SQL、事务处理到分页方案,覆盖了开发中90%以上的场景,建议读者跟随代码自己动手敲一遍,遇到问题优先查看控制台SQL日志,如果本教程对你有帮助,欢迎收藏转发,你的支持是我持续输出的动力。

抱歉,评论功能暂时关闭!