一、MyBatis是什么?
MyBatis 是一款开源的、轻量级的对象关系映射(ORM)框架,用于Java应用中的数据库持久层操作。它简化了与数据库之间的交互,让开发者可以更专注于编写SQL语句和关注业务逻辑,而不需要处理大量的JDBC底层细节,如加载驱动、创建连接、预编译Statement以及结果集的遍历和转换等。
MyBatis的特点包括:
- 自定义SQL:允许开发者直接编写SQL语句,提供了高度的灵活性和对SQL优化的控制。
- 映射配置:通过XML或注解的方式将SQL查询的结果映射到Java对象上,支持一对一、一对多等多种复杂映射关系。
- 动态SQL:支持在SQL语句中使用动态元素,可以根据运行时条件生成不同的SQL语句。
- 缓存机制:提供了一级缓存(Session级别)和二级缓存(全局/应用级别),有助于提高查询性能。
- 事务管理:能够与Spring等容器进行集成,实现声明式事务管理。
此外,MyBatis以其简单易用、易于学习和调试、高性能等特点受到广大开发者的青睐,尤其适用于需要精细控制SQL执行或者追求更高性能的场景,虽然相比全自动ORM框架(如Hibernate)牺牲了一些数据库无关性,但赋予了用户更多的自由度和对数据库操作的精确控制能力。
二、Mybatis-Plus是什么?
MyBatis-Plus(简称 MP)是一个基于 MyBatis 的持久层框架增强工具,它在 MyBatis 的基础上提供了额外的功能和简化操作,旨在进一步提升开发效率和减少重复工作。主要特性包括:
-
无侵入:设计上遵循“只做增强不做改变”的原则,意味着开发者可以在不修改现有 MyBatis 代码结构的情况下引入 MyBatis-Plus,并保持原有的功能正常运行。
-
自动填充:提供如创建时间、更新时间等字段的自动填充功能。
-
通用 CRUD 操作:内置了基本的增删改查方法,开发者无需编写大量的 CRUD SQL 和对应的 Mapper 接口方法。
-
条件构造器:简化复杂查询条件的构建,支持链式调用生成动态SQL。
-
分页插件:集成分页功能,可以方便地处理数据库分页查询。
-
性能损耗小:启动时自动注入 CURD 功能,但对原有 MyBatis 性能影响极低,可以直接面向对象进行操作。
-
全局配置:统一的全局配置项,简化了诸如主键策略、逻辑删除等全局性规则的设置。
-
其他增强功能:还包括诸如逻辑删除、表别名、自定义全局通用列、枚举类型转换等众多实用功能。
通过使用 MyBatis-Plus,开发者能够以更简洁高效的方式完成数据访问层的开发,从而将更多精力投入到业务逻辑实现中去。
三、Mybatis的ORM是什么
MyBatis虽然被部分人称为“半自动”或“轻量级”的ORM框架,但其本身确实提供了对象关系映射(Object-Relational Mapping, ORM)的功能。在MyBatis中,ORM主要体现在以下几个方面:
-
对象映射:通过XML配置文件或者注解的方式定义Java类(实体类)与数据库表之间的映射关系,例如将类的属性对应到数据库表的字段。
-
SQL映射:开发者可以编写自定义的SQL语句,并在MyBatis的映射文件中定义SQL语句与Java方法之间的映射关系。当调用接口方法时,MyBatis会根据映射规则执行相应的SQL操作,并将查询结果自动转换为对应的Java对象列表或者单个对象。
-
数据持久化和检索:MyBatis能够将Java对象自动持久化至数据库(插入、更新),并从数据库检索数据并填充到Java对象中。
-
动态SQL:MyBatis支持动态SQL构建,可以根据Java对象的属性值动态生成SQL语句,进一步体现了对象与关系型数据库交互的灵活性。
需要注意的是,相较于全自动化ORM框架如Hibernate,MyBatis允许开发人员直接编写SQL语句,这给予了用户更多的灵活性来优化SQL性能,但也意味着在某些情况下,开发者需要处理更多关于SQL语法层面的工作,因此被称为“半自动”。尽管如此,MyBatis在提供基本ORM功能的同时,保持了较低的学习曲线和较高的运行效率,得到了很多项目的青睐。
四、Mybatis的ORM是什么
Mybatis被称为半自动ORM映射工具,主要是因为虽然它提供了对象关系映射(ORM)的功能来简化数据库操作,但与全自动ORM框架相比,在SQL处理和对象关系映射方面要求开发人员参与更多的手工配置和编写工作。具体区别如下:
-
手动SQL控制:
- 在MyBatis中,开发者需要自己编写SQL查询语句,这给予用户对SQL执行的完全控制权,可以根据实际情况进行优化调整,以适应不同的数据库系统或特定性能需求。
- 全自动ORM工具如Hibernate,则根据实体类及关联关系自动生成SQL,尽可能地减少了人工编写SQL的需求。
-
对象-关系映射灵活性:
- MyBatis通过XML配置文件或注解来指定Java对象与数据库表之间的字段映射、结果集映射以及关系映射等,这些都需要显式配置。
- 全自动ORM框架通常基于元数据信息(比如类名、属性名等)和约定(如命名规则)来自动生成映射,有时也可以通过注解进行更细致的定制。
-
关联对象管理:
- MyBatis在处理关联对象或集合时,往往也需要开发者手写JOIN查询或者通过嵌套查询来完成关联对象的加载。
- Hibernate则可以通过其自身的HQL语言或JPA规范中的抓取策略自动处理一对多、一对一和多对多等复杂关系,实现关联对象的透明加载。
-
学习曲线与可控性:
- MyBatis由于需要直接编写SQL,因此对于熟悉SQL且有性能调优需求的开发者来说,可能具有更低的学习成本和更高的可控性。
- 全自动ORM工具在提供更高抽象层次的同时,可能会牺牲一定的灵活性,但对于希望快速开发且不太关注底层SQL细节的团队来说,可以提高开发效率。
综上所述,Mybatis的“半自动”特性体现在它允许并要求开发者更多地参与到数据库交互的具体实现中,以换取更强的灵活性和性能优化能力。而全自动ORM工具则倾向于提供更多自动化机制,降低数据库操作的复杂度,但在某些场景下可能牺牲了部分性能优化选项和定制化能力。
五、传统JDBC开发存在的问题
传统JDBC开发存在以下主要问题:
-
代码重复和繁琐:
- JDBC编程需要大量的样板代码来建立数据库连接、执行SQL语句、处理结果集等,这导致代码冗余且难以维护。
- 对查询结果集的处理通常涉及硬编码列名和手动将数据映射到Java对象的过程,当表结构变化时,这些代码需要相应更新。
-
资源管理效率低:
- 每次与数据库交互都要创建和关闭Connection、Statement和ResultSet等资源,频繁的创建和释放数据库连接会消耗大量系统资源,影响性能。
- 若资源未正确关闭,可能导致内存泄漏或数据库连接耗尽,影响系统的稳定性和可用性。
-
SQL语句硬编码:
- SQL查询语句直接写在Java代码中,不利于维护,当SQL发生变化时,必须修改源码并重新编译程序,违反了开闭原则(Open/Closed Principle)。
-
SQL注入风险:
- 使用字符串拼接的方式构造SQL语句容易引入SQL注入漏洞,不安全。
-
灵活性和可扩展性差:
- 无法方便地处理动态SQL和复杂关联查询,对于实体之间的关系映射操作较为复杂。
- 缺乏良好的缓存支持和事务管理机制。
-
不支持延迟加载和关联对象管理:
- 在处理一对多或多对一等复杂的对象关联时,需要额外编写大量逻辑来实现关联对象的加载。
为了解决这些问题,出现了许多ORM框架,如Hibernate、MyBatis等。这些框架通过提供诸如数据库连接池管理、自动对象映射、动态SQL构建、事务控制等功能,极大地简化了数据库访问层的开发工作,并提高了应用的整体性能和安全性。
六、Mybatis如何解决SQL注入问题
MyBatis框架在防止SQL注入方面提供了有效的机制:
-
预编译语句与占位符
#{}
:- MyBatis使用PreparedStatement预编译 SQL 语句的方式执行查询和更新操作。当在SQL映射文件中使用
#{}
占位符时,MyBatis会将参数值作为JDBC预编译的参数传递给数据库,而不是直接拼接到SQL字符串中。预编译的SQL语句会在执行前被数据库服务器解析并编译成执行计划,因此用户输入的数据不会影响SQL结构,从而避免了SQL注入。
<select id="selectUser" parameterType="int" resultType="User">SELECT * FROM user WHERE id = #{id} </select>
- MyBatis使用PreparedStatement预编译 SQL 语句的方式执行查询和更新操作。当在SQL映射文件中使用
-
动态SQL与安全过滤:
- MyBatis支持动态SQL标签如
<if>
,<choose>
,<when>
,<otherwise>
,<where>
等,这些标签可以用于构建条件复杂的动态SQL,同时确保即使是在动态构造SQL的过程中,传入的参数也会经过处理,防止恶意代码被执行。
- MyBatis支持动态SQL标签如
-
区别对待
${}
占位符:- MyBatis还提供了一种
${}
占位符,它会将变量内容直接替换到SQL语句中而不进行任何转义或预编译。这种方式不能有效防止SQL注入,因此通常只在确定数据来源安全或者需要原生表达式(例如表名、列名)的情况下谨慎使用,并且需要开发者手动确保注入攻击防护。
- MyBatis还提供了一种
总的来说,在MyBatis中,推荐始终使用#{}
来代替${}
以防止SQL注入,因为#{}
通过预编译方式保证了参数的安全性。对于确实需要动态构造SQL部分但又需确保安全的情况,应尽量利用MyBatis的动态SQL标签功能来实现。
七、Mybatis有哪些常用标签
Mybatis中常用的标签包括但不限于以下这些:
-
SqlMapConfig 标签:
- 作为MyBatis配置文件的根元素,用于定义全局配置信息,如数据源、事务管理器、映射文件的引用等。
-
SQL定义相关标签:
- select:定义查询语句。
- insert:定义插入语句。
- update:定义更新语句。
- delete:定义删除语句。
-
动态SQL标签:
- if:根据条件动态包含或排除部分SQL片段。
- choose(搭配when/otherwise):类似Java中的switch-case结构,根据条件执行不同的SQL块。
- where:智能地添加
WHERE
子句,只在至少有一个条件为真的时候才加入WHERE
关键字。 - set:用于更新语句时动态生成
SET
部分,自动过滤掉空值。 - foreach:遍历集合或数组,生成多个占位符和参数列表。
- trim、merge、concat:对SQL语句进行字符串处理,例如去除前后缀、合并多个片段、连接字符串等。
-
其他可重用性与简化SQL的标签:
- sql:定义可重用的SQL片段,可以在其他地方通过
include
标签引入。 - include:用于将之前定义的SQL片段引入到当前SQL语句中。
- sql:定义可重用的SQL片段,可以在其他地方通过
-
结果映射相关标签:
- resultMap:定义结果集映射规则,将数据库记录映射到Java对象属性上,支持复杂类型的映射,如一对一、一对多等关联关系。
-
参数传递相关标签:
- parameterType:在SQL语句中指定输入参数的类型。
以上标签通常在MyBatis的XML映射文件中使用,帮助开发者构建灵活且可维护的数据库操作逻辑。
八、Mybatis的foreach标签如何使用
MyBatis 的 foreach
标签用于遍历集合(如 List、数组或 Map)并生成动态 SQL。在处理需要对集合中的元素进行批量操作,尤其是构建 IN
条件时特别有用。以下是如何使用 MyBatis 中 foreach
标签的示例和详细说明:
<foreach [collection="集合属性名" | item="迭代变量名" | index="索引变量名" | open="循环开始字符串" | separator="元素分隔符" | close="循环结束字符串"]><!-- 在这里放置需要遍历部分的SQL语句 -->#{迭代变量名}
</foreach>
- collection:这是必须指定的属性,它指定了要遍历的集合对象的名称,这个名称通常是参数对象的一个属性名。
例如,如果你有一个名为 ids
的 List 类型参数:
List<Integer> ids = Arrays.asList(1, 2, 3);
对应的映射文件中可能这样使用 foreach
标签:
<select id="selectByIds" parameterType="java.util.List" resultType="com.example.MyEntity">SELECT * FROM my_tableWHERE id IN<foreach item="id" collection="list" open="(" separator="," close=")">#{id}</foreach>
</select>
-
item:定义了在遍历时的迭代变量名,即每次迭代过程中当前元素的引用名。
-
index:可选属性,如果需要获取当前元素的索引值,则可以设置此属性。
-
open、separator 和 close:
open
指定遍历开始时添加到SQL中的字符串,比如这里的 “(” 表示IN
子句的起始括号。separator
指定每次迭代之间插入的分隔符,这里是 “,” 表示多个id
值之间的逗号。close
指定遍历结束后添加到SQL中的字符串,例如这里的 “)” 表示IN
子句的结束括号。
应用以上配置后,当调用该查询方法并传入一个包含多个 id
的列表时,MyBatis 将自动生成如下形式的 SQL(假设传入了三个 id
):
SELECT * FROM my_table WHERE id IN (1, 2, 3)
此外,在使用 #{}
时要注意其会进行预编译参数化设置,有助于防止SQL注入攻击;而 ${}
则是简单地替换变量值,不进行预编译,通常只在非参数化的字符串拼接场景下谨慎使用。
九、在mapper中如何传递多个参数
在 MyBatis 的 Mapper 接口中,传递多个参数有以下几种常见方法:
-
使用
@Param
注解:
在 Mapper 接口的方法签名中,可以为每个参数添加@Param
注解来显式指定参数名称。这样,在对应的 XML 映射文件中可以通过注解的值来引用这些参数。public interface UserMapper {@Insert("INSERT INTO user (name, age) VALUES (#{name}, #{age})")int insertUser(@Param("name") String name, @Param("age") int age); }<!-- mapper.xml --> <insert id="insertUser">INSERT INTO user (name, age)VALUES (#{name}, #{age}) </insert>
-
直接传递多个参数:
如果接口方法的参数数量不多,并且顺序固定,MyBatis 会按照参数在方法签名中的顺序来匹配 XML 中的#{}
参数占位符。public interface UserMapper {int selectUser(String username, String area); }<!-- mapper.xml --> <select id="selectUser" resultType="com.example.User">SELECT * FROM user WHERE username = #{0} AND area = #{1} </select>
-
使用 Map 来封装参数:
将多个参数封装到一个Map
对象中,并通过键值对的形式传递。在 Mapper 方法和 XML 文件中,可以通过 map 中的 key 引用对应的参数。public interface UserMapper {User selectUserByParams(Map<String, Object> params); }<!-- mapper.xml --> <select id="selectUserByParams" resultType="com.example.User">SELECT * FROM user WHERE username = #{params.username} AND area = #{params.area} </select>// 调用时 Map<String, Object> params = new HashMap<>(); params.put("username", "test"); params.put("area", "NY"); userMapper.selectUserByParams(params);
-
使用自定义对象封装参数:
创建一个 Java Bean 类,包含所有需要作为参数传递的属性,然后将这个对象作为 Mapper 方法的参数。public class UserQuery {private String username;private String area;// getters and setters... }public interface UserMapper {User selectUser(UserQuery query); }<!-- mapper.xml --> <select id="selectUser" resultType="com.example.User">SELECT * FROM user WHERE username = #{query.username} AND area = #{query.area} </select>// 调用时 UserQuery query = new UserQuery(); query.setUsername("test"); query.setArea("NY"); userMapper.selectUser(query);
在实际应用中,后两种方式(使用 Map 或自定义对象)更常用于复杂查询或参数数量较多的情况,因为它们更具可读性和可维护性。
十、Mybatis的一级、二级缓存
MyBatis 提供了一级缓存和二级缓存两种级别的缓存机制,用于提高查询效率,减少数据库访问。
一级缓存(Local Cache)
-
作用范围:一级缓存是基于 SqlSession 级别的本地缓存。当一个 SqlSession 对象被创建后,它就拥有了自己的本地缓存区域。
-
特点:
- 默认开启且不可关闭。
- 同一 SqlSession 内部,如果执行相同的 SQL 语句并且参数相同,则在第一次查询结果后,会将结果对象放入缓存中,后续的请求直接从缓存中获取数据,而不会再次去数据库查询。
- 当 SqlSession 关闭或执行了插入、更新、删除等操作时,一级缓存会被清空,以确保数据的一致性。
-
适用场景:适用于单线程环境下的读多写少的场景,以及同一个业务逻辑需要多次执行相同查询的情况。
二级缓存(Mapper-Level Cache)
-
作用范围:二级缓存是基于 Mapper 映射级别的全局缓存,可以跨 SqlSession 共享数据,意味着不同 SqlSession 的查询结果如果满足条件,也可以共享同一个二级缓存中的数据。
-
特点:
- 二级缓存默认是关闭的,需要在 MyBatis 配置文件或具体的 mapper 映射文件中手动配置启用。
- 二级缓存的作用域更大,多个 SqlSession 在使用同一个 Mapper 的时候,能够共享这个 Mapper 中配置的二级缓存内容。
- 二级缓存提供了多种可配置选项,例如缓存实现类的选择、刷新策略、是否支持并发缓存等。
-
数据同步与一致性:
- 二级缓存的数据一致性和更新问题需要谨慎处理,因为它涉及到多个 SqlSession 和多线程环境下的数据同步问题。通常需要配合事务管理来确保缓存中的数据是最新的。
- 当在一个 SqlSession 中修改了数据库中的数据,而这个数据在另一个 SqlSession 的二级缓存中也有缓存时,如果没有采取合适的策略,可能会导致缓存数据不一致。
-
适用场景:适合多线程环境、对数据一致性要求相对宽松、读取频繁且变化较少的数据场景,通过合理的缓存策略可以显著提升应用性能。
总结起来,MyBatis 的一级缓存主要解决的是短生命周期内的重复查询问题,而二级缓存则旨在解决长周期内跨SqlSession的重复查询问题,但同时也带来了更高的复杂性和维护成本。
十一、Mybatis是如何进行分页的?分页插件的原理是什么?
MyBatis本身并不直接提供内置的分页功能,但可以通过多种方式实现分页查询:
-
手动分页:
- 开发者可以在SQL语句中使用数据库特定的分页语法,例如在MySQL中使用
LIMIT offset, row_count
来实现分页。
- 开发者可以在SQL语句中使用数据库特定的分页语法,例如在MySQL中使用
-
RowBounds方式:
- MyBatis提供了
RowBounds
类,它可以在Executor执行查询时对结果集进行内存分页。开发者可以设置起始行号和结束行号(或者总的记录数限制),但是请注意这种方式并不是物理分页,而是将所有数据加载到内存中再进行切片,对于大数据量的场景可能造成性能问题。
- MyBatis提供了
-
第三方分页插件:
- 为了方便且高效地处理分页查询,通常推荐使用第三方分页插件,如PageHelper、Mybatis-Plus等。这些插件利用了MyBatis的插件机制,通过拦截执行SQL的流程,在生成或执行SQL时动态添加合适的分页语句。
分页插件的工作原理主要包括以下步骤:
-
注册拦截器:在MyBatis配置文件中注册分页插件作为拦截器,该插件会拦截SQL执行过程中的关键点。
-
重写SQL:当插件检测到需要进行分页操作时,会在原SQL基础上追加或替换符合当前数据库方言(如MySQL、Oracle)的分页语句,如MySQL的
LIMIT
子句。 -
参数解析与传递:分页插件会捕获并解析用户请求中的分页参数,如页码、每页大小,并将它们转换为数据库能够识别的分页条件。
-
数据库方言支持:分页插件内部包含各种数据库的方言实现,可以根据数据库类型选择正确的分页策略。
-
结果处理:分页插件还会处理分页查询的结果,返回给客户端分页后的数据列表以及相关的分页信息(总记录数、总页数等)。
通过这样的方式,分页插件有效地实现了针对不同数据库的物理分页,极大地简化了开发人员编写分页查询的工作,并提高了系统性能。
十二、Mybatis-Plus是如何进行分页的
MyBatis-Plus 提供了对分页查询的便捷支持,它通过内置的分页插件(PaginationInterceptor 或者 PaginationInnerInterceptor)实现数据库级别的物理分页。以下是 MyBatis-Plus 进行分页的基本步骤和原理:
-
配置分页插件:
- 在 MyBatis-Plus 的配置文件中注册
PaginationInterceptor
分页拦截器。
@Bean public PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor(); }
- 或者在 XML 配置方式下进行配置。
- 在 MyBatis-Plus 的配置文件中注册
-
使用Page对象:
- 在业务代码中,创建一个
Page<T>
对象实例,并设置当前页码(current)、每页大小(size)等信息。
Page<User> page = new Page<>(currentPage, pageSize);
- 在业务代码中,创建一个
-
执行分页查询:
- 将
Page
对象作为参数传递给 Service 层中的方法,在方法内部调用 Mapper 中的方法时将Page
对象传入。
List<User> users = userMapper.selectPage(page, wrapper); // wrapper 是条件构造器或自定义的SQL片段
- 将
-
分页插件的工作原理:
- 当
PaginationInterceptor
拦截到带有Page
对象的 SQL 查询请求时,会自动修改生成的 SQL 语句,添加对应的数据库方言的分页语句。 - 如在 MySQL 中,会在原 SQL 语句后追加
LIMIT offset, row_count
子句,其中 offset 和 row_count 根据当前页码和每页大小动态计算得到。 - 同时,分页插件还会拦截查询结果集,封装返回的总记录数、总页数等信息到
Page
对象中。
- 当
-
获取分页结果:
- 业务层方法执行完毕后,返回的
Page
对象不仅包含了当前页的数据列表,还包含了诸如总记录数(total)、总页数(pages)等相关分页信息。
- 业务层方法执行完毕后,返回的
这样,通过 MyBatis-Plus 的分页插件机制,开发者可以轻松地完成复杂分页查询操作,而无需手动拼接 SQL 或处理复杂的分页逻辑。
十三、Myabtis 当中 #{}
和${}
的区别是什么?
在MyBatis中,#{}
和 ${}
都是用来在SQL语句中传递动态参数的占位符,它们的主要区别在于处理方式、安全性以及编译阶段:
-
处理方式:
-
#{}
(预编译占位符):- MyBatis会将
#{}
替换为数据库驱动所识别的预编译参数标记(如MySQL中的?
),并在执行SQL时使用PreparedStatement来设置参数值。 - 参数值会被正确地转义并加上单引号,适合用于传递任意类型的值,包括字符串和数值类型,且能防止SQL注入。
- MyBatis会将
-
${}
(字符串替换占位符):- MyBatis会直接将
${}
内的变量内容替换到SQL语句中,不做任何特殊处理。 - 参数值不会被自动加引号,因此当传入的是字符串时,开发者需要确保其格式正确,且这种做法容易导致SQL注入攻击。
- MyBatis会直接将
-
-
安全性:
#{}
提供了更好的安全性,因为它支持预编译机制,可以有效防止SQL注入攻击。${}
因为是直接拼接字符串,不提供预编译安全保护,若传入的参数未经验证,可能会有SQL注入风险。
-
使用场景:
#{}
通常用于传递查询条件等普通参数。${}
一般仅在特定场景下使用,比如当需要动态构建表名或列名等SQL语法元素,但这种情况应尽量避免,并确保这些动态构造的部分来自于可靠的、不受用户控制的数据源。
总结来说,在编写MyBatis映射文件时,除非有特殊需求,否则推荐优先使用#{}
占位符来传递参数,以确保应用的安全性和性能。