一、Mybatis基础知识
1. Mybatis概要
1.1 Mybatis理解
如果没有MyBatis的支持,大家是怎么实现通过程序控制数据库的?首先我们需要为程序引入MySQL连接依赖mysql-connector.jar
,加载数据库JDBC驱动,接着创建数据库连接对象Connection
、SQL语句执行器Statement
,再把SQL语句发送到MySQL执行,最后关闭SQL语句执行器和数据库连接对象。
整个过程是比较繁琐的,这是通过JDBC操作MySQL必走的过程。可实际开发可给不了你那么多时间,如果大家非要用JDBC去写大量的冗余代码也可以,能抗住催你开发进度的压力就行。
这是JDBC操作的过程。
public class JDBCController {
private static final String db_url = "jdbc:mysql://localhost:3306/db_user";
private static final String user = "root";
private static final String password = "root";
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
String sql = "select * from user order by id desc";
try {
connection = DriverManager.getConnection(db_url, user, password);
statement = connection.createStatement();
int result = statement.executeUpdate(sql);
System.out.println(result);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
}
MyBatis能帮助我们什么?早在2002年,MyBatis的前身iBatis诞生,并于2010年改名为MyBatis。该框架引入了SQL映射作为持久层开发的一种方法,也就是说我们不需要把SQL耦合在代码里,只需要把SQL语句单独写在XML配置文件中。
以下是MyBatis编写SQL的写法。SQL的编写已经和程序运行分离开,消除了大量JDBC冗余代码,同时MyBatis还能和Spring框架集成。整个SQL编写的流程变得更加灵活也更加规范化。
@Mapper
public interface UserMapper extends BatchMapper<UserDO> {
List<UserDO> selectAllUser();
}
<?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="org.JavaGetOffer.UserDO">
<select id="selectAllUser" resultType="org.JavaGetOffer.UserDO">
select * from user order by id desc
</select>
</mapper>
1.2 SqlSession是什么
从我们偷偷访问某个小网站开始,到我们不耐烦地关闭浏览器或者退出登录时,我们作为用户和网站的一次会话就结束了。MyBaits框架要访问数据库同样要与数据库建立通信桥梁,而SqlSession对象表示的就是MyBaits框架与数据库建立的会话。
我们可以利用SqlSession来操作数据库,如下代码。
@Test
public void testMybatis() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<UserDO> userList = userMapper.listAllUser();
System.out.println(JSON.toJSONString(userList));
}
2. Mybatis缓存
2.1 Mybatis缓存分类
软件系统合理使用缓存有一个好处。有了缓存,在原始数据没有更新的情况下,我们不需要重新再去获取一遍数据,这也减少了数据库IO,达到提升数据库性能的目的。
MyBatis同样提供了两个级别的缓存,一级缓存是基于上文提到的SqlSession实现,二级缓存是基于Mapper实现。
一级缓存作用在同一个SqlSession对象中,当SqlSession对象失效则一级缓存也跟着失效。我们梳理下一级缓存的生命周期。首先第一次查询时会把查询结果写入SqlSession缓存,如果第二次查询时原始数据没有改变则会读取缓存,但如果是修改、删除、添加语句的执行,那SqlSession缓存会被全部清空掉,这也是为了防止脏读的出现。
一级缓存缓存底层使用的是一个简单的Map数据结构来存储缓存,其中key为SQL + 参数
、val为查询结果集
。一级缓存的生命周期如下。
二级缓存的作用域是同一个命名空间namespace的Mapper对象,也就是说同一个Mapper下的多个SqlSession是可以共用二级缓存的。二级缓存的缓存写入、清空流程和一级缓存相似,但二级缓存的生命周期是和应用程序的生命周期一致的。为什么?因为Mybatis框架与Spring IOC集成的Mapper对象是单例对象。
另外大家还需要注意下,Mybatis的一级缓存是默认开启的且不能关闭,而二级缓存则需要我们手动开启,我们需要在配置文件中配置cacheEnabled
参数。
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2.2 Mybatis缓存局限性
缓存是好,就是问题有点多,目前大厂大都禁止了Mybatis缓存的使用。
南哥总结了下,主要有以下原因。
(1)适用场景少
Mybatis二级缓存更适用于读多写少的业务场景,但是对于细粒度的缓存支持并不友好。举个用烂了的商城例子,每个商品信息的更新是非常频繁的的,而让用户每次都看到的是最新的商品信息又非常重要。
在同一个namespace的Mapper中一般会包含多个商品信息的二级缓存,只要有某一个商品信息更新了,则所有商品缓存都会全部失效。那其实在这个业务场景中,二级缓存的存在已经没有多大必要了,还反而增加了系统复杂性。
(2)数据不一致性问题
如果多个不同namespace的Mapper都共同操作同一个数据库表的情况下,第一个Mapper更新了数据库表会清空它本身的二级缓存,但其他namespace的Mapper是没有感知的,仍然缓存的是旧数据,数据不一致的问题就出现了。
(3)不适用于分布式系统
现在还用单机部署的业务已经不多了,大家都紧跟潮流搭了个分布式、高可用的系统。在分布式系统中,如果每个节点都使用自己的本地缓存,假如现在节点A更新了缓存,但节点B、节点C是不会进行同步更新的,同样产生了数据不一致的问题。
3. Mybatis分页插件
Mybatis分页的原理其实很简单,没有想象的那么复杂。我们只需要拦截SQL查询语句,再把SQL语句作为子查询,外面包裹一层SELECT * FROM
后再加上LIMIT
的分页约束语句。
如下SQL示例,确实挺简单的。
SELECT * FROM user
SELECT u.* FROM (SELECT * FROM user) u LIMIT M, N
二、Mybatis常见面试题
1. 什么是 MyBatis?它与 Hibernate 有什么区别?
回答: MyBatis 是一个持久层框架,它通过 XML 或注解的方式将 SQL 语句与 Java 对象进行映射。与 Hibernate 不同,MyBatis 不完全采用 ORM(对象关系映射)思想,而是更加灵活地直接编写 SQL。MyBatis 适合处理复杂 SQL 场景,而 Hibernate 更适合简单的 CRUD 操作和全自动的对象映射。
2. MyBatis 的核心组件有哪些?
回答: MyBatis 的核心组件包括 SqlSessionFactory
、SqlSession
、Mapper
接口和 Executor
。SqlSessionFactory
用于创建 SqlSession
对象,SqlSession
是 MyBatis 执行 SQL 操作的接口,Mapper
接口用于定义 SQL 映射关系,Executor
负责执行 SQL 语句并返回结果。
3. MyBatis 是如何实现 SQL 映射的?
回答: MyBatis 通过 XML 文件或注解将 SQL 语句与 Java 对象的属性进行映射。XML 映射文件中,<select>
、<insert>
、<update>
、<delete>
等标签用于定义 SQL 语句,并通过 resultType
或 resultMap
将查询结果映射到 Java 对象中。注解方式则通过 @Select
、@Insert
等注解直接在接口方法上定义 SQL。
4. MyBatis 的 #
和 $
符号有什么区别?
回答: #
符号用于安全的参数替换,它会将参数值作为预编译语句的一部分,防止 SQL 注入攻击;而 $
符号会直接将参数值拼接到 SQL 语句中,不进行任何处理,因此容易引发 SQL 注入风险。例如:
SELECT * FROM user WHERE id = #{id} -- 安全 SELECT * FROM user WHERE id = ${id} -- 不安全
5. MyBatis 如何处理多表关联查询?
回答: MyBatis 通过 resultMap
和 association
、collection
标签实现多表关联查询。association
用于一对一关系,collection
用于一对多关系。例如:
<resultMap id="UserResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<association property="address" javaType="Address" column="address_id" select="selectAddress"/>
</resultMap>
6. 如何在 MyBatis 中实现分页?
回答: MyBatis 提供了多种分页实现方式,常用的有手动分页、数据库分页(如 LIMIT
关键字)和使用分页插件(如 PageHelper
)。例如,使用 LIMIT
关键字实现分页:
SELECT * FROM user LIMIT #{offset}, #{limit}
7. 什么是 MyBatis 中的动态 SQL?如何使用?
回答: 动态 SQL 是指根据条件动态生成 SQL 语句,避免硬编码多个 SQL 语句。MyBatis 提供了 <if>
、<choose>
、<when>
、<otherwise>
、<trim>
等标签,用于在 XML 配置文件中编写动态 SQL。例如:
<select id="findUserByConditions" resultType="User">
SELECT * FROM user WHERE 1=1
<if test="username != null">
AND username = #{username}
</if>
<if test="age != null">
AND age = #{age}
</if>
</select>
8. MyBatis 中的二级缓存是如何工作的?
回答: MyBatis 提供一级缓存(SqlSession
级别)和二级缓存(Mapper
级别)。二级缓存是跨 SqlSession
的,所有 SqlSession
对象共享这部分缓存。二级缓存需要手动配置,可以通过在 Mapper
XML 文件中使用 <cache>
标签来开启。示例:
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
9. MyBatis 中如何实现批量操作?
回答: MyBatis 可以通过 foreach
标签和 batch
模式实现批量操作。foreach
标签用于在 XML 文件中遍历集合,batch
模式通过设置 ExecutorType.BATCH
来执行批量更新操作。例如:
<insert id="insertUsers">
INSERT INTO user (username, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.age})
</foreach>
</insert>
10. 如何在 MyBatis 中使用注解方式实现映射?
回答: MyBatis 支持通过注解方式定义 SQL 映射。常用注解包括 @Select
、@Insert
、@Update
、@Delete
以及 @Results
、@Result
等。例如:
@Select("SELECT * FROM user WHERE id = #{id}") @Results({ @Result(property = "id", column = "id"), @Result(property = "username", column = "username") }) User findById(int id);
11. MyBatis 中的 Mapper 接口和 XML 映射文件如何关联?
回答: Mapper 接口和 XML 映射文件通过相同的接口名称和 XML 文件名进行关联。MyBatis 会根据接口的全限定名去查找对应的 XML 文件,并将 XML 文件中的 SQL 映射到接口方法。例如,如果有 UserMapper
接口,那么对应的 XML 文件通常命名为 UserMapper.xml
。
12. MyBatis 中的延迟加载是如何实现的?
回答: MyBatis 的延迟加载通过设置 lazyLoadingEnabled
和 aggressiveLazyLoading
来实现。当 lazyLoadingEnabled
为 true
时,关联对象会在被访问时才加载。通过在 <settings>
中配置:
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
13. 什么是 MyBatis 的 resultMap
?它的作用是什么?
回答: resultMap
是 MyBatis 用于定义结果集映射的高级配置。它允许更复杂的映射规则,例如字段与属性名称不一致、一对一和一对多的关联映射。resultMap
使得 SQL 查询结果能够灵活地映射到 Java 对象上。
14. 如何在 MyBatis 中处理复杂的查询结果?
回答: 处理复杂查询结果时,可以使用 resultMap
配合 association
和 collection
标签来映射复杂的对象结构。对于多表关联查询,association
用于一对一映射,collection
用于一对多映射,discriminator
用于继承映射。
15. MyBatis 支持哪些数据库?
回答: MyBatis 支持几乎所有主流关系型数据库,包括但不限于 MySQL、PostgreSQL、Oracle、SQL Server、DB2、SQLite 等。MyBatis 依赖于 JDBC 驱动程序,因此只要有对应的 JDBC 驱动,MyBatis 就能与之兼容。
16. MyBatis 的 SqlSession
是线程安全的吗?
回答: SqlSession
不是线程安全的,它应该在每个线程中单独创建并使用。一般推荐使用 try-with-resources
或手动关闭 SqlSession
来确保资源的释放。在 Web 应用中,可以使用事务管理器来管理 SqlSession
的生命周期。
17. 如何在 MyBatis 中实现乐观锁?
回答: MyBatis 可以通过在 SQL 中使用版本号字段实现乐观锁。通常在表中添加一个 version
字段,每次更新时检查并更新该字段。例如:
UPDATE user SET username = #{username}, version = version + 1 WHERE id = #{id} AND version = #{version}
18. 如何在 MyBatis 中进行事务管理?
回答: MyBatis 本身不处理事务管理,但可以通过 Spring 框架或手动管理事务。在 Spring 中,可以通过 @Transactional
注解自动管理事务。MyBatis 也可以使用 SqlSession
的 commit()
和 rollback()
方法手动管理事务。
19. 如何在 MyBatis 中实现读写分离?
回答: MyBatis 可以通过配置多个数据源来实现读写分离。使用 Spring 的 AbstractRoutingDataSource
来动态切换数据源,配置读写分离的规则。MyBatis 通过 SqlSessionFactory
的配置来选择不同的数据源。
20. MyBatis 的 Mapper
接口如何与 Spring 集成?
回答: MyBatis 可以通过 @Mapper
注解或 MapperScannerConfigurer
来与 Spring 集成。在 Spring Boot 项目中,只需在启动类中添加 @MapperScan
注解,MyBatis 会自动扫描并注册 Mapper
接口。
21. 如何处理 MyBatis 的 N+1 查询问题?
回答: MyBatis 的 N+1 查询问题通常出现在一对多查询中,解决方法是通过 join
查询或在 select
标签中使用 fetchType=EAGER
来预先加载数据,从而避免多次查询。
22. MyBatis 中如何处理枚举类型?
回答: MyBatis 可以通过自定义 TypeHandler
来处理枚举类型,将数据库中的数值映射为 Java 枚举。还可以在 XML 文件中通过 typeHandler
属性指定 TypeHandler
。
23. 如何在 MyBatis 中配置全局属性?
回答: MyBatis 支持在全局配置文件 mybatis-config.xml
中配置全局属性,如 cacheEnabled
、lazyLoadingEnabled
、mapUnderscoreToCamelCase
等。配置示例如下:
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> </settings>
24. 如何在 MyBatis 中使用存储过程?
回答: MyBatis 支持通过 XML 映射文件调用存储过程。使用 call
关键字调用存储过程,并通过 @Param
注解传递参数。例如:
<select id="callProcedure" statementType="CALLABLE"> {call my_procedure(#{param1, mode=IN}, #{param2, mode=OUT})} </select>
25. MyBatis 中如何进行 SQL 性能优化?
回答: MyBatis 的 SQL 性能优化可以通过以下方式实现:
- 使用索引和优化 SQL 查询语句;
- 使用 MyBatis 的缓存机制;
- 避免 N+1 查询问题;
- 通过 SQL 日志分析和优化慢查询。
让我们一起学习,一起进步!期待在评论区与你们见面。
祝学习愉快!