1. MyBatis入门:从零开始掌握持久层框架
作为一名Java开发者,我深知与数据库打交道是日常开发中最频繁的工作之一。还记得刚入行时,我还在用原始的JDBC操作数据库,每次都要写一大堆重复的模板代码:加载驱动、获取连接、创建Statement、处理结果集...直到遇见了MyBatis,我的开发效率才有了质的飞跃。
MyBatis是一款优秀的持久层框架,它完美解决了JDBC的繁琐问题。与Hibernate这样的全自动ORM框架不同,MyBatis属于半自动化ORM,这意味着它既保留了SQL的灵活性,又大大简化了数据库操作。在实际项目中,我发现这种"半自动"的特性特别适合需要精细控制SQL的场景。
1.1 MyBatis核心特性解析
经过多个项目的实战,我总结了MyBatis最值得关注的几个特点:
-
SQL与代码解耦:通过XML或注解配置SQL,彻底告别了JDBC中SQL与Java代码混杂的情况。我特别喜欢这种分离的设计,让SQL更易于维护。
-
灵活的映射机制:支持简单映射和复杂对象关联映射。记得有一次需要处理多层嵌套的对象关系,MyBatis的
<resultMap>标签轻松搞定了这个需求。 -
动态SQL支持:
<if>,<choose>,<foreach>等标签让动态SQL编写变得异常简单。这在处理多条件查询时特别有用。 -
插件机制:可以通过插件拦截MyBatis的核心方法,实现性能监控、分页等通用功能。我们团队就基于此开发了统一的分页插件。
-
与Spring无缝集成:通过
mybatis-spring项目可以轻松整合到Spring环境中。这也是我们大多数项目选择的技术栈。
提示:对于刚接触MyBatis的开发者,建议先从XML配置方式入手,等熟悉核心概念后再尝试注解方式。XML方式虽然稍显繁琐,但结构更清晰,更适合复杂SQL场景。
2. MyBatis环境搭建与核心配置
2.1 项目依赖配置
在开始第一个MyBatis程序前,我们需要准备以下依赖(以Maven项目为例):
xml复制<!-- MyBatis核心依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.14</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- 日志门面SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Logback日志实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
</dependency>
在实际项目中,我通常会锁定所有依赖的版本号,或者使用<dependencyManagement>统一管理版本,避免依赖冲突。
2.2 核心配置文件详解
MyBatis的核心配置文件mybatis-config.xml通常放在resources目录下。下面是一个完整的配置示例:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 必须放在environments之前 -->
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 日志实现 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 映射文件配置 -->
<mappers>
<mapper resource="mapper/CarMapper.xml"/>
</mappers>
</configuration>
关键配置说明:
-
environments:可以配置多个环境(开发、测试、生产),通过default指定默认环境。
-
transactionManager:事务管理方式,JDBC表示使用JDBC的事务管理。
-
dataSource:数据源配置,POOLED表示使用连接池。
-
mappers:注册SQL映射文件的位置。
注意事项:在实际项目中,我建议将数据库配置提取到外部properties文件中,使用
<properties>标签引入,这样不同环境可以轻松切换配置。
2.3 日志配置优化
良好的日志输出对调试MyBatis非常重要。我习惯使用Logback作为日志实现,配置如下:
xml复制<!-- logback.xml -->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- MyBatis日志级别 -->
<logger name="org.mybatis" level="DEBUG"/>
<!-- JDBC日志 -->
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
这样配置后,控制台会输出执行的SQL语句和参数,极大方便了调试。
3. MyBatis核心对象与执行流程
3.1 核心对象关系图
MyBatis的核心对象有三个,它们的关系如下:
code复制SqlSessionFactoryBuilder → SqlSessionFactory → SqlSession
-
SqlSessionFactoryBuilder:用于创建SqlSessionFactory,构建完成后即可丢弃。
-
SqlSessionFactory:一旦创建,应在应用运行期间一直存在。通常一个数据库对应一个SqlSessionFactory实例。
-
SqlSession:每个线程都应该有它自己的SqlSession实例,因为它不是线程安全的。使用后需要及时关闭。
3.2 获取SqlSession的标准流程
下面是我在实际项目中总结的标准获取SqlSession的代码模板:
java复制public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException("初始化MyBatis失败", e);
}
}
public static SqlSession getSqlSession() {
// 设置为false表示关闭自动提交,开启事务
return sqlSessionFactory.openSession(false);
}
}
使用方式:
java复制try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
// 执行数据库操作
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback();
throw e;
}
经验分享:我强烈推荐使用try-with-resources语法管理SqlSession,这样可以确保Session总是被正确关闭,避免资源泄漏。
3.3 MyBatis事务管理机制
MyBatis提供了两种事务管理方式:
-
JDBC:使用JDBC的事务管理机制,即通过Connection的commit/rollback方法。
-
MANAGED:将事务管理交给容器(如Spring)来处理。
在大多数独立应用中,我们使用JDBC事务管理:
xml复制<transactionManager type="JDBC"/>
关键点:
- 默认情况下,
openSession()方法不会自动提交事务 - 可以通过
openSession(true)开启自动提交,但不建议这样做 - 必须显式调用commit()提交事务,或者在发生异常时调用rollback()
4. MyBatis CRUD操作详解
4.1 映射文件基础结构
SQL映射文件通常以Mapper.xml结尾,基本结构如下:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.CarMapper">
<!-- CRUD操作定义 -->
</mapper>
namespace属性非常重要,它用于区分不同Mapper中的相同id操作。
4.2 插入操作实战
XML配置:
xml复制<insert id="insertCar" parameterType="com.example.pojo.Car"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_car
(car_num, brand, guide_price, produce_time, car_type)
VALUES
(#{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
</insert>
Java代码:
java复制try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
Car car = new Car();
car.setCarNum("京A12345");
car.setBrand("奔驰");
car.setGuidePrice(new BigDecimal("500000.00"));
car.setProduceTime(LocalDate.of(2023, 1, 1));
car.setCarType("燃油车");
int affectedRows = sqlSession.insert("com.example.mapper.CarMapper.insertCar", car);
System.out.println("插入成功,主键ID:" + car.getId());
sqlSession.commit();
}
关键点说明:
useGeneratedKeys="true"表示使用数据库自增主键keyProperty="id"指定将生成的主键值赋给Car对象的id属性#{}是MyBatis的参数占位符,会自动防止SQL注入
4.3 更新操作实战
XML配置:
xml复制<update id="updateCar" parameterType="com.example.pojo.Car">
UPDATE t_car
SET car_num = #{carNum},
brand = #{brand},
guide_price = #{guidePrice},
produce_time = #{produceTime},
car_type = #{carType}
WHERE id = #{id}
</update>
Java代码:
java复制try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
Car car = sqlSession.selectOne("com.example.mapper.CarMapper.selectCarById", 1);
car.setGuidePrice(new BigDecimal("480000.00"));
int affectedRows = sqlSession.update("com.example.mapper.CarMapper.updateCar", car);
sqlSession.commit();
}
4.4 删除操作实战
XML配置:
xml复制<delete id="deleteCarById" parameterType="long">
DELETE FROM t_car WHERE id = #{id}
</delete>
Java代码:
java复制try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
int affectedRows = sqlSession.delete("com.example.mapper.CarMapper.deleteCarById", 1L);
sqlSession.commit();
}
4.5 查询操作实战
4.5.1 单条记录查询
XML配置:
xml复制<select id="selectCarById" parameterType="long" resultType="com.example.pojo.Car">
SELECT
id,
car_num AS carNum,
brand,
guide_price AS guidePrice,
produce_time AS produceTime,
car_type AS carType
FROM t_car
WHERE id = #{id}
</select>
Java代码:
java复制try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
Car car = sqlSession.selectOne("com.example.mapper.CarMapper.selectCarById", 1L);
System.out.println(car);
}
4.5.2 列表查询
XML配置:
xml复制<select id="selectAllCars" resultType="com.example.pojo.Car">
SELECT
id,
car_num AS carNum,
brand,
guide_price AS guidePrice,
produce_time AS produceTime,
car_type AS carType
FROM t_car
</select>
Java代码:
java复制try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
List<Car> cars = sqlSession.selectList("com.example.mapper.CarMapper.selectAllCars");
cars.forEach(System.out::println);
}
4.5.3 结果映射技巧
当数据库列名与Java属性名不一致时,我们有几种解决方案:
- 使用AS别名(如上例所示)
- 开启驼峰命名自动映射:
xml复制<setting name="mapUnderscoreToCamelCase" value="true"/> - 使用resultMap:
xml复制<resultMap id="carResultMap" type="com.example.pojo.Car">
<id property="id" column="id"/>
<result property="carNum" column="car_num"/>
<result property="brand" column="brand"/>
<result property="guidePrice" column="guide_price"/>
<result property="produceTime" column="produce_time"/>
<result property="carType" column="car_type"/>
</resultMap>
<select id="selectCarById" parameterType="long" resultMap="carResultMap">
SELECT * FROM t_car WHERE id = #{id}
</select>
经验分享:对于简单映射,我推荐使用AS别名或开启驼峰映射;对于复杂映射(如关联查询),则必须使用resultMap。
5. MyBatis高级特性与最佳实践
5.1 动态SQL实战
MyBatis提供了强大的动态SQL功能,可以避免拼接SQL字符串的麻烦。
5.1.1 if条件
xml复制<select id="selectCarsByCondition" parameterType="map" resultType="com.example.pojo.Car">
SELECT * FROM t_car
WHERE 1=1
<if test="brand != null">
AND brand = #{brand}
</if>
<if test="minPrice != null">
AND guide_price >= #{minPrice}
</if>
<if test="maxPrice != null">
AND guide_price <= #{maxPrice}
</if>
</select>
5.1.2 choose/when/otherwise
xml复制<select id="selectCarsByType" parameterType="string" resultType="com.example.pojo.Car">
SELECT * FROM t_car
<choose>
<when test="type == 'luxury'">
WHERE guide_price > 300000
</when>
<when test="type == 'economic'">
WHERE guide_price < 100000
</when>
<otherwise>
WHERE guide_price BETWEEN 100000 AND 300000
</otherwise>
</choose>
</select>
5.1.3 foreach遍历
xml复制<select id="selectCarsByIds" resultType="com.example.pojo.Car">
SELECT * FROM t_car
WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
5.2 参数传递技巧
MyBatis支持多种参数传递方式:
-
单个基本类型参数:
java复制Car car = sqlSession.selectOne("selectCarById", 1L);XML中可以直接使用任意名称引用:
xml复制
WHERE id = #{id} -
Map参数:
java复制Map<String, Object> params = new HashMap<>(); params.put("minPrice", new BigDecimal("200000")); params.put("maxPrice", new BigDecimal("300000")); List<Car> cars = sqlSession.selectList("selectCarsByPriceRange", params);XML中通过key引用:
xml复制
WHERE guide_price BETWEEN #{minPrice} AND #{maxPrice} -
POJO对象参数:
java复制Car condition = new Car(); condition.setBrand("奔驰"); List<Car> cars = sqlSession.selectList("selectCarsByCondition", condition);XML中通过属性名引用:
xml复制
WHERE brand = #{brand} -
多个参数:使用
@Param注解java复制List<Car> selectCarsByPriceAndBrand( @Param("minPrice") BigDecimal minPrice, @Param("brand") String brand);XML中通过注解值引用:
xml复制
WHERE guide_price >= #{minPrice} AND brand = #{brand}
5.3 最佳实践总结
经过多个项目的实践,我总结了以下MyBatis最佳实践:
-
SQL映射文件组织:
- 按业务模块划分Mapper接口和XML文件
- 保持XML文件名与Mapper接口名一致
- 使用包扫描方式注册Mapper,避免逐个配置
-
事务管理:
- 保持事务粒度尽可能小
- 及时提交或回滚事务
- 考虑使用Spring管理事务
-
性能优化:
- 合理使用一级缓存(SqlSession级别)
- 谨慎使用二级缓存(需要处理并发问题)
- 批量操作使用BatchExecutor
-
代码规范:
- 使用try-with-resources管理SqlSession
- 为每个Mapper方法添加注释
- 保持XML中的SQL格式整洁
-
异常处理:
- 捕获MyBatis异常并转换为业务异常
- 记录详细的错误日志
- 实现全局异常处理器
避坑指南:在实际项目中,我发现最容易出问题的地方是事务管理和缓存使用。特别是在Web应用中,如果不注意SqlSession的生命周期管理,很容易导致连接泄漏或数据不一致的问题。建议在正式项目中使用Spring集成MyBatis,让Spring来管理这些资源。