传统 JDBC 开发虽然灵活,但存在诸多痛点,导致开发效率低下且易出错:
try-catch-finally
块、资源释放等代码。例如,一个简单的查询需要至少 20 行代码处理异常和资源关闭。ResultSet
手动提取数据并封装为对象,字段名与属性名的映射需逐一手动处理,复杂对象(如嵌套对象、集合)处理成本极高。commit()
和 rollback()
,多操作事务一致性难以保障。MyBatis 的解决方案:
MyBatis 通过 SQL 与代码解耦、自动化映射、动态 SQL 和 声明式事务管理,将 JDBC 的灵活性保留,同时将开发效率提升数倍。其核心思想是:用配置代替硬编码,用约定代替重复劳动。
MyBatis 架构分为三层,各层职责明确,通过接口和配置串联:
graph TD
A[接口层] -->|调用| B[核心处理层]
B -->|依赖| C[基础支持层]
C -->|提供| D[数据源、事务、连接池等]
接口层:
提供 SqlSession
和 Mapper
接口,开发者通过这两个入口操作数据库。
SqlSession
:封装了 CRUD 方法(如 selectOne
, insert
),类似 JDBC 的 Connection
,但功能更丰富。Mapper
接口:通过动态代理生成实现类,将接口方法与 XML/注解中的 SQL 绑定。核心处理层:
包含 MyBatis 的核心处理逻辑:
#{}
和 ${}
)。Executor
执行 SQL,支持缓存、批处理等策略。ResultSet
转换为 Java 对象,支持简单对象、嵌套对象、集合等。基础支持层:
提供基础设施支持,包括:
TypeHandler
),处理 Java 类型与数据库类型的转换。Interceptor
),支持自定义拦截器扩展。SqlSessionFactory
是 MyBatis 的起点,其初始化流程如下:
mybatis-config.xml
):
Configuration
类中,该类是 MyBatis 的“大脑”。SqlSessionFactoryBuilder
生成 DefaultSqlSessionFactory
实例。关键代码示例:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
MyBatis 的核心魔法之一是 Mapper 接口的动态代理,其实现原理如下:
MapperProxyFactory
创建 Mapper 接口的代理对象。SqlSession
执行 SQL,并将结果转换为方法声明的返回类型。示例流程:
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
userMapper
是代理对象,selectUserById
方法被拦截,找到对应的
标签执行 SQL。JDBC 痛点:
MyBatis 方案:
mybatis-config.xml
统一管理数据源、事务、插件等。POOLED
(默认)、UNPOOLED
和 JNDI
数据源,也可集成第三方连接池(如 HikariCP)。配置示例:
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="poolMaximumActiveConnections" value="20"/>
dataSource>
environment>
environments>
优势:
配置。JDBC 痛点:
MyBatis 方案:
@Select
、@Insert
等注解写在接口上。XML 示例:
<mapper namespace="com.example.UserMapper">
<select id="selectUserById" resultType="User">
SELECT id, name, email FROM users WHERE id = #{id}
select>
mapper>
注解示例:
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUserById(int id);
}
JDBC 痛点:
MyBatis 方案:
,
,
,
等标签支持灵活条件拼接。复杂示例:
<select id="findUsers" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
if>
<if test="emails != null and emails.size() > 0">
AND email IN
<foreach item="email" collection="emails" open="(" separator="," close=")">
#{email}
foreach>
if>
where>
ORDER BY id
<if test="limit != null">
LIMIT #{limit}
if>
select>
优势:
AND
或缺失的括号)。JDBC 痛点:
ResultSet
读取字段并填充到对象属性。MyBatis 方案:
mapUnderscoreToCamelCase
后,数据库字段 user_name
自动映射到属性 userName
。resultType
简化配置:指定返回类型的全限定类名或别名,MyBatis 自动创建对象并填充字段。示例:
<select id="selectUserById" resultType="com.example.User">
SELECT id, user_name, email FROM users WHERE id = #{id}
select>
JDBC 痛点:
User
包含 List
)需手动编写多重循环,代码极其繁琐。MyBatis 方案:
resultMap
自定义映射:通过
定义复杂映射关系,支持一对一、一对多关联。一对一示例:
<resultMap id="userWithRoleMap" type="User">
<id property="id" column="id"/>
<result property="name" column="user_name"/>
<association property="role" javaType="Role">
<id property="roleId" column="role_id"/>
<result property="roleName" column="role_name"/>
association>
resultMap>
<select id="selectUserWithRole" resultMap="userWithRoleMap">
SELECT u.*, r.role_id, r.role_name
FROM users u LEFT JOIN roles r ON u.role_id = r.role_id
WHERE u.id = #{id}
select>
一对多示例:
<resultMap id="userWithOrdersMap" type="User">
<id property="id" column="id"/>
<collection property="orders" ofType="Order">
<id property="orderId" column="order_id"/>
<result property="amount" column="amount"/>
collection>
resultMap>
<select id="selectUserWithOrders" resultMap="userWithOrdersMap">
SELECT u.*, o.order_id, o.amount
FROM users u LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
select>
优势:
#{}
与 ${}
的区别:
#{}
:预编译占位符,参数以安全方式替换,防止 SQL 注入。${}
:字符串替换,直接拼接到 SQL 中,适用于动态表名、排序字段等场景。安全示例:
<select id="selectUserByName" resultType="User">
SELECT * FROM users WHERE name = #{name}
select>
name
会被替换为预编译参数(如 ?
),数据库驱动处理类型转换和安全过滤。动态表名示例:
<select id="selectFromTable" resultType="map">
SELECT * FROM ${tableName}
select>
${tableName}
直接替换为字符串,需确保参数值安全(如不允许用户输入)。单个参数:
#{id}
。多个参数:
使用 @Param
注解指定参数名:
User selectUser(@Param("id") int id, @Param("name") String name);
在 XML 中通过 #{id}
和 #{name}
引用。
对象参数:
传递对象,属性直接通过 #{propertyName}
访问:
<insert id="insertUser" parameterType="User">
INSERT INTO users (name, email) VALUES (#{name}, #{email})
insert>
JDBC 痛点:
autoCommit
、在 try-catch
块中处理 commit()
和 rollback()
。MyBatis 方案:
SqlSession
手动控制事务。@Transactional
注解自动管理事务。编程式事务示例:
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insertUser(user1);
mapper.insertUser(user2);
session.commit(); // 手动提交事务
} catch (Exception e) {
session.rollback(); // 回滚事务
}
Spring 集成示例:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public void saveUsers(User user1, User user2) {
userMapper.insertUser(user1);
userMapper.insertUser(user2);
}
}
MyBatis 以配置化与动态代理为核心,通过解耦 SQL 与代码、自动化结果映射及灵活的动态 SQL 标签,将 JDBC 的繁琐操作转化为简洁的接口方法。其分层架构(接口层、核心处理层、基础支持层)结合 XML/注解配置,既保留了原生 SQL 的灵活性,又通过批处理、分页优化与二级缓存机制提升了性能,同时依托插件体系与 Spring 集成实现事务管理和扩展定制,最终在简化开发、保障安全性与维护性之间取得了优雅平衡,成为 Java 持久层的高效解决方案。