Mybatis总结
Mybatis 是Java生态中主流的ORM框架。
基础概念
1、Mybatis、和Jdbc、Hibernate的区别:
定义:Mybatis是一款半自动化的ORM框架,基于XML文件或注解配置SQL语句,将Java对象与数据库表进行映射,简化JDBC代码编写(不需要手动处理connention、ResultSet等),同时保留SQL的灵活性。
对比JDBC和HIbernate:
维度 | JDBC | MyBatis | Hibernate |
---|---|---|---|
ORM 类型 | 无(手动映射) | 半自动(需手动写 SQL) | 全自动(无需手动写 SQL) |
SQL 控制度 | 完全手动编写,灵活但冗余 | 手动编写 SQL,灵活且简化代码 | 自动生成 SQL,灵活性低 |
学习成本 | 低(原生 API) | 中(需理解配置与映射规则) | 高(需掌握 HQL、缓存、级联等) |
适用场景 | 简单项目、需极致控制 SQL | 中小型项目、需灵活调优 SQL | 大型项目、SQL 无需频繁调整 |
代码冗余度 | 高(重复处理连接、结果集) | 低(框架封装通用操作) | 极低(几乎无 SQL 代码) |
2、Mybatis的主要组成,各自功能
Mybatis各个组件主要围绕:SQL执行流程开展。主要有:
- SqlSessionFactoryBuilder:构建器,基于XML或Java配置创建SqlSessionFactory。创建后即可销毁(生命周期最短)。
- SqlSessionFactory:工厂,创建SqlSession的单例对象(整个应用生命周期内唯一),负责初始化Mybatis核心配置(如数据源。映射文件)。
- SqlSession:会话,代表与数据库的一次交互(生命周期为一次请求/事务),提供增删改查的API(如:selectOne()、insert()),可直接执行SQL或调用Mapper接口。
- Mapper接口:映射器,无实现类的接口,Mybatis通过动态代理生成 其实现类。将接口方法与XML/注解中的SQL语句绑定(原理:接口+SQL映射)。
- Excutor:执行器,Mybatis核心执行组件(由SqlSession内部调用),负责解析SQL,管理缓存,执行JDBC操作。分为3类型:
- SimpleExecutor:默认,每次执行SQL都创建一个新的Statement;
- ReuseExcutor:复用Statement(根据SQL语句缓存);
- BatchExecutor:批量执行SQL(使用批量插入/更新);
- MapperStatement:映射语句,封装XML/注解中的SQL配置(如SQL语句、参数类型、结果类型),是Mybatis解析SQL的核心载体。
配置与映射规则
1、Mybatis的核心配置文件(mybatis-config.xml)包含的关键节点。
核心配置文件定义 MyBatis 全局参数,节点需按固定顺序排列(否则报错),关键节点及作用:
- properties:引入外部属性文件(如 db.properties),避免硬编码数据库连接信息;
- settings:配置 MyBatis 全局开关(如缓存开启、驼峰命名映射、日志实现);
- typeAliases:为 Java 类型定义别名(如 com.example.User 别名 User),简化映射文件中的类型配置;
- typeHandlers:自定义类型处理器(如将 Java 枚举与数据库 VARCHAR 类型映射);
- objectFactory:自定义对象工厂(默认使用 DefaultObjectFactory,用于创建结果集映射的 Java 对象);
- plugins:配置插件(如分页插件 PageHelper、逻辑删除插件),通过拦截器增强 MyBatis 功能;
- environments:配置数据库环境(支持多环境,如开发、测试、生产),包含transactionManager(事务管理器)和 dataSource(数据源);
- mappers:注册 SQL 映射文件或 Mapper 接口(核心,告诉 MyBatis 去哪里找 SQL)。
2、Mybati中如何实现【表字段名与Java对象属性名不一致问题】的映射。
解决方式:
- SQL别名(简单直接):在SQL中给字段起别名,与Java属性对应。
- resultMap映射(推荐,可复用):在映射文件中定义resultMap,指定子u但与属性映射关系
- 开启驼峰命名自动映射(全局配置)。
- 自定义TypeHandler(特殊场景):针对复杂场景(如JSON字段与Java对象,自定义Type Handler处理映射逻辑)。
3、Mybatis中#{}和${}区别,推荐使用#{}
两者都是 用于向SQL中注入参数的,但是底层实现和安全性不同。
维度 | #{} | ${} |
---|---|---|
实现原理 | 预编译SQL(使用?占位符),参数通过PreparedStatement注入字符串拼接(直接将参数替换到SQL中 | |
安全性 | 防止SQL注入风险(参数当作值,不解析SQL语法) | 存在SQL注入风险(参数可能拼接为SQL关键字) |
适用场景 | 普通参数注入(条件查询、新增数据的字段值) | 动态SQL片段(如表名、排序字段,需保证参数安全) |
注:除非需要动态拼接 表名/字段名(且参数需要严格验证),否则优先使用#{}避免SQL注入。
动态SQL和关联查询
1、Mybatis 提供的动态SQL标签:
- if 条件判断,满足条件则拼接
- where:自动处理条件中的AND/OR(若条件都不满足则不拼接where,若第一个条件带AND则自动删除)
- choose when otherwise:类似于if else if-else,只执行第一个满足条件的where,否则执行otherwise
- foreach:遍历集合(List、Array),常用于In条件或批量插入;
- set:用于更新语句。
- trim:自定义 拼接规则,可替代where和if
- sql和include:抽取重复SQL片段,通过include复用,减少冗余。
2、Mybatis 处理一对一和 一对多问题。
关联查询的核心:通过ResultMap中的 <association/ >(一对一)和<collection/ >(一对多)标签映射关系。
1)一对一映射:
核心标签:<association/ > ,映射「单个关联对象」,属性如下:
- property:Java 对象中关联属性名(如 userCard);
- javaType:关联属性的类型(如 com.example.UserCard);
- column:关联查询的外键字段(如 user_id,用于子查询);
- select:子查询的 ID(用于「分步查询」),或直接在 resultMap 中配置字段映射(用于「关联查询」)。
<resultMap id="UserWithCardMap" type="User"><id column="user_id" property="userId"/><result column="user_name" property="userName"/><!-- 一对一关联 UserCard --><association property="userCard" javaType="UserCard"><id column="card_id" property="cardId"/><result column="card_no" property="cardNo"/></association>
</resultMap><select id="getUserWithCard" resultMap="UserWithCardMap">SELECT u.user_id, u.user_name, c.card_id, c.card_noFROM user uLEFT JOIN user_card c ON u.user_id = c.user_idWHERE u.user_id = #{userId}
</select>
2)一对多
核心标签:,映射「关联对象集合」,属性如下:
- property:Java 对象中集合属性名(如 orders);
- ofType:集合中元素的类型(如 com.example.Order,注意区别于 javaType);
- column:外键字段,其他属性与 类似。
<resultMap id="UserWithOrdersMap" type="User"><id column="user_id" property="userId"/><result column="user_name" property="userName"/><!-- 一对多关联 Order 集合 --><collection property="orders" ofType="Order"><id column="order_id" property="orderId"/><result column="order_no" property="orderNo"/><result column="create_time" property="createTime"/></collection>
</resultMap><select id="getUserWithOrders" resultMap="UserWithOrdersMap">SELECT u.user_id, u.user_name, o.order_id, o.order_no, o.create_timeFROM user uLEFT JOIN `order` o ON u.user_id = o.user_idWHERE u.user_id = #{userId}
</select>
缓存机制
Mybatis提供两级缓存。
1、Mybatis提供两级缓存,各自的特点:
一级缓存(本地缓存)、二级缓存(全局缓存)。核心是减少数据库查询次数。
缓存级别 | 作用范围 | 生命周期 | 开启方式 | 核心特点 |
---|---|---|---|---|
一级缓存 | SqlSession内部(会话级) | 随着SQLSession关闭而销毁 | 默认开启,无需配置 | 基于Hash Map实现,同一SqlSession内重复查询同一SQL会命中缓存 |
二级缓存 | SqlSessionFactory全局(应用级) | 随着应用关闭而销毁 | 需要手动开启(映射文件附加<cache/ >) | 基于HashMap或三方缓存(Redis)实现, 不同SQL session可共享缓存 |
2、一级缓存失效场景:(一级默认开启,但可能失效)
- 同一Sqlsession中执行更新操作(insert/update/delete),Mybatis会清空当前sqlsession的一级缓存(避免缓存与数据库数据不一致);
- 手动调用SqlSession.clearCache()方法;主动清理缓存;
- 同一sqlsession中查询不同的SQL或参数;缓存的可以组成:SQL语句+参数+环境+映射ID。不同key导致未命中。
- SqlSession关闭或提交,SQLSession生命周期结束,缓存随即销毁、
3、开启二级缓存,以及二级缓存的注意事项
开启步骤:
1. 全局配置开启二级缓存(默认已开启,可省略)
在mybatis.xml文件中配置
<settings><setting name="cacheEnabled" value="true"/>
</settings>
- 在映射文件中声明二级缓存
在需要开启二级缓存的Mapper.xml文件中添加<cache/ >标签;
<!-- UserMapper.xml -->
<cache eviction="LRU" <!-- 缓存淘汰策略:LRU(最近最少使用)、FIFO 等 -->flushInterval="60000" <!-- 缓存自动刷新时间(毫秒) -->size="1024" <!-- 缓存最大条目数 -->readOnly="true"/> <!-- 是否只读(true:返回缓存对象的引用,性能高;false:返回拷贝,安全) -->
- 实体类实现序列化接口
二级缓存可能需要将对象写入磁盘(如使用Ehcache时),需保证实体类implement Serializable。
注意事项:
- 二级缓存按Mapper划分:不同Mapper的缓存应相互独立,若需要跨Mapper共享缓存,需要配置cache-ref(引用其他Mapper的缓存);
- 避免缓存频繁更新的数据,
- 复杂关联查询(如一对多),需要考虑是否缓存。优先使用一级缓存或使用三方缓存
性能优化
1、Mybatis常见的性能优化
- SQL优化
- 避免Select *,只查询需要的字段
- 合理利用索引
- 减少关联查询
- 缓存优化
- 开启二级缓存(针对高频,低频更新的数据)
- 集成三方缓存,替代默认缓存。
- 执行器优化:
- 批量操作时,使用BatchExecutor,需要手动设置SqlSessiond 执行器类型
- 分页优化:
- 使用分页插件(如Page Helper),避免手动在这里插入代码片拼接limit语句。
- 避免全表查询后分页。会导致内存溢出
- 参数优化
- 避免大字段查询
- 延迟加载(懒加载)
- 开启全局懒加载,避免关联查询一次性查出所有数据(按需加载)。
<settings><setting name="lazyLoadingEnabled" value="true"/> <!-- 开启懒加载 --><setting name="aggressiveLazyLoading" value="false"/> <!-- 关闭积极加载(按需加载单个属性) -->
</settings>
2、Mybatis的延时加载,
定义:延迟加载(懒加载)是指 **关联对象在需要时才会加载,**而不是在主对象加载时,一次性加载。如:加载用户订单时,先加载用户信息,只有调用user.getOrder()时,才查询订单数据。
原理:通过动态代理实现延迟加载.
- 开启懒加载,Mybatis会为实体类(如:User)生成动态代理对象;
- 初始查询只加载主对象User的基本属性,关联属性如order会被设置为【代理对象]
- 当调用关联查询属性的getter方法时,代理对象会触发Mybatis的懒加载机制逻辑。执行关联SQL并填充数据。
- 若未调用关联属性,则不会执行关联查询,减少数据库压力。
MyBatis的事务:Mybatis本身不提供事务管理,而是依赖底层数据源和Spring管理。
- 原生Mybatis事务管理:通过SqlSession控制事务,默认关闭自动提交,需要手动commit()或rollback();
SqlSession sqlSession = sqlSessionFactory.openSession(false); // false:关闭自动提交
try {UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.insertUser(user1);mapper.insertUser(user2);sqlSession.commit(); // 手动提交
} catch (Exception e) {sqlSession.rollback(); // 异常回滚
} finally {sqlSession.close();
}
- Spring整合Mybatis事务管理
通过Spring的@Transactional注解或者xml配置文件声明事务,无需手动控制SqlSession ;
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactional(rollbackFor = Exception.class) // 声明事务,异常回滚public void batchInsert(List<User> userList) {for (User user : userList) {userMapper.insertUser(user);}}
}