一、MyBatis 基础概念相关
-
什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作,使得开发人员能够更专注于 SQL 语句本身以及业务逻辑,通过简单的 XML 配置文件或者注解就能将 Java 对象与数据库表中的记录进行映射关联。 -
MyBatis 与 JDBC 的区别是什么?
- 代码复杂度方面:JDBC 操作数据库时需要编写大量的样板代码,比如加载驱动、创建连接、编写 SQL 语句、设置参数、执行语句、处理结果集以及关闭资源等一系列繁琐操作。而 MyBatis 对 JDBC 进行了封装,极大地简化了这些流程,开发人员只需关注 SQL 语句编写和对象与数据库记录的映射关系,很多底层操作都由 MyBatis 自动完成。
- 数据库移植性方面:JDBC 是 Java 操作数据库的标准接口,理论上可以切换不同的数据库,但在实际切换时,由于不同数据库的 SQL 语法差异等,需要大量修改 SQL 语句。MyBatis 可以通过配置数据库连接信息方便地切换数据库,并且能针对不同数据库进行 SQL 语句的差异化配置,一定程度上提高了数据库移植的便利性。
- 数据映射灵活性方面:JDBC 处理查询结果集时,通常需要手动将结果集中的数据逐行提取并封装到 Java 对象中,比较麻烦。MyBatis 提供了强大的映射功能,比如通过
resultMap
可以灵活地将数据库表的列与 Java 对象的属性进行各种复杂的映射,支持一对一、一对多、多对多等关系映射。
-
简述 MyBatis 的工作原理。
- 加载配置文件:MyBatis 首先会加载全局配置文件(通常是
mybatis-config.xml
),这个文件包含了数据库连接相关的配置(如驱动类、连接地址、用户名、密码等)以及其他诸如类型别名、插件、映射器(mapper)等全局设置信息。 - 构建 SqlSessionFactory:根据加载的配置文件,创建
SqlSessionFactory
对象,它是线程安全的,整个应用程序生命周期内一般只需创建一次,是后续创建SqlSession
的工厂类。 - 获取 SqlSession:从
SqlSessionFactory
中获取SqlSession
对象,SqlSession
相当于数据库连接会话,提供了执行查询、插入、更新、删除等操作的方法,但它不是线程安全的,通常在每次需要与数据库交互时获取,用完后及时关闭。 - 查找映射语句(Mapper):当要执行具体数据库操作时,依据接口(通常是 Mapper 接口)和对应的 XML 映射文件(或基于注解的映射方式)去查找对应的 SQL 语句定义。
- 执行 SQL 语句:通过
SqlSession
调用对应的 Mapper 接口方法,底层会根据映射找到对应的 SQL 语句去执行,对于查询操作会按照配置的结果映射规则(如resultMap
)将结果转换为对应的 Java 对象返回。 - 处理结果:根据操作类型(查询、更新等)对返回结果进行相应处理,比如查询操作得到结果后按规则映射为 Java 对象或集合,更新操作返回受影响行数等信息。
- 关闭 SqlSession:完成数据库交互后,关闭
SqlSession
释放资源。
- 加载配置文件:MyBatis 首先会加载全局配置文件(通常是
二、配置文件相关
-
请介绍一下 MyBatis 的配置文件(
mybatis-config.xml
)主要包含哪些内容?- 环境配置(environments):可以配置多个数据库环境(如开发环境、测试环境、生产环境等),每个环境下定义事务管理器(
transactionManager
)类型(通常是JDBC
或MANAGED
)以及数据源(dataSource
)类型(如POOLED
、UNPOOLED
、JNDI
等),并配置相应的数据库连接详细信息,像驱动、连接地址、用户名、密码等。 - 类型别名(typeAliases):为 Java 类型定义别名,方便在映射文件等地方使用,减少全限定类名的书写,例如可以定义
typeAliases
下的<typeAlias alias="User" type="com.example.entity.User"/>
,后续在 SQL 映射等地方就可以直接用User
来指代com.example.entity.User
这个类了。 - 插件(plugins):可以配置插件来增强 MyBatis 的功能,比如分页插件、性能监控插件等,插件可以拦截 MyBatis 执行过程中的某些方法,实现自定义的逻辑,像在执行查询语句前添加分页逻辑等。
- 映射器(mappers):用于指定 SQL 映射文件(
.xml
文件)或者 Mapper 接口的位置,让 MyBatis 能够找到对应的 SQL 语句定义,常见的配置方式有通过<mapper resource="com/example/mapper/UserMapper.xml"/>
来指定 XML 文件路径,或者<mapper class="com.example.mapper.UserMapper"/>
直接指定接口类(一般基于注解的映射方式下会这样用)。
- 环境配置(environments):可以配置多个数据库环境(如开发环境、测试环境、生产环境等),每个环境下定义事务管理器(
-
如何在 MyBatis 配置文件中配置多数据源?
可以在environments
标签内配置多个<environment>
子标签,每个子标签代表一个不同的数据库环境,设置不同的id
、transactionManager
和dataSource
等信息,例如:
<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/mydb?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/testdb?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="654321"/>
</dataSource>
</environment>
</environments>
然后在获取 SqlSessionFactory
时,可以通过指定不同的环境 id
来选择使用对应的数据源,例
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "test"); // 这里选择使用 "test" 环境对应的数据源
三、SQL 映射相关
- MyBatis 的 SQL 映射文件(
.xml
文件)中常见的标签有哪些,分别有什么作用?<select>
:用于定义查询语句,通过id
属性来唯一标识该查询语句,在 Mapper 接口中会有对应的方法与之关联,resultMap
或resultType
属性用于指定查询结果的映射方式,可以返回单个对象、对象集合等不同形式的数据,例如:
<select id="getUserById" resultMap="UserResultMap">
select * from users where id = #{id}
</select>
<insert>
:用于定义插入语句,通常会配置keyProperty
和useGeneratedKeys
属性来获取自增主键的值(如果数据库支持自增主键),例如:
<insert id="addUser" keyProperty="id" useGeneratedKeys="true">
insert into users (username, password) values (#{username}, #{password})
</insert>
<update>
:定义更新语句,比如更新用户信息等操作,例如:
<update id="updateUser" >
update users set username = #{username}, password = #{password} where id = #{id}
</update>
<delete>
:用于定义删除语句,像删除指定用户等情况,例如:
<delete id="deleteUserById">
delete from users where id = #{id}
</delete>
<resultMap>
:是 MyBatis 中非常重要的一个标签,用于自定义结果集的映射关系,当数据库表的列名和 Java 对象的属性名不完全一致或者存在复杂的对象关系(一对一、一对多等)时,通过它来精确配置映射规则,例如:
<resultMap id="UserResultMap" type="com.example.entity.User">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
<result property="password" column="user_password"/>
<association property="address" javaType="com.example.entity.Address">
<id property="id" column="address_id"/>
<result property="city" column="address_city"/>
<result property="street" column="address_street"/>
</association>
</resultMap>
<parameterMap>
:在早期版本中用于定义参数映射,不过在 MyBatis 3 之后,使用#{}
或${}
占位符结合参数传递的方式基本替代了它的大部分功能,现在已经较少使用了。
- 解释一下 MyBatis 中
#{}
和${}
的区别。#{}
:是预编译处理的占位符,MyBatis 在处理 SQL 语句时,会将#{}
替换为?
,然后通过 PreparedStatement 来设置参数值,这样做可以有效防止 SQL 注入攻击,并且能根据参数类型自动进行类型转换等处理,是推荐使用的参数传递方式,例如:
select * from users where username = #{username}
${}
:是字符串拼接的方式,MyBatis 会直接将${}
中的内容替换到 SQL 语句中,不会进行预编译等安全处理,如果传入的内容包含恶意 SQL 语句片段,就容易引发 SQL 注入问题,不过在某些特定场景下有其用途,比如动态表名、动态排序字段等情况,例如要根据传入的表名动态查询数据:
select * from ${tableName} where id = #{id}
但使用 ${}
时需要特别谨慎,确保传入的内容是安全可靠的,避免安全风险。
四、接口与动态 SQL 相关
-
MyBatis 中 Mapper 接口和 XML 映射文件是如何关联的?
Mapper 接口和 XML 映射文件的关联主要通过以下几种方式:- 命名空间(namespace):在 XML 映射文件中,需要通过
<mapper namespace="com.example.mapper.UserMapper">
来指定命名空间,这个命名空间要和对应的 Mapper 接口的全限定类名保持一致,这样 MyBatis 就能根据接口方法名去 XML 文件中查找对应的 SQL 语句定义,例如接口中有User getUserById(int id)
方法,在 XML 文件中就可以通过<select id="getUserById"...>
来定义对应的查询 SQL 语句,MyBatis 会将接口方法调用与 XML 中的 SQL 语句对应起来。 - 方法名与 SQL 语句
id
匹配:在保证命名空间一致的基础上,接口中的方法名要和 XML 映射文件中定义的 SQL 语句的id
属性值相同(一般是驼峰命名法和下划线命名法之间按照一定规则转换后能对应上,比如getUserById
方法对应get_user_by_id
这样的 SQL 语句id
,不过最好保持完全一致更清晰),这样才能正确关联并执行相应的 SQL 语句。
- 命名空间(namespace):在 XML 映射文件中,需要通过
-
请举例说明 MyBatis 中的动态 SQL 有哪些用法以及相应的标签?
MyBatis 提供了丰富的动态 SQL 标签来根据不同条件动态拼接 SQL 语句,常见的有:<if>
:用于条件判断,只有当条件满足时,标签内的 SQL 语句片段才会被包含到最终的 SQL 语句中,例如:
<select id="getUsersByCondition" resultMap="UserResultMap">
select * from users
<where>
<if test="username!= null">
and username = #{username}
</if>
<if test="age!= null">
and age = #{age}
</if>
</where>
</select>
这里根据传入的 username
和 age
参数是否为 null
来动态决定是否添加对应的查询条件到 SQL 语句中。
<choose>
、<when>
、<otherwise>
:相当于 Java 中的switch
语句,用于多条件选择,只有一个<when>
分支会被执行,如果所有<when>
条件都不满足,则执行<otherwise>
中的语句,例如:
<select id="getDiscountByUserType" resultMap="DiscountResultMap">
select * from discounts
<where>
<choose>
<when test="userType == 'VIP'">
and discount_rate = 0.8
</when>
<when test="userType == 'NORMAL'">
and discount_rate = 0.9
</when>
<otherwise>
and discount_rate = 1.0
</otherwise>
</choose>
</where>
</select>
<trim>
:用于去除 SQL 语句中多余的关键字或空格等,常配合<if>
等标签使用,比如可以去除<where>
标签前面多余的and
或or
关键字,示例:
<select id="getUsersByCondition" resultMap="UserResultMap">
select * from users
<trim prefix="where" prefixOverrides="and|or">
<if test="username!= null">
and username = #{username}
</if>
<if test="age!= null">
and age = #{age}
</if>
</trim>
</select>
<foreach>
:用于循环遍历集合或数组,将集合中的元素逐个添加到 SQL 语句中,常用于in
条件查询等情况,例如:
<select id="getUsersByIds" resultMap="UserResultMap">
select * from users
<where>
<if test="ids!= null and ids.size() > 0">
id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
这里假设传入的 ids
是一个 List<Integer>
类型的集合,通过 <foreach>
标签将集合中的元素拼接成 in
条件中的值列表。
五、缓存相关
-
MyBatis 有几级缓存,分别是如何工作的?
MyBatis 有一级缓存和二级缓存:- 一级缓存:
- 作用范围:一级缓存是
SqlSession
级别的缓存,也就是在同一个SqlSession
内,对相同的查询语句进行多次查询时,第一次查询会去数据库获取数据,后续的查询如果没有发生数据修改等影响缓存的情况,就会直接从缓存中获取数据,而不用再次向数据库发起请求,提高了查询效率。 - 工作原理:当通过
SqlSession
执行查询操作时,MyBatis 会先查看一级缓存中是否已经存在对应的查询结果,如果存在则直接返回缓存中的数据,如果不存在则向数据库发起查询请求,将查询结果存入一级缓存中,供后续相同查询使用。但当执行了插入、更新、删除等会改变数据库数据的操作后,该SqlSession
的一级缓存会被清空,以保证缓存数据的准确性。
- 作用范围:一级缓存是
- 二级缓存:
- 作用范围:二级缓存是
Mapper
级别的缓存,它的作用范围比一级缓存更广,可以跨SqlSession
使用。也就是说,不同的SqlSession
对同一个 Mapper 中的相同查询语句进行查询时,如果二级缓存中有相应的数据且数据未过期等,就可以直接从二级缓存中获取数据,进一步提高了查询性能,尤其适用于多线程或者多个请求对相同数据的查询场景。 - 工作原理:首先要在 MyBatis 的配置文件以及对应的 Mapper 映射文件中开启二级缓存(配置
cacheEnabled
属性等),当一个SqlSession
执行查询操作并将结果存入二级缓存后,其他SqlSession
再进行相同查询时,会先查找二级缓存,如果命中缓存则直接返回数据,若未命中则去数据库查询,然后将查询结果存入二级缓存中。二级缓存默认使用PerpetualCache
(一种基于HashMap
的简单缓存实现),也可以通过配置实现自定义的缓存策略,并且可以设置缓存的过期时间、缓存的大小等参数来更好地管理缓存。
- 作用范围:二级缓存是
- 一级缓存: