文章目录
- [Mybatis 框架](#Mybatis 框架)
- SQL执行过程
- 扩展
-
- [#与的区别](#与的区别)
-
- [- $ 符号](#- $ 符号)
- [- # 符号](# 符号)
- 总结示例
- [Mybatis SQL分类](#Mybatis SQL分类)
-
- [- 动态 SQL](#- 动态 SQL)
- [- 静态 SQL](#- 静态 SQL)
- 静态SQL和动态SQL选择
- ${}、#{}与SQL是否静态SQL、动态SQL无直接关系
Mybatis 框架
SQL执行过程
数据库操作映射方式
MyBatis支持两种方式进行数据库操作映射:
- 映射文件:通过XML文件来定义SQL语句和映射关系
- 注解方式:通过在Java代码中使用注解来定义SQL语句和映射关系
这两种方式都可以实现数据库操作的映射,具体使用哪种方式取决于个人的喜好和项目需求。本文都以映射文件的方式进行分析。
SQL的执行过程
-
SQL解析
MyBatis 会将SQL 语句解析为内部的数据结构。这一过程中,MyBatis 会根据 SQL 中的参数类型、数量等信息,生成一个 MappedStatement 对象,表示当前的 SQL 语句及其相关的执行元数据。
-
参数绑定
在 SQL 解析完成后,MyBatis 会在 SQL 参数映射阶段,将 SQL 语句中的参数与 Java 对象进行映射,并将参数值处理成符合数据库预编译要求的形式。这样,在 SQL 预编译和执行阶段,就可以正确地传递参数并执行 SQL 语句。
-
SQL预编译
MyBatis 利用 JDBC 的 PreparedStatement 接口进行 SQL 语句的预编译。预编译过程会将 SQL 中的占位符(?)替换为实际参数,并将其转化为数据库可以优化执行的形式,从而提高 SQL 执行效率。
-
执SQL行
完成预编译后,MyBatis 会通过 JDBC API 将 SQL 语句提交给数据库执行,并获取查询结果或执行结果。数据库执行完成后,结果会返回给 MyBatis 进行进一步处理。
-
结果映射
MyBatis 会将数据库返回的结果集转换为相应的 Java 对象。这一过程包括类型转换、字段匹配等,通常通过 TypeHandler 来完成。TypeHandler 负责将数据库中的列值与 Java 对象的属性进行映射。
-
事务处理
如果启用了手动事务管理,MyBatis 会在 SQL 执行完成后,根据配置的事务管理策略,提交或回滚事务,确保数据一致性和事务的完整性。
-
缓存处理
若启用了缓存,MyBatis 会将查询结果存储到缓存中,以便下次执行相同的 SQL 语句时能够直接从缓存中获取结果,从而减少数据库的访问频率,提高性能。MyBatis 支持一级缓存(SqlSession 范围内)和二级缓存(跨 SqlSession 的共享缓存)。
-
日志记录与监控
MyBatis 会通过集成日志框架(如 Log4j、SLF4J)记录 SQL 执行的详细日志。这些日志信息可以帮助开发人员分析 SQL 执行的性能,发现潜在的问题,优化查询效率。
最后,将查询结果返回给调用方,自此,完成整个 SQL 执行过程。
下面详细介绍每一个过程:
- SQL解析
当 MyBatis 执行一个查询时,首先会对传入的 SQL 语句进行解析,解析 SQL 语句的结构和参数信息,为后续的参数绑定和执行做准备。
- 解析过程会进行语法分析和语义分析,以确保SQL语句的正确性
- 分析 SQL 语句中的参数占位符并提取出需要传入的参数类型和数量。
- 将 SQL 语句转化为 MyBatis 内部可识别的数据结构
MyBatis 使用 XML 或注解中的 SQL 语句,结合映射文件中的 MappedStatement 对象来表示 SQL 信息。MappedStatement 是 MyBatis 核心的配置对象,包含了 SQL 的元数据、类型处理器、缓存策略等。
解析过程涉及到标签的解析、参数的解析和动态SQL的处理
- 标签解析:
- 首先会解析映射文件的根节点(<select>、<insert>、<update>、<delete>等),然后逐个解析其中的标签。
- 解析标签时,MyBatis会根据标签的不同类型(如查询、插入、更新、删除)执行相应的解析逻辑。
- 动态SQL处理:
- MyBatis支持动态SQL,即根据不同的条件在运行时动态生成SQL语句。\
- 动态SQL使用一系列XML标签(如<if>、<choose>、<when>、<otherwise>等)进行条件判断和 SQL片段的组装。
- 在解析映射文件时,MyBatis会对动态SQL片段进行解析,并根据条件判断生成最终的SQL语句。
- 动态SQL的处理涉及到条件判断、循环、字符串拼接等操作,以根据运行时的条件生成最终的SQL语句。
参考:
sql
<select id="findUserById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
- 在上述例子中,findUserById 是一个 MappedStatement 对象,#{id} 是一个占位符。
- SQL参数映射
MyBatis 在 SQL 参数映射阶段,会将用户提供的参数绑定到 SQL 语句中的占位符。根据映射文件或注解中的 SQL 语句,MyBatis 会分析哪些占位符需要绑定哪些参数。这样,在 SQL 预编译和执行阶段,就可以正确地传递参数并执行 SQL 语句。
在 MappedStatement 中,有一个 BoundSql 对象,它包含了实际的 SQL 语句以及绑定的参数。BoundSql 会根据 SQL 的占位符(如 ? 或 #{})将传入的参数与 SQL 语句进行绑定。
-
MyBatis 首先会解析 SQL 语句,识别其中的占位符(#{})或者字符串替换(${})形式的参数 。
-
对于 ${} 形式的参数,MyBatis 会直接将参数值替换到 SQL 语句中(需要谨防SQL注入问题)。
参考:
java
Map<String, Object> params = new HashMap<>();
params.put("id", 1);
userMapper.findUserById(params);
- params 中的 id 将会绑定到 SQL 语句中的 #{id} 占位符。
- SQL预编译
MyBatis使用JDBC的PreparedStatement接口创建预编译的SQL语句,预编译的SQL语句中使用占位符(如?)代替参数。通过 SQL 预编译提升执行效率,并确保参数的安全性。
- 预编译有助于提高执行效率,因为 SQL 语句只需要解析一次,数据库会优化执行计划,避免每次都解析 SQL。
- PreparedStatement 允许 MyBatis 在数据库执行时才将参数值填充到预编译的 SQL 中,从而提高执行效率并避免 SQL 注入攻击。
参考:
java
PreparedStatement ps = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, 1);
- 将第一个参数(id)设置为 1
- SQL执行
MyBatis 会通过 JDBC API 将编译后的 SQL 语句提交到数据库执行。执行过程会获取执行结果,例如查询结果或更新、删除操作的影响行数。
- 执行时,MyBatis 会通过 Statement 或 PreparedStatement 接口执行 SQL。查询操作会返回一个 ResultSet,更新、删除等操作会返回受影响的行数。
- 如果执行的是查询操作,MyBatis 会进一步处理返回的 ResultSet,并将其转换为 Java 对象。
参考:
java
ResultSet rs = ps.executeQuery();
- 结果映射
执行 SQL 后,MyBatis 会将数据库返回的 ResultSet 映射为相应的 Java 对象。这个映射过程会根据 SQL 查询的字段与 Java 对象的属性进行转换。
- MyBatis 使用 ResultMap 来定义如何将查询结果映射到 Java 对象。ResultMap 可以指定每个字段与对象属性之间的映射关系。
- 例如,使用 TypeHandler 进行类型转换(比如将数据库中的 VARCHAR 类型转换为 String 类型)。
- 对于复杂的结果映射,MyBatis 允许嵌套的映射以及集合类型的处理。
参考:
xml
<resultMap id="userResultMap" type="User">
<result property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
- 事务处理
MyBatis 支持手动和自动事务管理。默认情况下,MyBatis 使用 JDBC 提供的事务控制。在执行数据库操作后,MyBatis 会根据配置决定是否提交或回滚事务。
- 如果使用自动提交(autocommit=true),事务会在每个操作后立即提交。
- 如果使用手动事务管理,开发者需要显式地调用 commit() 或 rollback() 来提交或回滚事务。
- 事务处理保证了操作的一致性,即在一个事务中的多个操作要么全部成功,要么全部失败。
参考:
java
sqlSession.commit(); // 提交事务
sqlSession.rollback(); // 回滚事务
- 缓存处理
MyBatis 提供了一级缓存和二级缓存机制来提高查询效率。一级缓存是 SqlSession 级别的缓存,而二级缓存是跨 SqlSession 的共享缓存。
- 一级缓存:在同一个 SqlSession 中,查询的结果会被缓存。只要没有关闭 SqlSession,再次查询相同的 SQL 会直接从缓存中获取结果,而不是重新查询数据库。
- 二级缓存:不同 SqlSession 之间可以共享缓存。二级缓存的使用需要在 MyBatis 配置文件中显式启用,并且每个映射器可以配置自己的缓存策略。
- 缓存机制有助于提高性能,尤其是在查询频繁但数据变化较少的场景中。
- 日志记录与监控
MyBatis 可以集成各种日志框架(如 Log4j、SLF4J 等)来记录 SQL 执行过程中的信息。开发人员可以通过日志输出 SQL 执行的详细信息,包括查询语句、执行时间等。
- MyBatis 的日志记录对于开发人员调试 SQL 和优化性能非常有帮助。
- 日志框架可以配置为不同的日志级别,例如 DEBUG、INFO、ERROR 等,方便查看不同详细度的信息。
参考:
xml
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
扩展
#与$的区别
- $ 符号
- $符号占位符是简单的字符串替换,不进行预编译和参数类型处理,也不会进行转义。
- $符号占位符直接将参数的值替换到SQL语句中,可以用于动态拼接SQL语句的部分内容。
- $符号占位符存在SQL注入的风险,因为参数值直接替换到SQL语句中,可能导致恶意注入攻击。
- 最终SQL的动态参数值一定不会有引号包裹
- # 符号
- #符号占位符是预编译的占位符,会对参数进行类型处理和安全处理。
- #符号占位符将参数值作为预编译参数传递给数据库,可以防止SQL注入攻击。
- #符号占位符可以用于动态生成SQL语句的条件部分,例如WHERE子句、ORDER BY子句等。
- 最终SQL的动态参数值可能会有引号包裹 (不是一定都会有引号包裹)
- 字符串类型:会自动将其包裹在单引号中。
- 数字类型:不会自动加单引号。
- 日期类型:会格式化日期并加上单引号。
总结示例
示例:
username的值为haha(String)
1. 使用$占位符
sql
SELECT id, username, email FROM users WHERE username = ${username}
# 最终实际执行的sql
SELECT id, username, email FROM users WHERE username = haha
2. 使用#占位符
sql
SELECT id, username, email FROM users WHERE username = #{username}
# 最终实际执行的sql
SELECT id, username, email FROM users WHERE username = 'haha'
Mybatis SQL分类
根据 SQL 查询的特性来区分的,可以将SQL分为动态 SQL 和静态 SQL。
- 动态 SQL
使用动态SQL,可以根据不同的条件生成不同的SQL语句,从而实现灵活的查询和更新操作。动态SQL可以使用if、choose、when、otherwise等标签来实现条件判断和循环操作,同时还可以使用foreach标签来实现对集合类型参数的遍历操作。这样可以避免在代码中使用大量的字符串拼接,提高代码的可读性和维护性。
动态 SQL 的特点:
- 可以根据不同的条件生成不同的 SQL 查询。
- 可以在查询语句中动态地添加、删除或修改部分 SQL 逻辑。
- 提供了更大的灵活性和动态性,可以根据运行时的条件来动态生成查询语句。
常见的动态 SQL 的示例:
- 使用条件语句(如 IF、CASE)来动态选择不同的查询分支。
- 使用循环语句(如 FOR、WHILE)来生成重复或动态数量的查询条件。
- 使用动态连接条件来构建动态查询条件。
- 使用动态排序来指定不同的排序方式。
- 静态 SQL
静态 SQL 的特点:
- 查询语句的结构和逻辑是固定的,在执行查询时不会发生变化。
- SQL 查询不受外部参数的影响,参数值在查询中直接拼接。
常见的静态 SQL 的示例:
- 简单的选择查询语句,不需要根据条件改变查询逻辑。
- 固定的更新语句,不受外部参数影响。
- 预定义的查询模板,参数值直接拼接在查询语句中。
- 创建表格、索引等静态结构定义。
静态SQL和动态SQL选择
由于静态sql是在应用启动的时候就解析,而动态sql是在执行该sql相关操作的时候才根据传入的参数进行解析的,所以静态sql效率会比动态sql好。
${}、#{}与SQL是否静态SQL、动态SQL无直接关系
- ${} 和 #{} 只是占位符,用来插入参数,它们本身并不决定 SQL 是静态还是动态。
- 动态 SQL 是通过 条件判断(如 、)来决定 SQL 的结构是否变化。
- 即使 SQL 中使用了 ${} 或 #{},如果没有动态条件,它仍然是静态 SQL。
示例1:
使用了${} 、#{},不是动态SQL
sql
<select id="getUserById" resultType="User">
SELECT id, username, email
FROM users
WHERE id = #{id}
</select>
- 这里的 #{id} 用来传递参数,但 SQL 本身 是固定的,没有根据任何条件变化。
- 这个查询并不是动态 SQL,因为它没有任何动态部分(比如没有使用 标签)。
示例2:
使用 ${} 来动态拼接表名,不是动态SQL
sql
<select id="getUsersByTableName" resultType="User">
SELECT id, username, email
FROM ${tableName}
WHERE id = #{id}
</select>
- 这里的 ${tableName} 是动态的,会根据传入的 tableName 参数拼接成不同的表名。
- 但 SQL 结构本身依然是 固定的,只是表名不同。
示例3:
使用了${} 、#{},是动态SQL
sql
<select id="selectUsers" resultType="User">
SELECT id, username, email
FROM users
<where>
<if test="username != null">AND username = #{username}</if>
<if test="email != null">AND email = #{email}</if>
</where>
</select>
- 使用了 标签来动态地添加 SQL 条件,因此整个 SQL 是动态的。
- 如果 username 和 email 参数存在,SQL 语句就会加入对应的 AND 条件;如果不存在,这些条件会被忽略。
- 这个查询才是真正的动态 SQL,因为它的结构根据输入的参数变化。但 #{} 只是用来传递参数,它和是否动态没有直接关系