2023-12-14
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/tiji/4423725160

什么是Mybatis?​​

MyBatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态SQL,可以严格控制SQL执行性能,灵活度高。

MyBatis 提供了与数据库交互的API,让程序员可以通过简单的XML或注解来配置和映射原生信息,将接口和Java的实体类映射到数据库中的记录,免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

Mybaits的优缺点?

优点

MyBatis 的核心优势在于其简单性和灵活性。其优点如下:

  1. 精细的SQL控制:MyBatis 允许我们手写SQL,因此可以进行精细的查询优化,充分利用数据库本身的特性和优化。
  2. 灵活性高:支持动态SQL,便于构建复杂查询。我们可以根据不同的条件动态地改变SQL语句,而无需改变Java代码结构。
  3. 易于理解和学习:MyBatis 的配置和使用相对简单直观,对于熟悉SQL的初学者来说是非常容易上手的。
  4. 消耗资源小:没有完整ORM框架那样的数据持久层自动化管理的开销,MyBatis 的资源消耗相对较小。
  5. 良好的集成性:可以很容易地与其他框架(如Spring)集成,同时社区和第三方工具支持良好。

缺点

  1. SQL管理:随着项目的扩展,SQL语句可能会变得难以管理,尤其是当这些SQL语句分散在大量的XML文件中时。
  2. 数据库移植性:因为需要手写SQL,所以在不同的数据库之间迁移可能需要重新编写特定的SQL语句,降低了代码的移植性。
  3. 部分自动化缺失:与完整的ORM工具相比,缺乏自动的CRUD操作、对象关系维护。
  4. 复杂性管理:对于有很多关联关系和继承映射的复杂领域模型,MyBatis的处理不如完整的ORM框架方便。
  5. 二次开发需求:为了满足特定需求,如分页、性能优化等,需要进行二次开发和额外配置。
  6. 维护性挑战:由于SQL和Java代码的分离,大型项目中的跨团队开发和维护可能会带来沟通和协作的挑战。
  7. 缓存支持有限: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语句,最终处理结果集并返回结果。这个过程涉及到详细的配置和多个组件之间的相互作用。具体如下:

  1. 配置解析
    • MyBatis启动时会加载配置文件(如mybatis-config.xml)和映射文件(Mapper XML),解析后创建Configuration(配置对象)。
  2. SqlSessionFactory构建
    • 通过配置对象,MyBatis构建SqlSessionFactory,此工厂对象为应用程序提供SqlSession
  3. SqlSession交互
    • SqlSession提供了执行SQL命令的所有方法。我们通过SqlSession来执行配置文件或注解中定义的SQL语句。
  4. 执行器(Executor)
    • Executor是MyBatis的一个核心组件,用于执行更新、查询、延迟加载等操作。
    • MyBatis有不同的Executor实现,比如简单执行器(SimpleExecutor)、重用执行器(ReuseExecutor)、批量执行器(BatchExecutor)等。
  5. 语句处理器(Statement Handler)
    • 对于执行的每一个SQL语句,MyBatis会创建一个Statement Handler。它使用ParameterHandler来处理SQL参数,并使用ResultSetHandler处理查询返回的结果集。
  6. 参数映射
    • SQL执行时,MyBatis会对SQL语句中的#{}占位符参数进行映射处理,替换为实际的参数值,并能处理参数类型转换。
  7. SQL执行与结果处理
    • 执行SQL语句后,MyBatis会使用ResultSetHandler将JDBC的ResultSet转换为Java对象或对象集合。
  8. 映射器(Mapper)
    • Mapper接口是用户自定义的DAO接口,MyBatis会为这些接口生成代理对象,代理对象内部调用MyBatis的核心代码执行。
    • 映射器XML文件定义了SQL语句与接口方法的映射关系。

Dao接口的工作原理是什么?

MyBatis中的DAO(Data Access Object)接口是用户定义的一个接口,它包含了访问数据库所需要的方法定义。这些方法对应于映射文件中定义的SQL语句。MyBatis框架使用这个接口和映射文件来在运行时动态创建DAO的实现,这个实现是一个Mapper代理对象。工作原理如下:

  1. 接口和映射文件
    • 写一个DAO接口,并定义了相关的方法。同时,为这些方法提供了SQL映射文件中的映射语句。
  2. SqlSession获取Mapper
    • 通过SqlSessiongetMapper()方法,MyBatis会为DAO接口生成一个动态代理对象(Mapper代理)。这个代理对象知道如何将接口方法调用转换成对映射文件中定义的SQL语句的执行。
  3. 方法调用转换
    • 当调用DAO接口的方法时,实际上是在调用代理对象的对应方法。这个代理对象会拦截这个调用,确定要执行的SQL语句。
  4. SQL语句的执行
    • 代理对象找到映射文件中对应的SQL语句,并通过MyBatis底层的执行器(Executor)执行这个语句。如果是查询操作,它还负责将结果映射成Java对象或对象集合。
  5. 结果返回
    • 执行完成后,方法的结果会被返回给调用者。如果方法是一个查询,MyBatis会将结果集映射为Java对象或者对象集合返回;如果方法是更新或删除,它会返回一个表示受影响行数的整数。
  6. SqlSession管理
    • SqlSession的生命周期应该由调用者来控制,通常在方法调用结束之后关闭SqlSession以释放资源。

Dao接口里的方法,参数不同时,方法能重载吗?

在MyBatis的Mapper接口中,方法重载通常是不被支持的。

这是因为MyBatis在构建时需要根据方法的名称来找到对应的SQL语句。如果Mapper接口中有多个同名的方法(即使它们的参数不同),MyBatis就无法确定应该使用哪个SQL语句。所以在 DAO 接口中我们要保证方法名的唯一性。

什么是MyBatis的接口绑定?有哪些实现方式?

MyBatis的接口绑定指的是将Mapper接口与一个XML文件或注解关联起来的过程,这样MyBatis就能通过接口直接调用XML映射文件中的SQL语句。

MyBatis提供了两种实现方式:

  1. XML映射文件: 这是最传统的方式。创建一个Mapper接口,然后提供一个同名的XML映射文件,文件名必须与接口的名称相同,并且放在与接口相同的包结构下。MyBatis在启动时会读取这些文件,然后根据这些映射文件中的namespace与接口进行绑定。接口中的方法名应该与映射文件中定义的statement的id相匹配。
  2. 注解: 注解方式是一种更加简洁的配置方法。可以直接在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>

fieldName1fieldName2 都是 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 对象的数据。

慎用延迟加载!!! 慎用延迟加载!!! 慎用延迟加载!!!

如何获取生成的主键?

可以使用 useGeneratedKeyskeyProperty 属性来完成。

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 中的 switchif-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 关键字,并且如果片段开始于 ANDOR,它会将它们去除,防止逻辑错误。

<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 的核心功能。

一、运行原理

  1. 动态代理:MyBatis 使用 Java 的动态代理来创建代理对象,代理 Mapper 接口的方法调用、Statement 的创建、查询和结果的处理等。
  2. 拦截点:MyBatis 允许插件拦截四种类型的方法:
    • Executor(更新、查询、提交、回滚等操作)
    • ParameterHandler(设置 SQL 参数)
    • ResultSetHandler(处理结果集)
    • StatementHandler(准备和执行 SQL 语句)
  3. 插件配置:通过在 MyBatis 配置文件中配置插件,可以指定插件拦截哪些方法。
  4. 拦截处理:当拦截到某个方法时,插件可以执行额外的处理,如日志记录、性能监控等。

二、编写一个插件

  • 实现 Interceptor 接口

创建一个类实现 MyBatis 的 Interceptor 接口,并实现其中的 intercept 方法。

  • 使用 @Intercepts 注解

使用 @Intercepts 注解标注你的插件类,指定要拦截的方法。在注解中,你可以使用 @Signature 来定义拦截的接口、方法以及参数类型

  • 插件逻辑

intercept 方法中编写你的插件逻辑。你可以在方法执行前后添加自定义逻辑。

  • 配置 MyBatis

在 MyBatis 的配置文件中添加你的插件,这样 MyBatis 就会加载并应用该插件。

MyBatis是如何防止SQL注入的?

MyBatis 防止 SQL 注入的主要机制是使用预处理语句(Prepared Statements),这是一种在数据库层面就内置了防止 SQL 注入的特性的数据库功能。详情如下:

  1. 参数化查询
    • MyBatis 使用参数化的 SQL 语句,也就是说,它不是直接在 SQL 语句中拼接参数,而是使用问号(?)作为参数的占位符。
    • 当执行 SQL 时,这些占位符会被实际的参数值替换,但这个替换过程是由数据库驱动处理的,不是简单的字符串替换。
  2. 安全的参数处理
    • 在数据库驱动层面,参数值会被正确地转义,确保它们不会被解释为 SQL 代码的一部分,这样就避免了 SQL 注入攻击。
    • 例如,如果用户的输入包含 SQL 语句的一部分(比如 '; DROP TABLE users; --),这个输入会被数据库驱动转义,从而在最终执行的 SQL 语句中失去作为代码的功能。
阅读全文