目录
6. Mybatis是如何将sql执行结果封装为目标对象并返回的?
8. 如何执行批量插入(SqlSessionFactory)
12. Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
MyBatis
是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通的 Java 对象)为数据库中的记录。注:喜欢的朋友可以关注公众号“JAVA学习课堂”方便阅读,内容同步更新。
1. 什么是Mybatis?
Mybatis是持久层框架,半自动ORM框架,对JDBC操作数据库的过程进行了封装,使得开发者只需要关注sql本身。
JDBC操作数据库:加载JDBC驱动程序、提供JDBC连接的URL 、创建数据库的连接connection、创建一个Statement、执行SQL语句、处理结果、关闭JDBC对象
2. 说说Mybatis的优缺点
MyBatis 作为一个优秀的持久层框架,具有许多优点,但同时也存在一些潜在的缺点。以下是 MyBatis 的主要优缺点概述:
优点
-
灵活性:MyBatis 提供了非常灵活的方式来映射 SQL 语句到 Java 对象。通过 XML 配置文件或注解,你可以精确地控制 SQL 语句的生成,这使得在复杂或特定需求的场景中能够编写出精确的 SQL 语句。
-
避免 JDBC 编码:MyBatis 封装了 JDBC 的底层实现,开发者无需再编写大量的 JDBC 代码,如设置参数、处理结果集等,这大大减少了代码量,提高了开发效率。
-
动态 SQL:MyBatis 提供了强大的动态 SQL 功能,允许你根据参数的不同动态地生成 SQL 语句。这使得在构建复杂查询时更加灵活和方便。
-
解耦:MyBatis 将 SQL 语句从 Java 代码中分离出来,放在 XML 配置文件中,这使得代码更加清晰、易于维护。同时,这也使得数据库层的修改可以独立于应用层进行。
-
插件机制:MyBatis 提供了插件机制,允许你编写自定义的插件来拦截和修改 SQL 语句的执行过程。这为扩展 MyBatis 的功能提供了很大的便利。
-
良好的性能:MyBatis 在处理大量数据时仍然能够保持较高的性能。它提供了缓存机制来缓存查询结果,避免重复执行相同的 SQL 语句。
缺点
-
SQL 语句编写:虽然 MyBatis 提供了灵活的方式来映射 SQL 语句,但这也要求开发者具备一定的 SQL 编写能力。如果 SQL 语句编写不当,可能会影响性能或引发错误。
-
XML 配置文件:MyBatis 的配置主要基于 XML 文件,这增加了配置文件的复杂性和维护成本。对于不熟悉 XML 的开发者来说,可能会感到有些困难。
-
错误处理:MyBatis 没有提供完整的错误处理机制。当 SQL 语句执行出错时,通常需要开发者自行处理错误并给出相应的提示。
-
学习曲线:对于初学者来说,MyBatis 的学习曲线可能比较陡峭。需要了解 MyBatis 的核心概念、配置方式、映射规则等。
-
与 ORM 框架的对比:与一些 ORM(对象关系映射)框架相比,MyBatis 需要手动编写 SQL 语句,这可能会增加一些工作量。ORM 框架通常能够自动处理数据库表与 Java 对象之间的映射关系,但在某些复杂场景下可能不够灵活。
-
依赖数据库:MyBatis 的 SQL 语句是针对特定数据库的,如果更换数据库,可能需要修改大量的 SQL 语句和映射配置。这增加了迁移数据库的难度。
3. Xml映射文件中,都有哪些标签
-
<mapper>
:这是 XML 映射文件的根元素。它通常包含一个namespace
属性,该属性指向映射文件对应的接口全限定路径。 -
<select>
:用于映射查询操作,包含 SELECT SQL 语句。 -
<insert>
:映射插入操作,包含 INSERT SQL 语句。 -
<update>
:映射更新操作,包含 UPDATE SQL 语句。 -
<delete>
:映射删除操作,包含 DELETE SQL 语句。 -
<resultMap>
:定义结果集映射规则,将数据库查询结果与 Java 对象属性进行映射。它通常包含<id>
、<result>
、<association>
、<collection>
等子标签。 -
<sql>
:定义可重用的 SQL 片段,可以被其他语句引用。然后通过<include>
标签引入这个 SQL 片段。 -
<include>
:用于引用已定义的 SQL 片段。 -
<selectKey>
:用于在插入操作后获取生成的主键值,通常用于不支持自增主键的数据库。 -
动态 SQL 标签:这些标签用于构建动态 SQL 语句,可以根据条件参数动态生成不同的 SQL。包括
<if>
、<choose>
、<when>
、<otherwise>
、<where>
、<set>
、<foreach>
等。<if>
:根据条件包含或排除 SQL 片段。<choose>
、<when>
、<otherwise>
:类似于 Java 中的 switch-case 结构,根据条件选择执行不同的 SQL 片段。<where>
:用于简化 WHERE 子句的构建,自动去除多余的“AND”或“OR”关键字。<set>
:用于更新语句中动态包含需要更新的字段,同样可以自动去除多余的逗号。<foreach>
:用于遍历集合或数组,并构建 SQL 语句的 IN 条件等。
-
<cache>
:用于配置二级缓存。
4. #{}和&{}有什么区别
- #{}是预编译处理,${}是字符串替换
- 处理#{}时,会将sql中的#{}替换为?号,调用预编译PreparedStatement 的set方法来赋值
- 处理时${}时,就是把{}替换成变量的值
- #{}可以防止 SQL 注入,安全
5. Mybatis是如何进行分页的,分页插件的原理是什么
MyBatis 提供了两种主要的分页方式:逻辑分页和物理分页。但通常我们讨论的是物理分页,因为它更加高效。
MyBatis 的物理分页
物理分页是通过在 SQL 语句中添加 LIMIT
和 OFFSET
子句来实现的。MyBatis 并没有直接提供物理分页的内置支持,但我们可以使用 MyBatis 的插件机制来扩展其功能,实现物理分页。
分页插件的原理
分页插件(如 PageHelper)的原理主要基于 MyBatis 的拦截器(Interceptor)机制。以下是分页插件的基本工作原理:
- 配置插件:在 MyBatis 的配置文件(如
mybatis-config.xml
)中配置分页插件,并设置相关参数,如数据库方言、分页参数等。 - 拦截 SQL 语句:当 MyBatis 执行查询操作时,分页插件会拦截 SQL 语句。这通常是通过实现 MyBatis 的
Interceptor
接口,并在intercept
方法中完成拦截的。 - 修改 SQL 语句:分页插件会分析拦截到的 SQL 语句,并根据分页参数(如当前页码、每页显示数量)来修改 SQL 语句。具体来说,插件会在 SQL 语句的末尾添加
LIMIT
和OFFSET
子句,以实现分页功能。 - 执行修改后的 SQL 语句:分页插件将修改后的 SQL 语句返回给 MyBatis,然后由 MyBatis 执行该 SQL 语句,并返回结果。
- 处理结果:MyBatis 将查询结果封装成 List 或其他形式,并返回给调用者。分页插件可能还需要对返回的结果进行处理,如计算总页数等。
注意事项
- 性能考虑:在使用物理分页时,应尽量减少每页显示的记录数,以减少数据库查询的压力。
- 插件选择:除了 PageHelper 之外,还有其他一些分页插件可供选择,如 MyBatis-Plus 的内置分页功能等。在选择插件时,应根据项目需求和团队习惯进行选择。
- 配置参数:在使用分页插件时,应仔细配置相关参数,以确保插件能够正常工作。例如,需要正确设置数据库方言、分页参数等。
- 兼容性:不同的分页插件可能具有不同的兼容性和限制。在使用插件之前,应仔细阅读文档并了解插件的兼容性和限制情况。
分页插件步骤:
项目中引入 PageHelper 的依赖。如果你使用 Maven,可以在 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>你的PageHelper版本号</version>
</dependency>
请注意将 你的PageHelper版本号 替换为当前可用的最新版本号。
MyBatis 配置文件(如 mybatis-config.xml
)或者 Spring Boot 项目的配置文件中(如 application.properties
或 application.yml
)不需要特别的配置(如果使用了 Spring Boot starter 的话)。
使用 PageHelper 进行分页的 Java 代码示例:
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class YourService {
@Autowired
private YourMapper yourMapper; // 假设这是你的 MyBatis Mapper 接口
public Page<YourEntity> findYourEntitiesByPage(int pageNum, int pageSize) {
// 设置分页信息,注意这里 pageNum 和 pageSize 的值是从 1 开始的,不是从 0 开始
PageHelper.startPage(pageNum, pageSize);
// 接下来执行的查询会进行分页,注意这里的查询语句会被 PageHelper 拦截并处理
List<YourEntity> list = yourMapper.selectYourEntities();
// 使用 Page 对象包装查询结果,Page 对象包含了当前页数据、总页数、总记录数等信息
Page<YourEntity> page = (Page<YourEntity>) list;
// 返回分页数据
return page;
}
}
6. Mybatis是如何将sql执行结果封装为目标对象并返回的?
1. 使用resultMap标签
使用resultMap标签,做java对象名和数据库列名映射
2. 数据库别名
使用数据库别名
7. Mybatis是怎么将mapper与xml联系起来的
MyBatis 将 Mapper 接口与 XML 映射文件联系起来的原理主要依赖于 JDK 动态代理技术和 MyBatis 的内部实现机制。以下是具体的过程:
- Mapper 接口定义:首先,你需要定义一个 Mapper 接口,这个接口中包含了与数据库操作相关的方法。
- XML 映射文件:然后,你需要编写一个与 Mapper 接口对应的 XML 映射文件(通常以
.xml
为后缀)。这个文件包含了具体的 SQL 语句、参数类型、结果映射等信息。在 XML 文件中,<mapper>
标签的namespace
属性通常设置为 Mapper 接口的全限定名,这样 MyBatis 就能知道这个 XML 文件是为哪个 Mapper 接口服务的。 - Mapper 代理类的生成:当 MyBatis 启动或 Mapper 接口被加载时,MyBatis 会使用 JDK 动态代理技术为这个 Mapper 接口生成一个代理类(通常是 MapperProxy)。这个代理类实现了 Mapper 接口,并拦截了接口中所有方法的调用。
- 方法调用与 SQL 执行:当应用程序调用 Mapper 接口中的方法时,实际上是在调用 Mapper 代理类的方法。Mapper 代理类会拦截这个方法的调用,并根据方法的名称和参数信息,在 XML 映射文件中找到对应的 SQL 语句。然后,MyBatis 会创建一个 SqlSession 对象,并将方法名和参数传递给 SqlSession 对象。SqlSession 对象会根据 XML 映射文件中的配置,解析 SQL 语句,设置参数,并执行 SQL 语句。
- 结果处理:执行完 SQL 语句后,SqlSession 对象会将查询结果返回给 Mapper 代理类。Mapper 代理类会根据 XML 映射文件中的结果映射规则(如
<resultMap>
标签),将查询结果转换成 Java 对象,并返回给应用程序。
8. 如何执行批量插入(SqlSessionFactory)
在 MyBatis 中执行批量插入通常涉及到 SqlSession
的使用,而不是直接使用 SqlSessionFactory
。SqlSessionFactory
是用于创建 SqlSession
的工厂类,而 SqlSession
是执行 SQL 语句的接口。
以下是如何使用 MyBatis 执行批量插入的步骤:
- 配置 MyBatis:确保你的 MyBatis 配置文件(如
mybatis-config.xml
)和 Mapper XML 文件已经正确配置。 - 获取 SqlSessionFactory:从 MyBatis 配置文件中获取
SqlSessionFactory
。 - 打开 SqlSession:使用
SqlSessionFactory
打开一个新的SqlSession
。 - 执行批量插入:
- 使用 Mapper 接口中的方法执行多次插入(如果 Mapper 方法支持批量插入)。
- 或者,手动构建批量插入的 SQL 语句,并使用
SqlSession
的insert()
方法执行。
- 提交事务:确保在批量插入后提交事务。
- 关闭 SqlSession:在完成所有操作后关闭
SqlSession
。
示例代码:
使用 MyBatis 执行批量插入的示例:
UserMapper.java
public interface UserMapper {
// 通常 Mapper 接口中不会有直接的批量插入方法,但你可以定义一个方法来执行自定义的 SQL
int batchInsertUsers(List<User> users);
}
UserMapper.xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 假设你有一个批量插入的 SQL 语句 -->
<insert id="batchInsertUsers" parameterType="list">
INSERT INTO users (column1, column2, ...)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.property1}, #{user.property2}, ...)
</foreach>
</insert>
</mapper>
执行批量插入
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = ... // 填充用户列表
// 假设 batchInsertUsers 是你的批量插入方法
userMapper.batchInsertUsers(users);
// 提交事务
sqlSession.commit();
}
9. Mybatis通过jdk动态代理的原理
MyBatis 通过 JDK 动态代理实现 Mapper 接口与 SQL 语句之间的关联,其原理步骤:
- Mapper 接口定义:
- 开发者定义 Mapper 接口,该接口中包含了与数据库操作相关的方法。
- XML 映射文件:
- 开发者为每个 Mapper 接口编写对应的 XML 映射文件,文件中定义了 SQL 语句、参数类型、结果映射等信息。
- XML 映射文件的
<mapper>
标签的namespace
属性设置为 Mapper 接口的全限定名,这样 MyBatis 就知道这个 XML 文件是为哪个 Mapper 接口服务的。
- SqlSessionFactory 与 SqlSession:
- MyBatis 通过 SqlSessionFactoryBuilder 加载配置文件(如 mybatis-config.xml),然后创建 SqlSessionFactory。
- SqlSessionFactory 负责创建 SqlSession,SqlSession 是执行 SQL 语句的接口。
- JDK 动态代理:
- 当应用程序通过 SqlSession 调用 Mapper 接口中的方法时,MyBatis 不会直接执行这些方法。
- 相反,MyBatis 使用 JDK 动态代理为 Mapper 接口生成一个代理对象(MapperProxy)。
- 这个代理对象实现了 Mapper 接口,并拦截了接口中所有方法的调用。
- 方法调用与 SQL 执行:
- 当应用程序调用 Mapper 接口中的方法时,实际上是调用了 Mapper 代理对象(MapperProxy)的方法。
- Mapper 代理对象会根据方法的名称和参数信息,在 XML 映射文件中找到对应的 SQL 语句。
- 然后,Mapper 代理对象会创建一个 SqlCommand 对象(包含 SQL 语句、参数等信息),并将其传递给 SqlSession 执行。
- 结果处理:
- SqlSession 执行 SQL 语句后,会将查询结果返回给 Mapper 代理对象。
- Mapper 代理对象会根据 XML 映射文件中的结果映射规则(如
<resultMap>
),将查询结果转换成 Java 对象,并返回给应用程序。
- 事务管理:
- MyBatis 支持事务管理,可以在 SqlSession 级别控制事务的提交和回滚。
- 开发者可以通过 SqlSession 的
commit()
和rollback()
方法来管理事务。
演示如何通过 JDK 动态代理实现 Mapper 接口与 SQL 语句之间的关联:
1. Mapper 接口定义
package com.example.mapper;
import com.example.model.User;
public interface UserMapper {
User selectUserById(int id);
void insertUser(User user);
// ... 其他方法 ...
}
2. XML 映射文件
<?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="com.example.mapper.UserMapper">
<select id="selectUserById" resultType="com.example.model.User">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insertUser" parameterType="com.example.model.User">
INSERT INTO users (name, age) VALUES (#{name}, #{age})
</insert>
<!-- ... 其他 SQL 语句 ... -->
</mapper>
3. MyBatis 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 数据库连接信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
<!-- ... 其他映射文件 ... -->
</mappers>
</configuration>
4. SqlSessionFactory 与 SqlSession
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
public class MyBatisDemo {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
try (Reader reader = Resources.getResourceAsReader(resource)) {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
// ... 使用 sqlSession 调用 Mapper 接口的方法 ...
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
5. JDK 动态代理
在 MyBatis 的内部实现中,当通过 sqlSession.getMapper(UserMapper.class)
获取 Mapper 接口的实例时,MyBatis 会使用 JDK 动态代理为 UserMapper
接口生成一个代理对象。但是,这部分代码是 MyBatis 框架内部实现的,我们不需要直接编写。
6. 方法调用与 SQL 执行
通过 sqlSession.getMapper(UserMapper.class)
获得的代理对象,我们可以像调用普通 Java 方法一样调用 Mapper 接口中的方法,MyBatis 会根据 XML 映射文件中的配置执行相应的 SQL 语句:
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1); // 执行 SQL 查询
10. Mybatis的工作原理(重要)
![](https://img-blog.csdnimg.cn/direct/fd9f237e2fc04333a9831a905d9bed6e.png)
MyBatis 的工作原理可以概括为以下几个重要步骤:
- mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件(或者Java文件配置)来构建SqlSessionFactory(SqlSessionFactory是线程安全的)
- SqlSessionFactory实例通过openSession方法开启一个SqlSession
- 通过SqlSession实例的getMapper方法获得指定的Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession
11. Mybatis实现一对一、一对多有几种方式
- 一对一
- 联合查询:联合查询是几个表联合查询,只查询一次,通过在 resultMap 里面配置 association 节点配置一对一的类就可以完成
- 嵌套查询:先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是
- 通过 association 配置,但另外一个表的查询通过 select 属性配置
- 一对多
- 联合查询:联合查询是几个表联合查询,只查询一次,通过在 resultMap 里面配置 collection 节点配置一对一的类就可以完成
- 嵌套查询:先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是
- 通过 collection 配置,但另外一个表的查询通过 select 属性配置
12. Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
MyBatis支持延迟加载。
延迟加载(Lazy Loading)是一种加载数据的策略,它只在真正需要数据时才进行加载,从而有助于提高系统性能和减少资源消耗。在MyBatis中,延迟加载的实现原理如下:
- 配置延迟加载:在MyBatis的配置文件或映射文件中,需要设置
lazyLoadingEnabled=true
来开启延迟加载功能。 - 生成代理对象:当主对象(如一个实体对象)被查询时,MyBatis会生成一个代理对象。这个代理对象包含了对关联对象的引用,但并不会立即加载关联对象的数据。
- 触发时机:当应用程序需要访问代理对象中的关联属性时,延迟加载机制会被触发。此时,MyBatis会创建一个新的SQL语句来查询关联对象的数据。
- 填充数据:查询完成后,MyBatis会将查询到的数据填充到代理对象中,使得关联属性变得可用。
MyBatis的延迟加载主要有两种类型:
- 全局延迟加载(全局懒加载):在MyBatis的配置文件中配置开启全局延迟加载。这样,对于所有的关联关系,都会按照配置进行延迟加载。
- 按需延迟加载(按需懒加载):在映射文件中使用
fetchType
属性设置按需延迟加载。这样,只有在需要的时候才会进行延迟加载。
通过配置全局延迟加载或按需延迟加载,可以根据实际业务需求灵活地使用延迟加载功能,从而提高系统性能。
13. 说说Mybatis的缓存机制
1. 一级缓存:基础 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,Session 中的所有 Cache 就将清空
2. 二级缓存其存储作用域为 Mapper(Namespace),使用二级缓存属性类需要实现Serializable 序列化接口
3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存 Namespaces)的进行了 C(增加)/U(更新)/D(删除)操作后,默认该作用域下所有 select 中的缓存将被 clear.需要在 setting 全局参数中配置开启二级缓存,如下 conf.xml 配置: