什么是Mybatis?
MyBatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态SQL,可以严格控制SQL执行性能,灵活度高。
MyBatis 提供了与数据库交互的API,让程序员可以通过简单的XML或注解来配置和映射原生信息,将接口和Java的实体类映射到数据库中的记录,免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
Mybaits的优缺点?
优点
MyBatis 的核心优势在于其简单性和灵活性。其优点如下:
- 精细的SQL控制:MyBatis 允许我们手写SQL,因此可以进行精细的查询优化,充分利用数据库本身的特性和优化。
- 灵活性高:支持动态SQL,便于构建复杂查询。我们可以根据不同的条件动态地改变SQL语句,而无需改变Java代码结构。
- 易于理解和学习:MyBatis 的配置和使用相对简单直观,对于熟悉SQL的初学者来说是非常容易上手的。
- 消耗资源小:没有完整ORM框架那样的数据持久层自动化管理的开销,MyBatis 的资源消耗相对较小。
- 良好的集成性:可以很容易地与其他框架(如Spring)集成,同时社区和第三方工具支持良好。
缺点
- SQL管理:随着项目的扩展,SQL语句可能会变得难以管理,尤其是当这些SQL语句分散在大量的XML文件中时。
- 数据库移植性:因为需要手写SQL,所以在不同的数据库之间迁移可能需要重新编写特定的SQL语句,降低了代码的移植性。
- 部分自动化缺失:与完整的ORM工具相比,缺乏自动的CRUD操作、对象关系维护。
- 复杂性管理:对于有很多关联关系和继承映射的复杂领域模型,MyBatis的处理不如完整的ORM框架方便。
- 二次开发需求:为了满足特定需求,如分页、性能优化等,需要进行二次开发和额外配置。
- 维护性挑战:由于SQL和Java代码的分离,大型项目中的跨团队开发和维护可能会带来沟通和协作的挑战。
- 缓存支持有限:MyBatis 自身的缓存机制比较简单,对于复杂的缓存需求,开发者需要自行实现或集成第三方缓存解决方案。
所以,MyBatis 的适用场景通常是那些需要与数据库交互且对SQL优化要求较高的项目,而对于那些需要快速开发和避免直接处理SQL的场景,可能更倾向于使用完整的ORM框架。
Hibernate 和 MyBatis 有什么区别?
基本哲学:
- Hibernate 是一个全功能的对象关系映射(ORM)框架,它封装了很多数据库操作的细节,提供了一个更高层次的对象数据管理接口。
- MyBatis 是一个半ORM框架,它允许你直接编写SQL,同时提供了一些ORM特性,比如对象映射。
SQL处理:
- Hibernate 自动生成SQL,用户几乎不需要编写任何SQL语句,这样可以减少开发时间,但有时可能牺牲了一些性能。
- MyBatis 需要用户自己编写SQL语句,这为性能优化提供了可能,但增加了开发工作量。
对象映射:
- Hibernate 通过HQL(Hibernate Query Language)或Criteria API来执行数据库操作,并将结果映射到Java对象,通常不需要用户了解底层的SQL。
- MyBatis 通过XML或注解方式将指定的SQL语句映射到Java方法和对象上。
缓存机制:
- Hibernate 提供了更为强大的内置一级和二级缓存机制,可以显著提高应用性能。
- MyBatis 提供一级缓存和二级缓存的支持,但相比Hibernate,它的缓存机制较为简单。
数据操作层次:
- Hibernate 作为一个完整的ORM解决方案,操作的是对象和对象的关系,它隐藏了很多JDBC和数据库相关的细节。
- MyBatis 更接近于JDBC,它操作的更多是数据而不是对象,因此开发者需要有更多的数据库和SQL知识。
#{} 和${}的区别是什么?
#{}
是占位符,预编译处理;${}
是拼接符,字符串替换,没有预编译处理。- MyBatis 在处理
#{}
时,会根据Java类型推断出JDBC类型,并根据类型对数据进行处理,而${}
传入的数据会被当作字符串拼接到SQL语句中。 #{}
会被MyBatis解析为一个占位符参数(?
),然后调用PreparedStatement的set方法来赋值,这意味着MyBatis会自动为SQL参数提供引号,防止SQL注入,而${}
会将参数直接嵌入到SQL语句中,这可能会导致SQL注入的风险,因为参数的内容会直接解释为SQL语句的一部分。
总结:尽管 ${}
在某些情况下有用,但是必须非常小心地使用,需要程序做严格的控制与校验,以避免SQL注入攻击。在大多数情况下,推荐使用 #{}
来传递参数,因为它更安全且易于维护。
模糊查询like语句该怎么写?
一共有四种写法:
一、使用**
#{}
**传递完整的模糊匹配字符串
在Java代码中构建带有通配符的字符串,然后传递给MyBatis:
// Java 拼接
String pattern = "%" + searchTerm + "%";
// 然后将pattern作为参数传递给MyBatis的映射器方法
SELECT * FROM table_name WHERE column LIKE #{pattern}
这种方式的缺点就是要在 Java 代码中拼接条件,使代码变得复杂且有点儿傻。
二:使用**
#{}
和SQL的CONCAT
**函数
将通配符直接在SQL中通过CONCAT
函数添加。
SELECT * FROM table_name WHERE column LIKE CONCAT('%', #{value}, '%')
**推荐!!**这种方式有一个缺陷就是它依赖数据库的 CONCAT()
函数,不同的数据库可能会所不同。
三、使用**
${}
**直接插入
这种方式需要在传递参数前就把通配符加入到字符串中,并且要确保字符串的安全性以避免SQL注入风险。
SELECT * FROM table_name WHERE column LIKE '%${value}%'
**不推荐!!**有 SQL 注入的风险,不可控。
四、在MyBatis的XML配置中使用bind元素
使用<bind>
元素可以在不改变方法签名的情况下,创建一个局部变量用于SQL查询。
<select id="findByName" parameterType="map" resultType="YourType">
<bind name="pattern" value="'%' + _parameter['name'] + '%'"/>
SELECT * FROM table_name WHERE column LIKE ${pattern}
</select>
这种方法会使 XML配置变得更复杂,且需要理解MyBatis的bind
功能。
五、使用**
#{}
**和XML中的字符串拼接
SELECT * FROM table_name WHERE column LIKE '%' || #{value} || '%'
并不是所有数据库都支持||
作为字符串拼接操作符。
能说说MyBatis的工作原理吗?
MyBatis工作原理是从SqlSessionFactory
获取SqlSession
,然后使用SqlSession
获得Mapper
接口的代理对象,通过这个代理对象的方法执行映射文件中定义的SQL语句,最终处理结果集并返回结果。这个过程涉及到详细的配置和多个组件之间的相互作用。具体如下:
- 配置解析
- MyBatis启动时会加载配置文件(如
mybatis-config.xml
)和映射文件(Mapper XML),解析后创建Configuration
(配置对象)。
- MyBatis启动时会加载配置文件(如
- SqlSessionFactory构建
- 通过配置对象,MyBatis构建
SqlSessionFactory
,此工厂对象为应用程序提供SqlSession
。
- 通过配置对象,MyBatis构建
- SqlSession交互
SqlSession
提供了执行SQL命令的所有方法。我们通过SqlSession
来执行配置文件或注解中定义的SQL语句。
- 执行器(Executor)
Executor
是MyBatis的一个核心组件,用于执行更新、查询、延迟加载等操作。- MyBatis有不同的Executor实现,比如简单执行器(SimpleExecutor)、重用执行器(ReuseExecutor)、批量执行器(BatchExecutor)等。
- 语句处理器(Statement Handler)
- 对于执行的每一个SQL语句,MyBatis会创建一个
Statement Handler
。它使用ParameterHandler
来处理SQL参数,并使用ResultSetHandler
处理查询返回的结果集。
- 对于执行的每一个SQL语句,MyBatis会创建一个
- 参数映射
- SQL执行时,MyBatis会对SQL语句中的
#{}
占位符参数进行映射处理,替换为实际的参数值,并能处理参数类型转换。
- SQL执行时,MyBatis会对SQL语句中的
- SQL执行与结果处理
- 执行SQL语句后,MyBatis会使用
ResultSetHandler
将JDBC的ResultSet
转换为Java对象或对象集合。
- 执行SQL语句后,MyBatis会使用
- 映射器(Mapper)
- Mapper接口是用户自定义的DAO接口,MyBatis会为这些接口生成代理对象,代理对象内部调用MyBatis的核心代码执行。
- 映射器XML文件定义了SQL语句与接口方法的映射关系。
Dao接口的工作原理是什么?
MyBatis中的DAO(Data Access Object)接口是用户定义的一个接口,它包含了访问数据库所需要的方法定义。这些方法对应于映射文件中定义的SQL语句。MyBatis框架使用这个接口和映射文件来在运行时动态创建DAO的实现,这个实现是一个Mapper代理对象。工作原理如下:
- 接口和映射文件
- 写一个DAO接口,并定义了相关的方法。同时,为这些方法提供了SQL映射文件中的映射语句。
- SqlSession获取Mapper
- 通过
SqlSession
的getMapper()
方法,MyBatis会为DAO接口生成一个动态代理对象(Mapper代理)。这个代理对象知道如何将接口方法调用转换成对映射文件中定义的SQL语句的执行。
- 通过
- 方法调用转换
- 当调用DAO接口的方法时,实际上是在调用代理对象的对应方法。这个代理对象会拦截这个调用,确定要执行的SQL语句。
- SQL语句的执行
- 代理对象找到映射文件中对应的SQL语句,并通过MyBatis底层的执行器(Executor)执行这个语句。如果是查询操作,它还负责将结果映射成Java对象或对象集合。
- 结果返回
- 执行完成后,方法的结果会被返回给调用者。如果方法是一个查询,MyBatis会将结果集映射为Java对象或者对象集合返回;如果方法是更新或删除,它会返回一个表示受影响行数的整数。
- SqlSession管理
SqlSession
的生命周期应该由调用者来控制,通常在方法调用结束之后关闭SqlSession
以释放资源。
Dao接口里的方法,参数不同时,方法能重载吗?
在MyBatis的Mapper接口中,方法重载通常是不被支持的。
这是因为MyBatis在构建时需要根据方法的名称来找到对应的SQL语句。如果Mapper接口中有多个同名的方法(即使它们的参数不同),MyBatis就无法确定应该使用哪个SQL语句。所以在 DAO 接口中我们要保证方法名的唯一性。
什么是MyBatis的接口绑定?有哪些实现方式?
MyBatis的接口绑定指的是将Mapper接口与一个XML文件或注解关联起来的过程,这样MyBatis就能通过接口直接调用XML映射文件中的SQL语句。
MyBatis提供了两种实现方式:
- XML映射文件: 这是最传统的方式。创建一个Mapper接口,然后提供一个同名的XML映射文件,文件名必须与接口的名称相同,并且放在与接口相同的包结构下。MyBatis在启动时会读取这些文件,然后根据这些映射文件中的namespace与接口进行绑定。接口中的方法名应该与映射文件中定义的statement的id相匹配。
- 注解: 注解方式是一种更加简洁的配置方法。可以直接在Mapper接口的方法上使用MyBatis提供的注解来指定SQL语句,如
@Select
、@Insert
、@Update
和@Delete
。这种方式省去了编写XML映射文件的需要,使得项目更加简洁,特别是对于简单的SQL操作。但是,对于复杂的SQL语句,使用XML映射文件会更加灵活和强大。
MyBatis是否可以映射Enum枚举类?
可以。MyBatis提供了对枚举类型本支持,可以将枚举类型的名称或者索引值存储到数据库中,也可以将数据库中的值映射回Java的枚举类型。
有如下几种映射的方式:
- 按名称映射
默认情况下,MyBatis可以自动将枚举的名称(即调用枚举的name()
方法得到的字符串)存储到数据库中,并在从数据库读取时按名称将字符串转换回枚举。
- 按序号映射
MyBatis也可以配置为按枚举的序号(即枚举声明中的位置,从0开始)进行映射。在这种情况下,MyBatis将调用枚举的ordinal()
方法来获取其序号,并存储该序号到数据库中。
- 自定义TypeHandler
如果上面两种方式无法满足我们的需求,我们可以自定义TypeHandler
,自定义的方式允许我们控制Java类型和数据库类型之间的精确转换方法,如下:
public class MyEnumTypeHandler extends BaseTypeHandler<MyEnum> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, MyEnum parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.getCode()); // 假设每个枚举值都有一个getCode方法
}
@Override
public MyEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
String code = rs.getString(columnName);
return MyEnum.getByCode(code); // 假设MyEnum有一个根据code获取枚举的方法
}
// 实现其他必要的方法...
}
然后在 XML 中使用:
<typeHandlers>
<typeHandler handler="com.example.MyEnumTypeHandler" javaType="com.example.MyEnum"/>
</typeHandlers>
实体类属性名和表中字段名不一样 ,怎么办?
如果实体类的属性名和表中的字段名不一致,我们可以使用 @Result
注解来映射字段名和属性名,或者在 XML 映射文件中使用 <resultMap>
元素来解决这个问题。
使用
@Result
注解
@Select("SELECT field_name1, field_name2 FROM table_name")
@Results({
@Result(property = "propertyName1", column = "field_name1"),
@Result(property = "propertyName2", column = "field_name2")
})
List<YourEntity> selectAll();
@Select
注解指定了要执行的 SQL 查询@Results
注解定义了查询结果如何映射到实体类的属性上property
是实体类的属性名column
是 SQL 查询结果集中的列名。
使用 XML 映射文件
<resultMap id="YourEntityResultMap" type="YourEntity">
<result property="propertyName1" column="field_name1"/>
<result property="propertyName2" column="field_name2"/>
</resultMap>
然后,在 <select>
或其他 SQL 映射语句中引用这个 resultMap:
<select id="selectAll" resultMap="YourEntityResultMap">
SELECT field_name1, field_name2 FROM table_name
</select>
在 XML 中还有一种方式,就是使用 AS
:
<select id="selectAll" resultType="XxxDTO">
select field_name1 as fieldName1,field_name2 as fieldName2
from table_name
</select>
fieldName1
、fieldName2
都是 XxxDTO 里面的属性。
Mybatis是如何进行分页的?分页插件的原理是什么?
MyBatis 本身是不直接支持分页的,通常需要我们手动写分页的 SQL 语句,或者使用分页插件来简化操作。下面是两种常用的方法。
1、手动编写分页 SQL
在 SQL 语句中直接使用数据库的分页语法。比如,在 MySQL 中,可以使用 LIMIT
语句来实现分页:
SELECT * FROM some_table LIMIT #{offset}, #{limit}
在 MyBatis 的映射文件或注解中,#{offset}
和 #{limit}
是传入的参数,分别表示分页的起始行和返回的记录数。
2、使用分页插件
目前有一些分页插件,这些插件实现的原理都是通过拦截 MyBatis 的 SQL 操作,然后在查询 SQL 后面添加数据库对应的分页语句。具体过程如下,以 PageHelper 分页插件为例:
- 设置分页参数:在执行查询之前,调用 PageHelper 的静态方法设置分页参数
PageHelper.startPage(pageNum, pageSize);
需要注意,这条语句一定要紧跟查询 SQL 前面。
- 插件拦截器:PageHelper 定义了一个 MyBatis 插件拦截器(Interceptor),它会拦截查询操作。
- SQL 修改:在执行查询操作时,PageHelper的拦截器会自动地将分页参数转换为数据库对应的分页语句,比如在 MySQL 中,它会添加
LIMIT
语句。 - 执行查询:带有分页语句的查询被执行。
- 结果处理:查询结果会被自动封装到 PageInfo 对象中,提供了获取总记录数、总页数、当前页码等信息的便利方法。
Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
在 MyBatis 的 XML 映射文件中,id 必须在同一个命名空间(namespace)内唯一。不同的 XML 映射文件通常代表不同的命名空间,因此在不同的 XML 映射文件中,id 可以重复,因为它们属于不同的命名空间。
每个 XML 映射文件都有一个 <mapper>
标签,该标签的 namespace
属性用于定义该映射文件的命名空间:
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUser" ...>
...
</select>
</mapper>
<!-- ProductMapper.xml -->
<mapper namespace="com.example.mapper.ProductMapper">
<select id="selectUser" ...>
...
</select>
</mapper>
尽管两个 <select>
标签的 id 都是 selectUser
,但它们属于不同的命名空间,所以没有冲突。
Mybatis是否支持延迟加载?原理是什么?
MyBatis 支持延迟加载(也称为懒加载),它允许按需加载关联的对象,而不是在加载主对象时立即加载所有关联对象。
1、延迟加载的配置
在 MyBatis 的配置文件中,可以通过设置 lazyLoadingEnabled
属性为 true
来启用延迟加载:
<settings>
<!-- 开启全局的延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
2、映射配置
在 MyBatis 的映射文件中,需要配置 association
(一对一关联)或 collection
(一对多关联)标签,并指定 fetchType
属性为 lazy
:
<association property="author" column="author_id"
javaType="Author" fetchType="lazy">
<!-- 关联的映射 -->
</association>
或者对于一对多关系:
<collection property="posts" ofType="Post"
column="blog_id" select="selectPosts" fetchType="lazy">
<!-- 关联的映射 -->
</collection>
在这里,select
属性指定了加载关联对象时要使用的映射语句的 id。
MyBatis 将只在真正需要使用到关联对象的数据时才去数据库中查询加载,从而可以提高应用程序的性能,特别是在处理包含大量关联对象的复杂映射时。然而,延迟加载也可能导致所谓的“N+1 查询问题”,因为每个关联对象的加载都可能产生一次数据库查询。
3、实现原理
延迟加载的原理是基于代理对象。当加载一个对象时,MyBatis 会为关联的属性创建一个代理(Proxy)对象。当实际访问这些关联的对象时(比如调用它们的方法),代理对象就会触发加载真实数据的操作。
例如,假设有一个 Blog 对象和一个 Author 对象,它们是一对一关联的。在延迟加载启用的情况下,当加载 Blog 对象时,并不会立即加载 Author 对象。而是给 Author 属性生成一个代理对象。当第一次访问 Blog 对象的 Author 属性(例如调用 getAuthor()
方法)时,代理对象就会触发对应的 SQL 查询,加载 Author 对象的数据。
慎用延迟加载!!! 慎用延迟加载!!! 慎用延迟加载!!!
如何获取生成的主键?
可以使用 useGeneratedKeys
和 keyProperty
属性来完成。
1、注解
@Insert("INSERT INTO users (username, password, email) VALUES (#{username}, #{password}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
useGeneratedKeys="true"
:这个属性告诉 MyBatis 需要获取由数据库自动生成的主键。keyProperty="id"
:这个属性指定了与生成的主键值相绑定的目标属性,即实体类中接收主键值的属性名。
执行这个 insertUser
方法后,user
对象的 id
属性将被设置为数据库生成的主键值。
2、XML 配置
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, password, email)
VALUES (#{username}, #{password}, #{email})
</insert>
当你执行这个 insert
语句后,MyBatis 会获取数据库生成的主键值,并赋值给传入对象的 id
属性。
这种机制依赖于 JDBC 驱动的支持,目前大多数都支持 JDBC 的 getGeneratedKeys
方法来检索由数据库在插入操作中自动生成的键。包括 MySQL、PostgreSQL、SQL Server、 Oracle 等主流数据库都支持这一特性。
MyBatis支持动态SQL吗?
MyBatis 是支持动态 SQL的。MyBatis 供了多种 XML 标签来支持动态 SQL,它允许在运行时根据不同的条件构建不同的 SQL 语句。下面举几个很常见的例子。
1、
用于根据条件包含 SQL 片段。如果 <if>
标签中的测试条件为真,则包含其内的 SQL 片段
<select id="findActiveUsers" resultType="User">
SELECT * FROM users
WHERE status = 'ACTIVE'
<if test="type != null">
AND type = #{type}
</if>
</select>
2、
, , 和
相当于 Java 中的 switch
或 if-else if-else
语句。
<select id="findUsersByType" resultType="User">
SELECT * FROM users
<choose>
<when test="type == 'NEW'">
WHERE creation_date = CURRENT_DATE
</when>
<when test="type == 'ACTIVE'">
WHERE status = 'ACTIVE'
</when>
<otherwise>
WHERE status = 'INACTIVE'
</otherwise>
</choose>
</select>
3、
能够自动地在其包含的 SQL 片段前面添加 WHERE
关键字,并且如果片段开始于 AND
或 OR
,它会将它们去除,防止逻辑错误。
<select id="findUsers" resultType="User">
<where>
<if test="name != null">
name = #{name}
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
</select>
还有很多其他的标签,比如 <set>
、<foreach>
、<bind>
这里就不一一介绍了。
MyBatis如何执行批量操作?
MyBatis 支持批量操作,一般有两种方式:
- 使用
<foreach>
标签来构建批量操作的 SQL 语句 - 使用批量执行的功能来进行批处理
**一、使用 ****
<foreach>
**标签来构建批量操作的 SQL 语句
在Mybatis 中,我们可以使用 foreach
标签来遍历一个集合,并为集合中的每个元素生成 SQL 语句的一部分:
<insert id="batchInsertUsers" parameterType="list">
INSERT INTO users (username, password, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.password}, #{user.email})
</foreach>
</insert>
list
是传入的参数,它包含了要插入的 User对象。<foreach>
会为列表中的每个用户生成一组值,并用逗号分隔。
二、使用 ExecutorType.BATCH 的批量操作
这种方案需要在 MyBatis 配置文件中将 defaultExecutorType
设置为 BATCH
。这样配置之后,SQL会话(SqlSession
)将默认以批处理模式执行操作。
<configuration>
<settings>
<setting name="defaultExecutorType" value="BATCH"/>
</settings>
</configuration>
这样,在 XML 写的 SQL 语句不需要特别的批量操作语法, MyBatis 会在内部处理批处理逻辑:
<insert id="insertUsers" parameterType="list">
INSERT INTO users (username, password, email) VALUES (#{user.username}, #{user.password}, #{user.email})
</insert>
在业务层面我们需要创建一个 SqlSession
实例,并指定 ExecutorType.BATCH
。然后,对每个对象调用映射器方法,MyBatis 会将这些操作积累起来,直到调用 commit
:
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (YourEntity entity : entities) {
mapper.insertUsers(entity);
}
session.commit();
}
说说Mybatis的一级、二级缓存?
MyBatis 提供了两级缓存机制:一级缓存和二级缓存。
一、一级缓存
一级缓存是 MyBatis 中的默认缓存。每当一个新的 SqlSession
被创建时,都会有一个新的一级缓存被创建。这意味着一级缓存是会话(SqlSession
)级别的,它仅在会话内部有效,且只对单个会话可见。
- 工作原理:当一个
SqlSession
发出一个数据库查询请求时,结果会被缓存在这个会话中。如果后续的查询请求(在同一个SqlSession
中)与之前的查询相同,那么数据将直接从缓存中获取,而不是再次访问数据库。 - 生命周期:一级缓存的生命周期与
SqlSession
相同。当SqlSession
被关闭时,其对应的一级缓存也会被清空。 - 限制:一级缓存仅限于单个会话范围内,这意味着它不会在多个会话之间共享数据。
二、二级缓存
与一级缓存不同,二级缓存是跨会话的,它可以被多个 SqlSession
共享。二级缓存存储在一个全局作用域,这使得不同会话间可以共享数据。
- 配置:要启用二级缓存,需要在 MyBatis 配置文件中进行相关配置,并在映射文件中明确指定使用二级缓存。
- 工作原理:当一个
SqlSession
查询数据库时,MyBatis 会先检查二级缓存是否有匹配的数据。如果找到匹配的数据,就会直接使用它;如果没有,它将查询数据库并将结果放入二级缓存中。 - 生命周期:二级缓存的生命周期与应用的生命周期相同。一旦初始化,它将在应用运行期间一直存在。
- 注意事项:使用二级缓存时需要谨慎,因为不当的使用可能会导致数据不一致的问题。比如,在某个会话中修改了数据,但这个修改可能不会立即反映到二级缓存中。
两者比较
- 作用域:一级缓存是会话级别的,而二级缓存是跨会话的。
- 生命周期:一级缓存随
SqlSession
结束而清除,二级缓存则通常伴随应用的整个生命周期。 - 数据共享:一级缓存不会在不同的会话之间共享数据,二级缓存可以。
为什么Mapper接口不需要实现类?
由于 MyBatis 使用了动态代理技术来自动创建 Mapper 接口的实现,所以不需要显示实现 Mapper 接口。实现过程如下:
- 当调用 Mapper 接口的方法时,MyBatis 会使用 Java 的动态代理机制来创建一个实现了该接口的代理对象。这个代理对象会拦截接口方法的调用。
- 这个代理对象根据调用的方法名、传入的参数等信息去查找对应的 SQL 语句。
- 找到 SQL 语句后,代理对象会执行这个 SQL 语句,并将执行结果返回。
MyBatis都有哪些Executor执行器?它们之间的区别是什么?
MyBatis 提供了三种类型的执行器(Executor),这些执行器决定了 SQL 语句的执行行为。
Simple Executor:
Simple Executor
是 MyBatis 的默认执行器。它对每个查询或更新操作执行一个单独的数据库操作。换句话说,每次对数据库的操作,如查询或更新,都会打开和关闭一个数据库连接。
Simple Executor
适用于大多数场景,特别是当数据库操作不是太频繁或者不需要特殊的事务管理时。
Reuse Executor:
Reuse Executor
会重用预处理语句(PreparedStatement
)。当执行多个相同的语句时,它会重用同一个 PreparedStatement
,从而减少对数据库的操作。
Reuse Executor
适用于需要重复执行相同语句的情况,因为它通过重用 PreparedStatement
来提高效率。
Batch Executor:
Batch Executor
用于批量操作。它会积累 SQL 语句,直到调用 commit
或者 flushStatements
方法时才一次性执行所有积累的 SQL。这主要用于批量插入或更新操作,可以显著减少与数据库的交互次数,提高性能。
适合处理大量的插入、更新操作,特别是在需要执行大批量操作以提高性能的情况下。
他们之前的区别如下:
- 资源使用:Simple Executor 每次操作都打开和关闭数据库连接,而 Reuse Executor 会重用连接,Batch Executor 则在最后一起执行积累的操作。
- 性能差异:Simple Executor 对性能的影响最小,但在频繁操作的场景下效率较低。Reuse Executor 在重复语句的情况下更高效,而 Batch Executor 在批量操作时能大幅提高性能。
- 适用场景:Simple Executor 更通用,但在特定场景下(如重复语句或批量操作),Reuse 或 Batch Executor 可能更合适。
MyBatis中如何指定使用哪一种Executor执行器?
一、全局配置
我们可以在 MyBatis 的全局配置文件中,通过设置 defaultExecutorType
属性来指定默认的执行器类型。这个配置会影响到所有的 SqlSession
,除非在获取 SqlSession
时明确指定了不同的执行器类型。
<configuration>
<settings>
<!-- 设置默认执行器类型为 BATCH -->
<setting name="defaultExecutorType" value="BATCH"/>
</settings>
</configuration>
二、局部配置
我们也可以在获取 SqlSession
时指定执行器类型,这将覆盖全局配置文件中的设置。
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
这样写无论全局配置是什么,这个特定的 SqlSession 将使用 Batch Executor
。
说说Mybatis的插件运行原理,如何编写一个插件?
MyBatis 的插件运行原理基于 Java 的动态代理机制。插件(Plugin)在 MyBatis 中用于拦截在某个执行点上的操作,如执行 SQL 语句前后的处理。这种机制允许开发者在不修改 MyBatis 核心代码的情况下,扩展或修改 MyBatis 的核心功能。
一、运行原理
- 动态代理:MyBatis 使用 Java 的动态代理来创建代理对象,代理 Mapper 接口的方法调用、Statement 的创建、查询和结果的处理等。
- 拦截点:MyBatis 允许插件拦截四种类型的方法:
- Executor(更新、查询、提交、回滚等操作)
- ParameterHandler(设置 SQL 参数)
- ResultSetHandler(处理结果集)
- StatementHandler(准备和执行 SQL 语句)
- 插件配置:通过在 MyBatis 配置文件中配置插件,可以指定插件拦截哪些方法。
- 拦截处理:当拦截到某个方法时,插件可以执行额外的处理,如日志记录、性能监控等。
二、编写一个插件
- 实现 Interceptor 接口
创建一个类实现 MyBatis 的 Interceptor
接口,并实现其中的 intercept
方法。
- 使用 @Intercepts 注解
使用 @Intercepts
注解标注你的插件类,指定要拦截的方法。在注解中,你可以使用 @Signature
来定义拦截的接口、方法以及参数类型
- 插件逻辑
在 intercept
方法中编写你的插件逻辑。你可以在方法执行前后添加自定义逻辑。
- 配置 MyBatis
在 MyBatis 的配置文件中添加你的插件,这样 MyBatis 就会加载并应用该插件。
MyBatis是如何防止SQL注入的?
MyBatis 防止 SQL 注入的主要机制是使用预处理语句(Prepared Statements),这是一种在数据库层面就内置了防止 SQL 注入的特性的数据库功能。详情如下:
- 参数化查询:
- MyBatis 使用参数化的 SQL 语句,也就是说,它不是直接在 SQL 语句中拼接参数,而是使用问号(
?
)作为参数的占位符。 - 当执行 SQL 时,这些占位符会被实际的参数值替换,但这个替换过程是由数据库驱动处理的,不是简单的字符串替换。
- MyBatis 使用参数化的 SQL 语句,也就是说,它不是直接在 SQL 语句中拼接参数,而是使用问号(
- 安全的参数处理:
- 在数据库驱动层面,参数值会被正确地转义,确保它们不会被解释为 SQL 代码的一部分,这样就避免了 SQL 注入攻击。
- 例如,如果用户的输入包含 SQL 语句的一部分(比如
'; DROP TABLE users; --
),这个输入会被数据库驱动转义,从而在最终执行的 SQL 语句中失去作为代码的功能。