ChatGPT 整理的“MyBatis工作原理面试剖析:深度解读“

user:
1136. MyBatis 的核心原理,使用 MyBatis 与其他 ORM 框架对比,有哪些优点和缺点


assistant:
MyBatis 的核心原理是将 SQL 语句和 Java 对象映射,实现对象关系映射(ORM)。与其他 ORM 框架相比,MyBatis 的优点和缺点如下:

优点:

  1. 灵活性高:MyBatis 不强制采用任何特定的数据模型,使用者可以自由地编写 SQL 语句,灵活性非常高,可以更好地满足复杂的业务需求。
  2. 优化能力强:MyBatis 的底层是基于 JDBC 的,使用者可以通知数据库执行优化查询,提高 SQL 执行效率,对复杂查询的优化支持也很好。
  3. 易于集成:MyBatis 可以与许多流行的数据源一起使用,并与 Spring、Spring Boot 等框架无缝集成。
  4. 简单易用:MyBatis 的基本概念和使用方式非常简单易懂。因为 MyBatis 是组成 SQL 语句的纯 Java 对象,因此 Java 开发人员可以轻松理解它的工作原理。

缺点:

  1. 编写 SQL 语句需要一定的 SQL 编写经验,需要具备 SQL 的基础知识。
  2. 需要手动管理事务。
  3. 对于简单的 CRUD 操作,MyBatis 的代码可能会变得冗长,会对代码的可维护性造成影响。

user:
1137. MyBatis 与 Hibernate 有什么异同?


assistant:
MyBatis与Hibernate是两种不同的ORM框架,它们之间有以下异同点:

异同点:

  1. 对于SQL操作的支持程度不同。MyBatis更适合于开发者对SQL的控制程度更高的单一项目,Hibernate更加适合处理面向对象范畴内的复杂关系映射。

  2. 配置方式不同。MyBatis是基于XML和注解来配置SQL语句的;而Hibernate则是通过对象关系映射文件(hbm.xml)来进行配置。

  3. 关于缓存的处理方式不同。Hibernate默认提供了二级缓存,这种缓存方式的实现是基于JVM内存中的数据结构,缓存更新比较及时但是缓存命中率没有那么高。MyBatis提供的缓存种类比Hibernate少,它的一级缓存是基于SqlSession实例的本地缓存,二级缓存支持Ehcache等第三方缓存,这种缓存方式的命中率比较高,但由于多个线程共同使用,需要考虑并发访问的问题。

  4. 对于SQL调优的难易程度不同。对于MyBatis来说,你必须非常了解SQL语句和SQL调优,这需要你有丰富的经验和技巧;而Hibernate对于开发者来说,调优相对容易,Hibernate更依赖于底层数据库系统的优化和调优。

  5. 编程方式不同。MyBatis以SQL语句为中心,优先考虑解决SQL问题;Hibernate以面向对象编程为中心,提供了对象持久化的解决方案,解决了面向对象模型很难映射到关系型数据库的问题。

总结:

MyBatis和Hibernate之间的差异并不是那么明显,这要是取决于开发者在应用中的具体要求。开发者可以根据自己的需求,结合框架的特性来选择自己熟悉的框架,以此来更高效地处理好SQL操作和面向对象范畴内的关系映射。


user:
1139. MyBatis 中有哪些常用的查询方法?


assistant:
MyBatis 中常用的查询方法包括以下几种:

  1. 根据主键查询:通过selectByPrimaryKey方法,根据表的主键查询数据。

  2. 条件查询:

  • selectByExample方法:通过 Example 对象进行条件查询,Example 对象中包含查询条件和排序规则,可以对复杂业务场景进行支持。
  • selectByMap方法:通过 Map 对象进行条件查询,Map 对象中包含查询条件和排序规则,可以灵活组合各种查询条件。
  • selectOne方法:通过传入的参数进行条件查询,如果查询结果超过一条,则会抛出异常。
  1. 分页查询:
  • selectByExampleWithRowbounds方法:使用 RowBounds 对象进行分页查询,该方法支持的数据库较多,且返回的是 List 集合,适合数据量小的场景。
  • selectPage方法:使用 PageHelper 插件进行分页查询,该方法返回 PageInfo 对象,该对象包含了分页的必要信息,适合数据量较大的场景。
  1. 通用查询:
  • selectAll方法:查询表中所有数据;
  • selectDistinct方法:查询去重后的结果集;
  • selectColumn方法:查询指定列的结果集;
  • selectCount方法:查询表中数据的数量。

总之,在 MyBatis 中,具体使用哪种查询方法要根据具体的业务场景和查询需求来决定。


user:
1141. 什么是 MyBatis 命名空间?


assistant:
MyBatis 命名空间指的是使用 XML 文件配置 MyBatis 的 mapper 时,用于定义 SQL 语句和映射关系的作用域。

在一个 XML mapper 文件中,可以使用一个命名空间来组织多个 SQL 语句和映射关系。命名空间的作用如下:

  1. 区分不同的 mapper 文件:如果多个 mapper 文件中存在同名的 SQL 语句或映射,通过指定不同的命名空间就可以区分它们,避免冲突。

  2. 方便管理和维护:在一个 mapper 文件中,通过定义不同的命名空间,可以将不同的 SQL 语句和映射关系进行分类和组织,方便管理和维护。

在 MyBatis 的 mapper 文件中,可以通过 namespace 属性来定义命名空间,例如:

<mapper namespace="com.example.userDao">
    ...
mapper>

其中 com.example.userDao 就是命名空间。在一个 mapper 文件中只能指定一个命名空间。可以通过命名空间来引用其中定义的 SQL 语句和映射关系,例如:

<select id="getUserById" resultMap="userResultMap" parameterType="int">
    SELECT * FROM user WHERE id = #{id} 
select>

其中的 getUserById 就是 SQL 语句的 ID,可以通过 namespace.SQL_id 的方式来引用这个 SQL 语句,例如:

<insert id="addUser" parameterType="com.example.User">
    SELECT * FROM #{user} WHERE name = #{name}
insert>


<select id="getUserByName" resultMap="userResultMap" parameterType="String">
    
    ${namespace}.addUser
select>

user:

  1. MyBatis 中如何进行 Mapper 的动态代理?

assistant:
MyBatis 中使用 Mapper 接口与映射 XML 文件进行交互。实际上,MyBatis 通过动态代理技术为每个 Mapper 接口生成一个代理对象。这个代理对象实现了 Mapper 接口,并拦截了接口方法的调用,在方法调用前后执行相应的操作。

在 MyBatis 中,Mapper 接口通过 MapperRegistry 注册到 Configuration 中,并且为每个 Mapper 接口生成对应的代理对象。这个代理对象实现了 Mapper 接口定义的所有方法,并且在方法调用前后自动执行相应的操作,例如打开和关闭数据库连接、执行 SQL 语句等。

MyBatis 动态生成 Mapper 代理对象的具体实现是通过 MapperProxyFactory 完成的。在生成代理对象时,会先创建一个 MapperMethod 对象,它包含了映射 XML 文件中对应 SQL 语句的 ID 和参数类型等信息。生成代理对象时,会将该对象传入代理对象的构造函数中。当调用 Mapper 接口中的方法时,代理对象会将方法名与 MapperMethod 对象中存储的参数进行匹配,并最终执行对应的 SQL 语句。

例如,假设有一个 UserMapper 接口,其中定义了如下方法:

public interface UserMapper {
    User getUserById(int id);
}

MyBatis 会生成一个实现该接口的代理对象,它的实现代码大概如下:

public class UserMapperProxy implements UserMapper {
    private SqlSession sqlSession;

    public UserMapperProxy(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    public User getUserById(int id) {
        return sqlSession.selectOne("getUserById", id);
    }
}

在项目中使用该接口时,会通过 SqlSession.getMapper 方法获取该接口的代理对象。例如:

SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);

在这里,首先使用 SqlSession.openSession 方法打开一个新的数据库会话,然后通过 SqlSession.getMapper 方法获取该接口的代理对象,并调用它的方法。MyBatis 会自动处理数据库连接的打开和关闭等操作。


user:
1144. MyBatis 输入参数#{}中与${}有什么不同,分别代表什么含义?


assistant:
MyBatis 中,#{}${}都是用来表示 SQL 中的占位符,用于向 SQL 语句中动态传递参数。它们在功能上有所区别,#{}主要应用于 PreparedStatement 中的参数绑定,而${}则主要用于字符串替换。具体来说,它们的含义如下:

  1. #{}

#{}表示 SQL 中的占位符,可以在其中指定参数名称,并在 MyBatis 中通过名称传递参数。例如:

SELECT * FROM users WHERE id = #{id}

在上面的 SQL 语句中,#{id}是一个动态参数,MyBatis 会在执行 SQL 语句前,将该参数绑定到 PreparedStatement 对象上。这样做可以有效地防止 SQL 注入攻击,并提高 SQL 的可读性和可维护性。

#{}还支持一些特殊的类型处理器,例如:MyBatis 内置的 TypeHandler、自定义的 TypeHandler 等。通过这些类型处理器,可以在处理数据时,自动将 Java 类型和数据库类型进行转换。例如,下面的语句就使用了一个自定义的类型处理器:

INSERT INTO users (id, name, birthday)
VALUES (#{id}, #{name}, #{birthday, typeHandler=LocalDateTypeHandler})

在上面的语句中,#{birthday, typeHandler=LocalDateTypeHandler}表示使用自定义的类型处理器 LocalDateTypeHandler 来处理参数 birthday

  1. ${}

${}表示 SQL 中的字符串替换,可以在其中指定参数名称,并在 MyBatis 中通过名称传递参数。例如:

SELECT * FROM users WHERE name = '${name}'

在上面的 SQL 语句中,${name}是一个动态参数,MyBatis 会将它替换为对应的参数值,并将 SQL 语句发送给数据库。与 #{} 不同,${} 不会将参数值进行预编译,也不会提供任何防止 SQL 注入的保护机制。因此,使用 ${} 构造 SQL 语句时,应当对参数进行安全检查,以防止 SQL 注入攻击和其他安全风险。

另外,需要注意的是,在使用 ${} 时,要特别注意参数值中是否包含特殊字符(例如:引号、斜杆、分号、换行符等),如果包含这些字符,可能会导致语法错误或者 SQL 注入攻击。


user:
1145. MyBatis 中如何定义别名查询?


assistant:
MyBatis 中可以使用标签来实现别名查询,通过为查询的字段取别名,可以使得查询结果集中的字段名与数据库表的字段名可以不同,这样做可以增强查询结果的可读性和可维护性。

下面是一个示例,假设有一个表 users,包含 idusernameage 三个字段。现在需要查询其中所有年龄小于 30 岁的用户,并将结果中的 id 字段别名为 userId

<select id="selectUsers" resultMap="userResultMap">
  SELECT id AS userId, username, age FROM users WHERE age < 30
select>

<resultMap id="userResultMap" type="User">
  <result property="id" column="userId"/>
  <result property="username" column="username"/>
  <result property="age" column="age"/>
resultMap>

在上面的代码中,SELECT 语句中使用了 AS 关键字为 id 字段定义了别名 userId,从而在查询结果集中,id 字段将以 userId 的名称出现。

标签定义了一个名称为 userResultMap 的结果集映射,它将查询结果集中的每个字段与对应的 Java 类型属性进行了映射。在 result 标签中,property 属性指定了 Java 类型中的属性名称,column 属性指定了查询结果集中的字段名称。这样做可以使得查询结果集的字段名与 Java 类型属性名称一一对应,从而简化代码编写。

在代码中,查询语句和结果集映射都分别定义在 元素通过 resultMap 属性引用来使用。

  • type 属性定义了结果映射对象的类名。
  • property 属性定义了 Java 对象中的属性名称。
  • column 属性定义了 SQL 查询结果集中的列名称。
  • 在查询 SQL 语句中使用 resultMap

    <select id="selectUser" resultMap="userResultMap">
      select user_id, user_name, user_age from users where user_id = #{userId,jdbcType=INTEGER}
    select>
    

    在这个例子中,select 元素中的 resultMap 属性指定了这个查询结果集映射所使用的 resultMap 的 ID,映射关系会被 MyBatis 底层的映射器使用来转换这个查询的结果集。这样,查询的结果集中每列的别名和 resultMapproperty 属性名一致时,MyBatis 就可以自动映射这个查询结果为一个 User 对象,而不需要任何额外的配置。


    user:
    1147. MyBatis 的结果集 resultMap 可以定义哪些类型?


    assistant:
    MyBatis 的结果集 resultMap 可以定义以下类型:

    1. 基本类型:支持包括 Java 基本数据类型及其包装类、String 以及 java.math.BigDecimal 等类型。

    2. JavaBean 对象:支持普通的 JavaBean 对象,可以将 SQL 查询的结果集映射到 JavaBean 的属性上。

    3. 嵌套类型:支持将 SQL 查询的结果集映射到复杂的嵌套类型结构上。

    4. 枚举类型:支持将 SQL 查询的结果集映射到 Java 枚举类型上。

    5. 集合类型:支持将 SQL 查询的结果集映射到 Java 集合类型上,如 List、Set、Array 等。

    6. 关联查询:支持通过嵌套查询方式将关联查询的结果映射到 Java 对象上。

    resultMap 中,要使用 标签指定结果集每列和 Java 对象属性的映射关系。不同的类型在 标签中的配置也是不同的,常用的几种 配置方式如下:

    • propertycolumn:将查询结果集中的一列映射到 Java 对象的一个属性。

    • association:将查询结果集中的一些列,映射到 Java 对象的一个复杂类型属性上,也就是将一个 JavaBean 映射到另一个 JavaBean 中。

    • collection:将查询结果集中的一些列,映射到 Java 对象的一个集合属性上,也就是将一组 JavaBean 映射到 Java 的 List、Set、Array 等类型上。

    • discriminator:将查询结果集中的一列作为 Java 对象类型的判断依据,然后根据条件映射到不同的 JavaBean 类型上。

    需要注意的是,resultMap 的配置方式非常灵活,可以根据实际业务需求进行定制。在实际开发中,一般需要根据数据库设计和应用功能实现的需要定义多个 resultMap,并在 实际上是在这些元素中使用的,因此传递参数的方式与这些元素相同。

    元素中,可以使用 selectresultMap 属性来引用其他查询。如果要向这些查询传递参数,可以在其中使用 include 元素,并将参数传递给 include 元素。

    例如,我们有以下示例的数据表:

    CREATE TABLE orders (
        id INT PRIMARY KEY,
        customer_id INT,
        order_date DATE
    );
    
    CREATE TABLE customers (
        id INT PRIMARY KEY,
        name VARCHAR(50)
    );
    
    CREATE TABLE items (
        id INT PRIMARY KEY,
        order_id INT,
        product_id INT,
        quantity INT,
        FOREIGN KEY (order_id) REFERENCES orders(id)
    );
    

    我们希望将一张订单表的数据 orders 与与之对应的客户表 customers 进行关联,并且查询出订单项 items 的信息,最后将这些数据映射为 Java 中的一个嵌套对象 Order。具体的实现如下:

    
    <resultMap id="OrderMap" type="Order">
        <id property="id" column="id" />
        <result property="orderDate" column="order_date" />
        <association property="customer" javaType="Customer">
            <id property="id" column="customer_id" />
            <result property="name" column="name" />
        association>
        <collection property="items" ofType="Item">
            <id property="id" column="id" />
            <result property="productId" column="product_id" />
            <result property="quantity" column="quantity" />
            
            <association property="product" javaType="Product"
                         select="com.example.mapper.ProductMapper.selectById"
                         resultMap="ProductMap">
                <include refid="productId"/>
            association>
         collection>
    resultMap>
    
    
    <select id="selectById" resultMap="OrderMap">
        SELECT o.id, o.order_date, o.customer_id, c.name, i.id, i.product_id, i.quantity
        FROM orders o
        JOIN customers c ON o.customer_id = c.id
        JOIN items i ON o.id = i.order_id
        WHERE o.id = #{id}
    select>
    

    items 元素中,我们通过 select 属性引用了 ProductMapper 接口中的 selectById 方法,并使用 元素传递了 productId 参数,即使用 include 引用了 item 对象中 productId 属性的值。在 ProductMapper 中需要定义 selectById 方法,并在其中通过 #{id} 引用传递过来的参数:

    <select id="selectById" resultType="Product">
        SELECT * FROM products WHERE id = #{id}
    select>
    

    通过这种方式,我们就可以实现在 中调用