Mybatis
什么是 ORM 框架?
ORM(Object-Relational Mapping,对象关系映射)框架是一种编程技术,用于实现面向对象编程语言中的对象与数据库中的表之间自动转换。ORM 框架的作用是作为应用程序的数据层和数据库之间的桥梁,它允许开发者使用面向对象的范式来操作关系型数据库,而不需要直接编写 SQL 语句。
ORM框架的工作原理
- 映射:将数据库表映射到面向对象语言中的类,以及将类的属性和方法映射到数据库的字段和约束上。
- 对象持久化:将对象保存到数据库,自动处理数据类型转换和关系维护。这通常涉及将对象的当前状态转换为数据库中的记录。
- 查询:使用面向对象的查询语言访问数据库,简化了复杂查询的编写。开发者可以像操作对象一样查询数据库,而无需编写SQL语句。
- 缓存:ORM框架通常会缓存查询结果和频繁访问的对象,以提高数据库访问的性能。
ORM 框架关键特性:
- 抽象化:ORM 提供了一种抽象的方式,将数据库表映射到程序中的类,将表中的行映射到对象实例,将列映射到对象属性。这样可以减少开发人员与底层数据库交互的复杂性。
- 查询功能:大多数 ORM 框架提供了自己的查询语言或 API,让开发者可以用更自然的方式构造查询,而不是直接使用 SQL。
- 事务管理:ORM 框架通常会提供对事务的支持,确保数据的一致性和完整性。
- 变更跟踪:一些 ORM 框架能够追踪对象的变化,在提交时只更新那些确实发生变化的数据。
- 关联管理:ORM 框架处理实体之间的关系(一对一、一对多、多对多等),简化了复杂查询和关联数据的加载。
- 迁移支持:某些 ORM 框架提供数据库模式迁移工具,使得在开发过程中更容易地管理和演化数据库结构。
- 性能优化:高级的 ORM 框架可能包括缓存机制、懒加载(Lazy Loading)、批量查询等功能以提高性能。
ORM框架的优点
- 简化开发:ORM框架将数据库操作转化为面向对象的操作,开发者可以使用面向对象的方式进行数据库操作,无需编写复杂的SQL语句,减少了开发工作量。
- 提高可维护性:使用ORM框架可以将数据库操作与业务逻辑分离,使代码更加清晰和可维护。同时,ORM框架提供了一些常用的功能,如事务管理、缓存管理等,进一步简化了开发人员的工作。
- 跨数据库支持:ORM框架通常支持多种数据库,开发者可以在不同的数据库之间切换,而无需修改代码。这提高了代码的复用性和灵活性。
常见的ORM框架
- Hibernate:一个Java语言的ORM框架,支持多种关系数据库,如MySQL、Oracle和SQL Server等。它提供了丰富的映射方式,支持延迟加载、级联操作等功能,是一个成熟、稳定的框架。
- MyBatis:一个基于Java的持久层框架,通过配置文件和注解方式将接口与SQL语句绑定。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,可以使用简单的XML或注解进行配置和原始映射。
- Entity Framework(EF):一个.NET平台的ORM框架,它提供了数据访问技术,通过描述实体和它们之间的关系,将数据转换为我们熟悉的.NET对象。EF支持多种数据库,包括SQL Server、MySQL、SQLite等。
此外,还有如SQLAlchemy(Python)、ActiveRecord(Ruby)、**Sequelize和Mongoose(JavaScript)**等其他优秀的ORM框架。
ORM框架的考虑因素
- 项目需求:根据项目使用的编程语言和数据库类型选择合适的ORM框架。
- 性能:选择性能表现优秀的框架,以确保系统的高效运行。
- 易用性:选择易于学习和使用的框架,以降低开发成本和提高开发效率。
- 社区活跃度:选择有活跃社区支持的框架,以便在遇到问题时能够得到及时的帮助和支持。
什么是 Mybatis?
1、Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱
动、创建连接、创建statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。
2、MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数
以及获取结果集。
3、通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java 对象和 statement 中 sql 的动态参数
进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返回 result 的过
程)。
以下是 MyBatis 的一些关键特性:
- 简单易学:对于那些已经熟悉 SQL 和 JDBC 的开发者来说,MyBatis 学习曲线相对较低,因为它基本上是直接操作 SQL。
- SQL 和 Java 对象的映射:MyBatis 提供了丰富的映射标签(如
<resultMap>
)来实现从 SQL 查询结果到 Java 对象的复杂映射关系。 - 动态 SQL:MyBatis 支持使用 OGNL 表达式在运行时构建动态 SQL 语句,这使得你可以根据不同的条件组合 SQL 语句。
- 支持多种数据库:MyBatis 可以与任何类型的数据库协同工作,只要提供了相应的 JDBC 驱动程序。
- 缓存机制:MyBatis 内置了一级缓存(SqlSession 级别),并且可以通过配置启用二级缓存(Mapper 级别),有助于提高查询效率。
- 事务管理:虽然 MyBatis 本身不提供完整的事务管理功能,但它可以很容易地集成到 Spring 等框架中,利用这些框架提供的事务管理能力。
- 灵活性:MyBatis 允许你完全控制 SQL,并且可以在需要的时候编写优化过的 SQL 语句,这对性能要求较高的应用非常有用。
- 与 Spring 框架的良好整合:MyBatis 可以方便地与 Spring 框架结合使用,Spring 提供了 MyBatis 的工厂类,可以帮助我们更容易地管理 Bean 和事务。
MyBatis的优缺点?
优点
SQL 灵活性:
- 开发者可以直接编写原生 SQL 语句,这使得你可以完全掌控 SQL,并且可以在必要时进行优化。
易于学习和使用:
- 对于熟悉 SQL 和 JDBC 的开发者来说,MyBatis 的学习曲线较低,因为它的概念和使用相对简单。
支持动态 SQL:
- MyBatis 提供了强大的动态 SQL 功能,允许根据业务逻辑动态构建 SQL 语句。
良好的数据库移植性:
- 只要调整 SQL 语句以适应目标数据库的方言,就可以很容易地切换不同的数据库。
与 Spring 框架整合良好:
- MyBatis 能够很好地与 Spring 框架集成,利用 Spring 的依赖注入、事务管理等功能。
性能优势:
- 因为可以手写优化后的 SQL 语句,所以对于一些高性能需求的应用场景,MyBatis 提供了更好的性能表现。
映射关系清晰:
- 使用
<resultMap>
标签,MyBatis 可以处理复杂的关系映射,如一对一、一对多等关联查询。
缓存机制:
- 内置一级缓存(SqlSession 级别),并支持配置二级缓存来提升查询效率。
缺点
需要手动编写 SQL:
- 这虽然提供了灵活性,但也增加了开发的工作量,特别是在项目规模较大或团队成员对 SQL 不熟悉的环境下。
数据库移植性差:
- SQL 语句依赖于数据库导致数据库移植性差,不能随意更换数据库。
维护成本较高:
- 随着项目的增长,管理和维护大量的 XML 文件中的 SQL 语句可能会变得困难。
不适合快速开发:
- 如果你追求的是快速开发和原型设计,那么相比全自动化的 ORM 框架(如 Hibernate),MyBatis 可能不是最优选择,因为它要求更多的手动工作。
对象-关系映射的复杂度:
- 当涉及到复杂的对象模型时,映射配置可能变得相当复杂,尤其是在处理继承结构或多态关系的情况下。
缺乏对懒加载的支持:
- 默认情况下,MyBatis 不提供懒加载功能,尽管可以通过自定义的方式实现,但这又增加了复杂性和潜在的问题。
不适合小型项目:
- 对于一些非常简单的 CRUD 操作,使用 MyBatis 可能会显得过于笨重,因为需要额外的配置和 SQL 编写。
MyBatis相对JDBC有哪些好处?
MyBatis相对JDBC在代码简化、资源管理、SQL语句管理、动态SQL支持、复杂映射关系处理、高效性、易于整合和扩展以及灵活性等方面都具有显著优势。这些优势使得MyBatis成为现代软件开发中处理数据库交互的重要工具之一。
代码简化和配置优化
减少样板代码
JDBC:使用 JDBC 时,开发者需要编写大量的样板代码来管理数据库连接、创建语句、处理结果集以及关闭资源等。
MyBatis:MyBatis 自动处理这些任务,减少了重复的代码量,让开发者可以专注于业务逻辑。
代码分离:
MyBatis通过配置文件和注解的方式,将SQL语句与Java代码分离。
这种分离大大简化了代码结构,提高了代码的可读性和可维护性。
自动映射:
MyBatis提供了结果映射机制(如
resultMap
),能够自动将数据库查询结果映射为Java对象。这减少了手动处理结果集的繁琐工作。
集中管理:
在MyBatis中,SQL语句被统一放在XML映射文件中或注解中。
这种方式使得开发者能够方便地对SQL语句进行管理和优化。
资源管理优化
连接池管理:
MyBatis使用连接池管理数据库连接,避免了传统JDBC中频繁创建和关闭连接所带来的资源浪费问题。
连接池能够复用数据库连接,提高了系统的性能和稳定性。
SQL语句管理优化
动态SQL支持:
MyBatis支持动态SQL语句的编写,可以根据不同的条件生成不同的SQL语句。
这使得开发者能够更灵活地处理复杂的查询和更新操作,提高了代码的复用性和可维护性。
复杂映射处理:
MyBatis提供了
resultMap
机制,能够处理复杂的Java对象与数据库表之间的映射关系。这使得开发者能够更方便地处理一对一、一对多等复杂的关联查询场景。
高效性和性能优化
轻量级操作:
- MyBatis采用轻量级的JDBC操作来进行数据库交互,执行速度快,并极大降低了内存使用需求。
缓存机制:
MyBatis提供了一级缓存和二级缓存,通过缓存机制来优化性能。
特别是二级缓存,它可以缓存查询结果,避免重复执行相同的SQL语句。
易于整合和扩展
高集成度:
MyBatis可以与多种持久层框架和ORM框架进行集成,如Spring等。
这使得开发者能够在现有技术栈的基础上轻松引入MyBatis,实现与数据库的高效交互。
可扩展性:
- MyBatis提供了多种灵活的插件扩展形式,可以根据自身业务量创建插件,方便于业务实现。
灵活性和定制化开发
灵活性:
- MyBatis不会对开发人员进行过多限制,可以自由灵活地进行SQL映射和CRUD操作。
定制化开发:
- 开发人员可以根据业务需求进行定制化开发,不必因为框架的限制而妥协。
MyBatis 与JPA 有哪些不同?
MyBatis 和 JPA(Java Persistence API)都是用于管理 Java 应用程序与关系型数据库之间交互的持久层框架,但它们在设计理念、使用方式以及适用场景上有显著的不同。以下是 MyBatis 与 JPA 的一些主要区别:
设计理念
- MyBatis:MyBatis 是一个半自动化的 ORM 框架,它允许开发者直接编写 SQL 语句,并将这些 SQL 映射到 Java 对象中。它给予开发人员对 SQL 的完全控制权,适用于需要对 SQL 进行优化或定制化操作的场景。
- JPA:JPA 是一个规范,提供了一套标准的 API 来实现对象关系映射。它是全自动化的 ORM 框架,通常通过实体类和注解来定义表结构和关系,自动生成 SQL 语句,适合那些希望快速开发且不关心底层 SQL 实现的开发者。
使用方式
SQL 编写:
MyBatis 要求开发者手动编写 SQL 语句,支持动态 SQL 和存储过程。
JPA 一般不需要显式地编写 SQL,而是通过 JPQL(Java Persistence Query Language)或者 Criteria API 构建查询。
对象映射:
在 MyBatis 中,对象映射是通过 XML 文件或注解配置的
<resultMap>
来实现的。JPA 则是通过实体类上的注解(如
@Entity
,@Table
,@Column
等)来定义对象和数据库表之间的映射。
特性差异
懒加载:
MyBatis 默认不支持懒加载,但可以通过配置实现。
JPA 支持懒加载,可以减少不必要的数据加载,提高性能。
事务管理:
MyBatis 不自带事务管理功能,但可与 Spring 等框架集成来处理事务。
JPA 提供了内置的事务管理能力,简化了事务处理。
缓存机制:
MyBatis 内置一级缓存,支持二级缓存的配置。
JPA 规范要求实现一级缓存(也称为持久化上下文),并且许多 JPA 提供商还实现了二级缓存。
迁移和支持:
MyBatis 需要手动管理数据库模式的变化。
JPA 提供了诸如 Hibernate Tools 或 EclipseLink 等工具来帮助管理和迁移数据库模式。
性能
MyBatis:由于允许开发者手写SQL,因此能够针对特定查询进行优化。在一些复杂查询或对性能要求极高的场景中,MyBatis的性能通常较优。
JPA:对查询的优化通常不如MyBatis灵活,因为它自动生成SQL查询。尽管JPA实现(如Hibernate)提供了很多优化机制(如懒加载、批量处理等),但对于一些复杂查询,JPA可能不如MyBatis那么高效。JPA在执行时通常会有更多的抽象和开销,尤其是在处理大量数据时,可能导致性能下降。
适用场景
MyBatis:适合于需要高性能、复杂查询或需要精细控制SQL执行的场景。用于对SQL语句有较高要求的场景,如需要优化查询性能或处理复杂业务逻辑。
JPA:适用于大多数传统的CRUD操作。特别是在业务模型复杂或需要与数据库操作解耦的场景中表现优异。适合于希望使用更高层次的抽象来简化开发的场景。
MyBatis和Hibernate的区别有哪些?
MyBatis 和 Hibernate 都是用于 Java 应用程序中处理持久化的框架,但它们在设计理念、使用方式以及适用场景上有显著的区别。
设计理念
- MyBatis:MyBatis 是一个半自动化的 ORM 框架,它允许开发者直接编写 SQL 语句,并将这些 SQL 映射到 Java 对象中。它给予开发人员对 SQL 的完全控制权,适用于需要对 SQL 进行优化或定制化操作的场景。
- Hibernate:Hibernate 是一个全自动化的 ORM 框架,实现了完整的对象关系映射(ORM)。它通过实体类和注解来定义表结构和关系,并自动生成 SQL 语句。Hibernate 提供了更高层次的抽象,减少了开发者与数据库交互的复杂性。
使用方式
SQL 编写
MyBatis:要求开发者手动编写 SQL 语句,支持动态 SQL 和存储过程。
Hibernate:一般不需要显式地编写 SQL,而是通过 HQL(Hibernate Query Language)或者 Criteria API 构建查询。
对象映射
MyBatis:对象映射是通过 XML 文件或注解配置的
<resultMap>
来实现的。Hibernate:通过实体类上的注解(如
@Entity
,@Table
,@Column
等)来定义对象和数据库表之间的映射。
特性差异
懒加载
MyBatis:默认不支持懒加载,但可以通过配置实现。
Hibernate:提供了懒加载功能,默认情况下只加载必要的数据,减少不必要的数据加载,提高性能。
二级缓存
MyBatis:内置一级缓存,支持二级缓存的配置,但需要额外集成第三方缓存组件。
Hibernate:提供了一级缓存(也称为持久化上下文),并且大多数 Hibernate 实现都包含二级缓存的支持。
事务管理
MyBatis:本身不自带事务管理功能,但可与 Spring 等框架集成来处理事务。
Hibernate:提供了内置的事务管理能力,简化了事务处理。
迁移和支持
MyBatis:需要手动管理数据库模式的变化。
Hibernate:提供了诸如 Hibernate Tools 或 EclipseLink 等工具来帮助管理和迁移数据库模式。
适用场景
- MyBatis 更适合那些对 SQL 性能有严格要求的应用程序,或者当应用程序逻辑与特定数据库特性紧密相关时。
- Hibernate 更适合于快速开发 CRUD 应用程序,尤其是当你想要利用全自动化的 ORM 功能时,例如自动化生成数据库表结构和映射。
开发体验
- MyBatis:提供了更多的灵活性和对 SQL 的精确控制,但在某些情况下可能会增加开发和维护的工作量。
- Hibernate:简化了数据库操作,尤其是在处理复杂的对象模型和关系时,但有时可能因为其自动化的程度而显得不够灵活。
MyBatis 的架构设计?
MyBatis 的架构设计可以分为三个主要层次:接口层、数据处理层和基础支撑层。以下是每个层次的核心组件和它们的作用:
API接口层:这是最顶层,主要提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层接收到调用请求后,会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
SqlSession:这是 MyBatis 与数据库交互的关键组件,提供了执行SQL语句、获取映射器(Mapper)、管理事务等功能。通过
SqlSession
,可以执行数据库的增删改查操作,并获取操作结果。Executor:MyBatis 执行器,负责根据SQL语句、参数和映射规则,将SQL语句发送到数据库,并获取结果。Executor 可以分为 SimpleExecutor、ReuseExecutor 和 BatchExecutor 等类型,用于不同的场景和需求。
MappedStatement:保存 SQL 语句的数据结构,其中的类属性都是由解析 XML 文件中的 SQL 标签转化而成。
ParameterHandler:负责将 MyBatis 传递的参数映射到 SQL 语句中。
ResultSetHandler:负责处理 SQL 查询返回的结果集,将结果集转换为 Java 对象。
StatementHandler:负责创建 Statement 的处理器,根据不同的业务创建不同功能的 Statement。
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
Configuration:MyBatis 的配置文件解析和存储的核心组件,负责解析 MyBatis 的配置文件,包括数据库连接信息、映射文件路径、插件、类型处理器等配置信息。
SqlSessionFactory 和 SqlSessionFactoryBuilder:
SqlSessionFactory
是 MyBatis 工作的核心,用于创建SqlSession
对象。SqlSessionFactoryBuilder
会根据配置信息或者代码生成SqlSessionFactory
。TypeHandler:负责 Java 数据类型和 JDBC 数据类型之间的映射和转换。
反射模块:MyBatis 封装出了反射模块,提供了比原生反射更简洁易用的 API 接口,以及对类的元数据增加缓存,提高反射的性能。
类型转换模块:在为 SQL 语句绑定实参时,将 Java 类型转为 JDBC 类型,在映射结果集时再由 JDBC 类型转为 Java 类型。
日志模块:集成主流的日志框架,如 Log4j、Log4j2、Slf4j 等。
资源加载:负责资源的加载,如配置文件、映射文件等。
为什么说Mybatis是半自动ORM映射工具?
MyBatis 提供了一个介于传统 JDBC 和全自动 ORM 框架之间的解决方案,既保持了对 SQL 的精细控制,又简化了对象与数据库之间的映射工作。因此,它被称为“半自动”的 ORM 工具。对于那些既希望简化持久层开发,又不想失去对 SQL 控制权的应用程序而言,MyBatis 是一个非常好的选择。同时,这也意味着开发者需要承担更多的责任来确保 SQL 的正确性和效率。
手动编写 SQL
- MyBatis:开发者需要手动编写 SQL 语句,包括查询、插入、更新和删除等操作。虽然它支持通过注解或 XML 文件来定义这些 SQL 语句,但最终的 SQL 是由开发者编写的。
- 全自动 ORM:如 Hibernate 等框架会自动生成 SQL 语句,开发者不需要直接接触 SQL,只需操作实体类。
灵活的对象映射
- MyBatis:提供了
<resultMap>
或@Results
注解来定义结果集到 Java 对象的映射规则。这使得你可以精确地控制如何将数据库表中的列映射到 Java 类的属性上,同时也支持复杂的关系映射(如一对一、一对多等)。 - 全自动 ORM:通常使用注解或配置文件自动推断并设置对象与数据库表之间的映射关系,减少了对映射规则的手动定义需求。
SQL 的优化和定制
- MyBatis:允许开发者根据具体的业务逻辑和性能要求来优化 SQL 语句,这对于那些对数据库性能有严格要求的应用来说非常重要。
- 全自动 ORM:生成的 SQL 语句可能不总是最优的,因为它们是基于通用规则生成的,未必能考虑到特定场景下的优化需求。
对数据库特性的利用
- MyBatis:由于可以直接编写 SQL,所以可以充分利用特定数据库的功能和特性(例如存储过程、特定的 SQL 函数等),这对于需要紧密耦合数据库特性的应用非常有用。
- 全自动 ORM:试图提供一个跨数据库的抽象层,可能会限制你使用某些数据库特有的功能。
动态 SQL 支持
- MyBatis:内置了强大的动态 SQL 功能,允许根据不同的条件组合 SQL 语句,这种灵活性是全自动 ORM 框架难以提供的。
MyBatis基本要素和核心对象有哪些?
基本要素
核心配置文件(mybatis-config.xml):
MyBatis的核心配置文件,用于配置MyBatis的运行环境,包括数据源、事务管理器、映射文件等。
通过该文件,MyBatis可以解析并加载相关的配置信息,从而初始化MyBatis的运行环境。
SQL映射文件(mapper.xml):
SQL映射文件用于定义SQL语句与Java对象之间的映射关系。
在这些文件中,可以编写具体的SQL语句,并指定这些语句如何与Java对象进行交互。
核心接口和类:
- SqlSessionFactoryBuilder, SqlSessionFactory, SqlSession
核心对象详解
SqlSessionFactoryBuilder:
SqlSessionFactoryBuilder是用于构建SqlSessionFactory的工具类。
它通过读取MyBatis的核心配置文件(mybatis-config.xml)或编程方式构建SqlSessionFactory对象。
SqlSessionFactoryBuilder的生命周期较短,通常在方法体内使用,创建完SqlSessionFactory对象后即可丢弃。
SqlSessionFactory:
SqlSessionFactory是MyBatis的核心对象之一,用于创建SqlSession对象。
它在整个MyBatis应用程序的生命周期中始终存在,并且是单例的。
通过SqlSessionFactory,可以获取到SqlSession对象,进而执行SQL操作。
SqlSession:
SqlSession是MyBatis用于执行SQL操作的对象,类似于JDBC中的Connection。
它包含了执行SQL所需的所有方法,如select、insert、update、delete等。
SqlSession对象不是线程安全的,因此每个线程都应该有自己的SqlSession实例。
在使用完SqlSession后,应该关闭它以释放资源。
此外,虽然在一些资料中没有明确列出,但Configuration对象和Executor对象也是MyBatis中非常重要的核心对象:
Configuration:
Configuration对象用于封装MyBatis的配置信息,包括核心配置文件和SQL映射文件的内容。
它通过XPathParser解析XML配置文件,将配置信息加载到内存中,供MyBatis运行时使用。
Executor:
Executor是MyBatis用于执行SQL操作的核心对象之一。
它通过调用Configuration对象中的Handler(如StatementHandler、ParameterHandler、ResultSetHandler等)来完成具体的数据库操作。
Executor对象在每次创建SqlSession时都会创建,用于执行该SqlSession中的所有SQL操作。
此外,还有一些其他重要的组件,虽然它们可能不被视为“核心”组件,但在 MyBatis 的运行中也扮演着重要的角色:
- Configuration:MyBatis 的配置类,用于存储 MyBatis 的配置信息,如数据库连接信息、Mapper 映射文件的路径等。
- TypeHandler:用于处理 Java 类型与数据库类型之间的转换。
- ResultMap:用于定义查询结果与 Java 对象之间的映射关系。
- TransactionManager:用于管理事务的接口,提供了事务的提交和回滚等方法。
这些核心组件和辅助组件相互配合,共同实现了 MyBatis 框架的核心功能,为开发者提供了便捷、高效的数据库操作方式。
MyBatis 都有哪些 Executor 执行器?
MyBatis提供了3种不同的Executor,分别为SimpleExecutor、ResueExecutor、BatchExecutor,这些Executor都继承至BaseExecutor,BaseExecutor中定义的方法的执行流程及通用的处理逻辑,具体的方法由子类来实现,是典型的模板方法模式的应用。
SimpleExecutor:
- 特点:MyBatis 中最基本的执行器,每次执行 SQL 语句时都会为该语句创建一个新的 Statement 对象,并且用完之后立刻关闭。
- 适用场景:单次执行 SQL 语句的场景,特别是对于那些不需要高频率执行相同 SQL 的操作。
- 优缺点:实现简单直接,不需要管理 Statement 对象的生命周期,但每次执行都会创建新的 Statement,因此在需要频繁执行相同 SQL 语句的场景下,性能表现相对较差。
ReuseExecutor:
- 特点:可以复用 Statement 对象的执行器,通过复用 Statement 对象来减少资源消耗,提高执行效率。
- 执行流程:执行 SQL 语句时,首先检查是否已经存在与该 SQL 语句匹配的 Statement 对象,如果存在则直接复用,如果不存在则创建一个新的 Statement 对象,并将其保存以便下次复用。在整个会话期间,Statement 对象会被复用,直到会话结束时才会关闭。
- 适用场景:频繁执行相同 SQL 语句的场景,尤其是在同一会话中执行大量相同或类似的查询时。
- 优缺点:通过复用 Statement 对象减少了开销,性能优于 SimpleExecutor,但在需要处理大量不同 SQL 语句的场景下,复用的优势可能不明显。
BatchExecutor:
- 特点:用于批量执行 SQL 语句的执行器,通过将多条 SQL 语句积累到一个批处理中,统一提交给数据库执行,从而减少数据库连接的次数,提升性能。
- 执行流程:打开一个数据库连接,并创建一个 Statement 对象。多次调用相同或不同的 SQL 语句时,这些语句会被添加到批处理中,但不会立即执行。当批量操作完成后,调用 Executor.commit() 方法,BatchExecutor 会将批处理中的所有 SQL 语句一次性提交给数据库执行。
- 适用场景:批量插入、更新或删除操作的场景,通过减少数据库交互次数来显著提高性能。
- 注意事项:在批量处理操作后,需要手动执行提交(commit)操作,否则 SQL 语句不会真正执行。同时,BatchExecutor 需要特别注意事务管理和错误处理,因为一次性提交多个 SQL 语句后,如果某个语句失败,可能会影响整个批处理的成功率。
注意
- CachingExecutor:Executor 都严格限制在 SqlSession 生命周期范围内。的批量操作功能。另外,我们知道MyBatis支持一级缓存和二级缓存,当MyBatis开启了二级缓存功能时,会使用CachingExecutor对上面3个进行装饰,为执行查询操作增加二级缓存功能,这是装饰器模式的应用。
Executor 的选择
在 MyBatis 配置中,可以通过设置 defaultExecutorType
属性来指定默认使用的执行器类型:
<!--取值范围 SIMPLE, REUSE, BATCH -->
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
或者在打开 SqlSession
时动态指定:
// 另外一种直接通过Java对方法赋值的方式session = factory.openSession(ExecutorType.BATCH);ExecutorType 是一个枚举,它只有三个值 SIMPLE, REUSE, BATCH
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
MyBatis工作流程?
MyBatis的工作流程是一个有序的过程,它涉及多个步骤和组件的协同工作。以下是MyBatis工作流程的详细解释:
读取MyBatis配置文件:
MyBatis的核心配置文件是
mybatis-config.xml
,它包含了MyBatis的运行环境配置,如数据库连接信息、事务管理器、类型别名、类型处理器、插件等。MyBatis会首先读取并解析这个配置文件,将其封装成一个
Configuration
对象,供后续步骤使用。
加载映射文件:
映射文件(Mapper XML文件)是MyBatis的重要组成部分,它定义了SQL语句与Java对象之间的映射关系。
MyBatis会在配置文件中指定映射文件的位置,并加载这些文件。每个映射文件通常对应数据库中的一张表,并包含了该表相关的SQL语句。
构造会话工厂(SqlSessionFactory):
通过MyBatis的环境配置信息和映射文件,MyBatis会构造出一个会话工厂
SqlSessionFactory
。SqlSessionFactory
是MyBatis的核心对象之一,它用于创建SqlSession
对象,后者是执行SQL语句的接口。
创建会话对象(SqlSession):
由
SqlSessionFactory
创建出SqlSession
对象。SqlSession
是非线程安全的,因此每个线程都应该有自己的SqlSession
实例。SqlSession
提供了执行SQL语句、获取Mapper接口实例和管理事务的方法。
Executor执行器:
MyBatis底层定义了一个
Executor
接口来操作数据库。Executor
是MyBatis执行SQL语句的核心组件。SqlSession
使用Executor
来执行SQL语句。Executor
会根据SqlSession
传递的参数动态地生成需要执行的SQL语句,并负责查询缓存的维护。MyBatis提供了多种类型的
Executor
,如SimpleExecutor
(每次执行都会开启一个新的Statement对象)、ReuseExecutor
(会重用预处理语句)和BatchExecutor
(执行更新操作时会将所有的SQL语句添加到一个批处理集合中,等待统一执行)。
MappedStatement对象:
在
Executor
执行SQL语句时,会用到MappedStatement
对象。MappedStatement
是对映射信息的封装,它包含了要执行的SQL语句的ID、参数类型、返回类型、SQL语句本身以及结果映射等信息。MappedStatement
是MyBatis在执行SQL语句时的重要依据。
输入参数映射:
在执行SQL语句之前,MyBatis需要将Java对象中的参数映射到SQL语句中。这个过程称为输入参数映射。
输入参数可以是基本数据类型、集合类型(如Map、List)或POJO类型。MyBatis会根据
MappedStatement
中的参数类型信息来进行映射。
执行SQL语句:
在完成了输入参数映射后,
Executor
会执行SQL语句。这个过程涉及到JDBC的底层操作,如创建Statement对象、设置参数、执行SQL语句等。执行SQL语句后,MyBatis会获取到数据库返回的结果集。
输出结果映射:
MyBatis需要将数据库返回的结果集映射回Java对象中。这个过程称为输出结果映射。
输出结果可以是基本数据类型、集合类型或POJO类型。MyBatis会根据
MappedStatement
中的返回类型信息来进行映射。
提交/回滚事务
提交事务:如果启用了事务并且操作成功完成,则调用
SqlSession.commit()
提交更改。回滚事务:如果发生异常,可以通过调用
SqlSession.rollback()
来撤销所有未提交的操作。
处理缓存:
MyBatis支持一级缓存和二级缓存。一级缓存是
SqlSession
级别的缓存,二级缓存是SqlSessionFactory
级别的缓存。在执行SQL语句之前,MyBatis会先检查缓存中是否有可用的数据。如果有,则直接返回缓存中的数据;如果没有,则执行SQL语句并从数据库中获取数据。
执行完SQL语句后,MyBatis会将结果集缓存起来,以便后续使用。
关闭会话:
- 在使用完
SqlSession
后,应该关闭它以释放资源。关闭SqlSession
会关闭与之关联的数据库连接等资源。
- 在使用完
MyBatis的缓存机制?
MyBatis的缓存机制是其提高查询效率、减少数据库压力的重要手段。MyBatis缓存分为一级缓存和二级缓存,以下是关于MyBatis缓存机制的详细介绍:
一级缓存
定义与特点:
一级缓存是SqlSession级别的缓存,也称为本地缓存。
默认情况下,一级缓存是开启的,且不能被关闭。
每个SqlSession对象都有自己的一级缓存,且各个SqlSession对象的一级缓存是相互独立的。
与
SqlSession
的生命周期相同,当SqlSession
关闭或提交事务后,一级缓存会被清空。
工作原理:
- 当执行一个查询时,MyBatis 会先检查一级缓存中是否存在该查询的结果。
- 如果存在(Key为MapperId+Offset+Limit+SQL+所有的入参),则直接返回缓存中的结果;如果不存在,则执行 SQL 查询并将结果存入缓存。
- 在同一
SqlSession
中,后续对相同数据的查询将直接从缓存中读取。 - 一旦
SqlSession
被关闭或提交/回滚了事务,一级缓存中的内容就会被清除。
失效情况:
当执行insert、update或delete等DML操作,并commit后,一级缓存中的数据会被清空,以确保缓存中的数据与数据库中的数据保持一致。
如果手动调用了sqlSession.clearCache()方法,也会清空当前SqlSession的一级缓存。
说明:
一级缓存对于短生命周期的
SqlSession
或者频繁更新的数据表可能效果有限。对于长时间存在的
SqlSession
,应谨慎使用,以避免内存泄漏。
二级缓存
定义与特点:
二级缓存是mapper级别的缓存,也称为全局缓存。
二级缓存默认是关闭的,需要显式地在 MyBatis 配置文件或 Mapper XML 文件中启用,并且可以为每个 Mapper 接口单独配置。
二级缓存是基于namespace(即mapper的命名空间)的,同一个namespace的mapper共享同一个二级缓存,即使是不同
SqlSession
。MyBatis 提供了内置的缓存实现(如
PerpetualCache
),也允许集成第三方缓存框架(如 Ehcache, Caffeine 等)。
工作原理:
当一个SqlSession执行查询操作后,如果一级缓存中没有找到结果,且二级缓存已开启,MyBatis会将查询结果缓存到二级缓存中。
当另一个SqlSession执行相同的查询时,会先在一级缓存中查找,如果一级缓存中没有找到,则会去二级缓存中查找。
如果二级缓存中找到了结果,则直接返回结果,无需再次访问数据库。
配置与属性:
**全局配置,**在MyBatis的全局配置文件中,通过设置
<setting name="cacheEnabled" value="true"/>
来开启二级缓存。**Mapper 级别配置,**在mapper的XML配置文件中,通过添加
<cache/>
标签来配置二级缓存。<cache/>
标签可以配置多个属性:eviction:指定缓存回收策略,如 LRU(最近最少使用)、FIFO(先进先出)等。
flushInterval:自动刷新缓存的时间间隔(毫秒),超过此时间未访问的数据项将被移除。
size:缓存的最大条目数。
readOnly:设置为
true
表示只读模式,禁止修改缓存中的数据;设置为false
则允许写操作影响缓存。
说明:
二级缓存适合用于那些很少更改的数据,例如字典表、静态配置等。
对于频繁更新的数据表,应该禁用二级缓存,或者使用适当的缓存失效策略,确保数据的一致性。
xml<!-- 全局配置 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> <!-- Mapper 级别配置 --> <mapper namespace="com.example.UserMapper"> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> <!-- ... --> </mapper>
对于 Mybatis 的缓存机制需注意
- MyBatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,即使用相同的namespace、sql语句和参数进行查询时,才会命中缓存。
- 对于不同的namespace,其查询的数据会放在自己对应的缓存中,是相互独立的。
- MyBatis提供了Cache接口和多个实现类,允许开发者自定义二级缓存的实现。
- 在使用二级缓存时,需要确保POJO类实现了序列化接口,因为MyBatis在缓存对象时会进行序列化操作。
MyBatis中二级缓存的缺点?
MyBatis 的二级缓存(也称为 Mapper 级别缓存)确实能够显著提升查询性能,特别是在多用户并发读取相同数据的情况下。然而,它也有一些潜在的缺点和局限性,开发者在使用时需要注意这些问题:
数据一致性问题
延迟更新:由于二级缓存存储的是旧的数据副本,如果数据在数据库中被其他进程或事务修改了,而这些更改没有及时反映到缓存中,则会导致读取到陈旧数据的问题。
分布式环境下的同步:在一个分布式的系统中,多个应用实例可能会共享同一个缓存区域。如果没有适当的缓存失效策略或同步机制,不同实例之间的缓存可能会出现不一致的情况。
内存占用
缓存膨胀:如果不正确地配置缓存大小或者没有合理的淘汰策略,缓存可能会不断增长,最终消耗过多的内存资源,导致应用程序性能下降甚至崩溃。
缓存污染:频繁变化的数据被缓存后,可能很快就会变得无效,但仍然占据着宝贵的内存空间,降低了缓存的有效利用率。
缓存问题:
- **缓存过期:**二级缓存中的数据可能因为时间过期或其他原因而失效,导致查询结果不准确。
- 缓存穿透问题:如果一个查询请求的结果在数据库中不存在,那么每次请求都会穿透缓存,导致缓存失效。这会增加数据库的访问压力,并降低缓存的命中率。
复杂性增加
配置与管理难度:启用二级缓存后,需要额外考虑缓存的配置、监控和维护工作。例如,如何设置合适的缓存回收策略 (
eviction
)、刷新间隔 (flushInterval
) 和最大条目数 (size
) 等参数。集成第三方缓存框架:虽然 MyBatis 支持多种内置缓存实现,但在某些情况下,你可能希望集成更强大的第三方缓存解决方案(如 Ehcache, Redis)。这会引入额外的技术栈依赖,并且需要处理更多的配置细节。
不适合所有场景
频繁更新的数据:对于那些经常发生变化的数据表来说,使用二级缓存反而可能导致更多问题,比如增加了不必要的复杂性和开销,同时也难以保证数据的一致性。
个性化查询:当每个用户的查询条件都非常独特时,缓存命中率可能会非常低,使得缓存的效果大打折扣。
事务边界
- 跨SqlSession的事务管理:二级缓存的作用范围超出了单个
SqlSession
,这意味着在某些复杂的业务逻辑中,你需要特别注意事务边界,确保不会因为缓存的存在而导致意外的行为。
- 跨SqlSession的事务管理:二级缓存的作用范围超出了单个
对象关系映射问题:
- 关联对象加载:MyBatis使用了对象关系映射(ORM)的方式来操作数据,而二级缓存只能缓存查询结果,不能缓存对象的关系。这可能导致在查询关联对象时,需要从数据库中重新加载关联对象的数据,从而降低了缓存的利用率。
序列化问题
- 对象序列化/反序列化开销:如果缓存中的对象需要在网络上传输(例如分布式缓存),那么每次读写都会涉及到对象的序列化和反序列化操作,这可能会带来额外的性能开销。
对象更新问题:
- **对象更新管理:**如果在更新一个对象时,没有正确地清除缓存中的对应数据,可能会导致查询到过期的数据。这需要开发者在更新对象时,注意同步更新缓存中的数据,以确保数据的一致性。
MyBatis如何处理延迟加载?
MyBatis 的延迟加载(Lazy Loading)实现机制依赖于动态代理和特定的配置来确保只有在真正需要时才加载关联对象的数据。MyBatis 的延迟加载机制减少了不必要的数据传输,提高了应用性能。正确理解和配置延迟加载可以帮助你构建更加高效的应用程序,但同时也需要注意潜在的问题,如 SqlSession
生命周期、事务管理和序列化挑战。
配置与初始化
首先,延迟加载需要在全局配置文件 mybatis-config.xml
中启用,并且可以通过设置 lazyLoadingEnabled
和 aggressiveLazyLoading
参数来控制其行为。
lazyLoadingEnabled
:是否启用延迟加载,默认为false
。aggressiveLazyLoading
:当设置为true
时,任何对延迟加载属性的访问都会触发所有相关联的对象被加载;当设置为false
时,则只加载实际访问的属性。从 MyBatis 3.5.0 开始,这个选项已被弃用,取而代之的是更细粒度的控制方式。
<settings>
<!-- 启用延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 禁用激进的延迟加载(默认是 true,即开启) -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
映射配置
在定义结果映射时,可以使用 <association>
或 <collection>
标签中的 fetchType
属性来指定哪些关联应该延迟加载。
XML 配置:
<resultMap id="userResult" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<!-- 定义延迟加载的关联 -->
<association property="profile" javaType="Profile" select="selectProfile" fetchType="lazy"/>
<collection property="orders" ofType="Order" select="selectOrdersByUserId" fetchType="lazy"/>
</resultMap>
注解配置:
@Results(id = "userResult", value = {
@Result(property = "id", column = "user_id"),
@Result(property = "name", column = "user_name"),
@Result(property = "profile", javaType = Profile.class, one = @One(select = "selectProfile", fetchType = FetchType.LAZY)),
@Result(property = "orders", javaType = List.class, many = @Many(select = "selectOrdersByUserId", fetchType = FetchType.LAZY))
})
禁用延迟加载
在某些情况下,可能需要禁用延迟加载,例如,在批量加载数据时。可以通过设置 fetchType="eager"
或在全局配置中禁用延迟加载来实现。
<association property="address" column="address_id" javaType="Address" select="selectAddressById" fetchType="eager"/>
延迟加载的实现机制
MyBatis 使用代理对象来实现延迟加载。当访问一个延迟加载的属性时,MyBatis 会动态地创建一个代理对象来代理该属性的访问。当真正访问该属性时,代理对象会触发 SQL 查询从数据库中加载数据。
假设有一个 User
类,它有一个延迟加载的 Address
属性:
public class User {
private Integer id;
private String username;
private Address address; // 延迟加载
// getters and setters
}
当访问 user.getAddress()
时,如果 address
属性是延迟加载的,MyBatis 会检查是否已经加载了 address
。如果还没有加载,MyBatis 会执行配置的 selectAddressById
查询来加载 Address
对象。
动态代理
- MyBatis使用Java动态代理或CGLIB来实现延迟加载。
- 当MyBatis解析
ResultMap
并发现某个属性配置了lazy="true"
时,它会在加载这个对象时,为该属性创建一个代理对象。这个代理对象可以是一个Java动态代理(基于接口的)或者是CGLIB代理(基于子类的)。 - 代理对象在被实际访问时才会触发数据库查询,加载真实的数据。
核心组件:
- 代理对象:MyBatis为延迟加载的属性创建代理对象,这个代理对象并不包含实际的数据,而是仅仅记录下要执行的SQL语句及其参数。
- Invoker接口:这是MyBatis内部的一个接口,用于执行代理对象的延迟加载逻辑。它封装了SQL的执行和结果的返回。
- 延迟加载触发器:当代理对象的属性被访问时,触发器启动,调用Invoker接口,执行延迟加载的SQL语句,从数据库中获取数据并填充到原始对象中。
实际执行过程:
- 当应用程序首次访问该代理对象的属性时,例如调用
user.getAddress()
,MyBatis通过代理机制捕获这个访问请求。 - 代理对象的Invoker接口在捕获到访问请求后,立即执行预先定义好的SQL查询,从数据库中获取实际的数据。
- 查询结果返回后,MyBatis将结果映射到目标对象(如
Address
),并填充到原始对象的相应属性中。 - 在延迟加载完成后,MyBatis会将代理对象替换为实际的查询结果对象,后续对该属性的访问将直接返回已经加载的数据,而不会再次触发延迟加载。
注意事项
性能权衡:虽然延迟加载可以提高性能,但过多的延迟加载可能会导致大量且频繁触发额外的 SQL 查询,反而可能导致性能下降,称为“N+1 查询问题”。因此,需要谨慎使用延迟加载。
事务管理:延迟加载通常跨越多个数据库访问,因此需要在事务管理上进行适当的处理,以确保数据的一致性和完整性。
**SqlSession 生命周期:**为了支持延迟加载,
SqlSession
必须保持打开状态,直到所有延迟加载的属性都被访问完毕。一旦SqlSession
关闭,尝试访问未加载的延迟属性将导致异常,因为此时已经没有可用的数据库连接来执行额外的查询。缓存:MyBatis 提供了二级缓存机制,可以进一步减少数据库访问,提高性能。
序列化问题:如果需要序列化包含延迟加载属性的对象,请特别注意,因为序列化过程中可能会触发延迟加载,进而导致数据库连接问题或其他意外行为。你可以通过实现
Serializable
接口并在必要时手动加载延迟属性来避免这种情况。
MyBatis有哪些常用注解?
这些常用注解分为三大类:SQL语句映射,结果集映射和关系映射。
@Select注解:实现查询功能
@Select("Select * from user")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "sex", property = "sex"),
@Result(column = "age", property = "age")
})
List<User> queryAllUser();
@SelectKey注解:插入后,获取id的值
以MySQL为例,MySQL在插入一条数据后,使用select last_insert_id() 可以取到最后生成的主键。注意:before属性,默认是true,在执行插入语句之前,执行select last_insert_id()。如果设置为false,则在插入这个语句之后,执行select last_insert_id()
@Insert("insert into user(id,name) values(#{id},#{name})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
@SelectKey(statement = "select last_insert_id()" ,keyProperty = "id",keyColumn = "id",resultType = int.class,before = false)
public int insert(User user);
@Insert:实现新增功能
@Insert("insert into user(id,name) values(#{id},#{name})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
public int insert(User user);
@Update注解:实现更新功能
@Update("update user set name= #{name},sex = #{sex},age =#{age} where id = #{id}")
void updateUserById(User user);
@Delete注解:实现删除功能
@Delete("delete from user where id =#{id}")
void deleteById(Integer id);
结果集映射
@Result,@Results,@ResultMap是结果集映射的三大注解。
@Results各个属性的含义,id为当前结果集声明唯一标识,value值为结果集映射关系,@Result代表一个字段的映射关系,column指定数据库字段的名称,property指定实体类属性的名称,jdbcType数据库字段类型,@Result里的id值为true表明主键,默认false;使用@ResultMap来引用映射结果集,其中value可省略。
声明结果集映射关系代码:
@Select({"select id, name, class_id from student"})
@Results(id="studentMap", value={
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="name", property="name", jdbcType=JdbcType.VARCHAR),
@Result(column="class_id ", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();
引用结果集代码:
@Select({"select id, name, class_id from student where id = #{id}"})
@ResultMap(value="studentMap")
Student selectById(integer id);
关系映射
@one注解:用于一对一关系映射
@Select("select * from student")
@Results({
@Result(id=true,property="id",column="id"),
@Result(property="name",column="name"),
@Result(property="age",column="age"),
@Result(property="address",column="address_id",one=@One(select="cn.mybatis.mydemo.mappers.AddressMapper.getAddress"))
})
public List<Student> getAllStudents();
@many注解:用于一对多关系映射
@Select("select * from t_class where id=#{id}")
@Results({
@Result(id=true,column="id",property="id"),
@Result(column="class_name",property="className"),
@Result(property="students", column="id", many=@Many(select="cn.mybatis.mydemo.mappers.StudentMapper.getStudentsByClassId"))
})
public Class getClass(int id);
MyBatis配置文件(mybatis-config.xml)的结构?
MyBatis 配置文件(mybatis-config.xml)的结构是 MyBatis 框架运行的基础,它定义了 MyBatis 的全局配置信息和映射文件的加载路径等关键设置。以下是 MyBatis 配置文件(mybatis-config.xml)的详细结构:
<?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>
<!-- 属性配置 -->
<properties>
<!-- 可以引入外部属性文件,例如数据库连接信息 -->
<property resource="db.properties"/>
<!-- 或者直接在properties标签内定义属性 -->
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<!-- 全局设置 -->
<settings>
<!-- 配置项示例 -->
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 更多配置项... -->
</settings>
<!-- 类型别名 -->
<typeAliases>
<!-- 为Java类型设置别名 -->
<typeAlias type="com.example.model.User" alias="User"/>
<!-- 更多类型别名... -->
</typeAliases>
<!-- 类型处理器 -->
<typeHandlers>
<!-- 自定义类型处理器 -->
<!-- 需要实现org.apache.ibatis.type.TypeHandler接口或继承org.apache.ibatis.type.BaseTypeHandler类 -->
<!-- <typeHandler handler="com.example.type.MyTypeHandler"/> -->
<!-- 更多类型处理器... -->
</typeHandlers>
<!-- 对象工厂 -->
<!-- <objectFactory type=""/> -->
<!-- 插件 -->
<plugins>
<!-- 插件配置 -->
<!-- <plugin interceptor="com.example.plugin.MyPlugin"/> -->
<!-- 更多插件... -->
</plugins>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源 -->
<dataSource type="POOLED">
<!-- 引用properties中定义的属性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<!-- 更多环境配置... -->
</environments>
<!-- 数据库厂商标识 -->
<!-- <databaseIdProvider type=""/> -->
<!-- 映射器 -->
<mappers>
<!-- 通过resource属性指定映射文件的位置 -->
<mapper resource="com/example/mapper/UserMapper.xml"/>
<!-- 或者通过class属性指定Mapper接口的全限定名(需要Mapper接口和映射文件有相同的命名空间) -->
<!-- <mapper class="com.example.mapper.UserMapper"/> -->
<!-- 或者通过包扫描方式加载Mapper接口和映射文件 -->
<!-- <package name="com.example.mapper"/> -->
<!-- 更多映射器... -->
</mappers>
</configuration>
结构解析
- 根元素
<configuration>
:整个 MyBatis 配置文件的根节点,包含所有的配置信息。 <properties>
:用于配置外部属性文件,可以将数据库连接信息等配置在外部文件中,便于管理和维护。也可以直接在<properties>
标签内定义属性。<settings>
:用于配置 MyBatis 的全局设置,如缓存、延迟加载、驼峰命名等。这些设置会改变 MyBatis 的运行时行为。<typeAliases>
:用于定义类型别名,可以简化 XML 配置文件中的类全限定名。<typeHandlers>
:用于定义 Java 类型与数据库中的数据类型之间的转换关系。可以自定义类型处理器,需要实现org.apache.ibatis.type.TypeHandler
接口或继承org.apache.ibatis.type.BaseTypeHandler
类。<objectFactory>
(可选):用于配置 MyBatis 创建对象实例的工厂类。<plugins>
:用于配置 MyBatis 的插件,插件可以修改 MyBatis 的内部运行规则。<environments>
:用于配置 MyBatis 的多套运行环境,可以配置多个环境变量,比如使用多数据源时,就需要配置多个环境变量。每个环境变量包含事务管理器和数据源配置。<databaseIdProvider>
(可选):用于配置数据库厂商标识,MyBatis 可以根据数据库厂商的不同执行不同的 SQL 语句。<mappers>
:用于配置 SQL 映射文件,可以使用resource
、url
或class
来指定映射文件的位置或 Mapper 接口的全限定名。也可以使用包扫描方式加载 Mapper 接口和映射文件。
注意事项
- MyBatis 配置文件的元素节点是有一定顺序的,必须按照上述顺序进行配置,否则会编译错误。
- 在配置文件中可以通过
${}
引用<properties>
中定义的属性值。 - 如果属性不止在一个地方配置,MyBatis 会按照以下顺序来加载:首先在
<properties>
元素体内指定的属性被读取;然后根据<properties>
元素中的resource
属性读取类路径下属性文件或根据url
属性指定的路径读取属性文件,并覆盖已读取的同名属性;最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。因此,通过方法参数传递的属性具有最高优先级,resource
/url
属性中指定的配置文件次之,最低优先级的是<properties>
属性中指定的属性。
以上就是 MyBatis 配置文件(mybatis-config.xml)的详细结构和解析。
MyBatis 映射器(XML)标签有哪些?
MyBatis 的映射器(Mapper XML 文件)用于定义 SQL 语句及其与 Java 对象之间的映射关系。这些映射文件包含了一系列的元素,每个元素都有其特定的作用和用途。以下是 MyBatis 映射器 XML 文件中常用的主要元素:
<mapper>
根元素
- 作用:作为映射文件的根元素,指定命名空间(namespace),该命名空间通常与对应的 Mapper 接口名称相匹配。
- 属性:
namespace
:指定一个唯一的命名空间,通常是 Mapper 接口的全限定名。
<mapper namespace="com.example.UserMapper">
<!-- 其他元素 -->
</mapper>
SQL 操作语句元素
<select>
作用:定义查询操作,返回结果集可以是单个对象、列表或映射。
属性:
id
:唯一标识符,对应 Mapper 接口中的方法名。parameterType
:参数类型。resultType
或resultMap
:结果类型或结果映射。
<insert>
作用:定义插入操作,并可选择性地获取自动生成的主键。
属性:
id
:唯一标识符。parameterType
:参数类型。useGeneratedKeys
和keyProperty
:用于获取自动生成的主键值。
<update>
作用:定义更新操作。
属性:
id
:唯一标识符。parameterType
:参数类型。
<delete>
作用:定义删除操作。
属性:
id
:唯一标识符。parameterType
:参数类型。
<select id="selectUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name, email) VALUES (#{name}, #{email})
</insert>
<update id="updateUser" parameterType="User">
UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
</update>
<delete id="deleteUser" parameterType="int">
DELETE FROM users WHERE id = #{id}
</delete>
<sql>
定义 SQL 片段
- 作用:定义可重用的 SQL 片段,可以在其他 SQL 语句中通过
<include>
引用来使用。 - 属性:
id
:唯一标识符。
<sql id="userColumns">
id, username, password
</sql>
<select id="selectUsers" resultType="map">
SELECT <include refid="userColumns"/> FROM users
</select>
<resultMap>
结果映射
作用:定义从数据库列到 Java 属性的复杂映射规则,支持嵌套结果、关联对象等。
子元素:
<id>
和<result>
:分别映射主键和普通字段。<association>
和<collection>
:用于处理一对一和一对多的关系。
<resultMap id="userResult" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<association property="profile" javaType="Profile" column="profile_id"/>
<collection property="orders" ofType="Order" column="user_id"/>
</resultMap>
动态 SQL 元素
<if>
,<choose>
,<when>
,<otherwise>
,<foreach>
,<trim>
,<set>
,<bind>
:用于构建动态 SQL 语句,根据条件添加或移除 SQL 片段。
<select id="findActiveBlogWithTitleLike" parameterType="map" resultType="Blog">
SELECT * FROM BLOG
WHERE state = 'ACTIVE'
<if test="title != null">
AND title like #{title}
</if>
</select>
<cache>
二级缓存配置
作用:在 Mapper 级别上配置二级缓存。
属性:
eviction
:指定缓存回收策略,如 LRU(最近最少使用)、FIFO(先进先出)等。flushInterval
:自动刷新缓存的时间间隔(毫秒),超过此时间未访问的数据项将被移除。size
:缓存的最大条目数。readOnly
:设置为true
表示只读模式,禁止修改缓存中的数据;设置为false
则允许写操作影响缓存。
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<cache-ref>
引用其他命名空间的缓存
- 作用:引用另一个命名空间的缓存配置,实现缓存共享。
- 属性:
namespace
:要引用的命名空间。
<cache-ref namespace="com.example.OtherMapper"/>
<parameterMap>
参数映射(已弃用)
- 作用:定义如何将输入参数映射到 SQL 语句中的占位符。此元素在新版本中已被弃用,推荐直接在 SQL 语句中使用
#{}
占位符。
Mybatis 映射文件和内部数据结构之间的映射关系?
MyBatis XML映射文件
MyBatis的XML映射文件是框架配置的重要组成部分,它主要用于定义SQL语句、结果映射规则、参数映射规则等内容。每个映射文件通常对应一个Mapper接口,描述了与数据库交互的具体细节。XML映射文件的主要元素包括:
- namespace:指定当前映射文件对应的Mapper接口的命名空间。
- sql:定义可复用的SQL片段。
- resultMap:定义数据库查询结果与Java对象之间的映射关系。
- parameterMap(较少使用):定义SQL语句参数与Java对象之间的映射关系。在现代MyBatis配置中,通常使用注解或直接在SQL中指定参数类型,而不是依赖parameterMap。
- SQL语句(select, insert, update, delete):定义具体的SQL查询、插入、更新、删除语句。
MyBatis内部数据结构
MyBatis在运行时会将XML映射文件中的配置信息解析并存储到相应的内部数据结构中,这些数据结构在框架运行期间用于执行SQL语句、映射查询结果等操作。主要的内部数据结构包括:
- Configuration:MyBatis的核心配置对象,包含了全局配置信息和所有映射文件的信息。
- MappedStatement:描述一条具体的SQL语句及其相关信息(如SQL类型、参数映射、结果映射等)。
- ResultMap:定义数据库结果集与Java对象之间的映射关系。
- SqlSource:封装了SQL语句的源代码及其动态生成的SQL语句。
- ParameterMap(较少使用):描述SQL语句中的参数信息及其与Java对象的映射关系。在现代MyBatis中,其地位已被注解或直接在SQL中指定参数类型所取代。
- Cache:表示MyBatis的二级缓存,每个映射文件可以有一个Cache对象。
- TypeHandler:处理Java类型与JDBC类型之间转换的处理器。
XML映射文件与内部数据结构的映射关系
- namespace与Configuration:在XML映射文件中,namespace元素用于指定映射文件所对应的Mapper接口的命名空间。在MyBatis中,每个映射文件对应一个Configuration对象中的Mapper条目。namespace用于在Configuration对象中注册和检索对应的Mapper。
- sql与SqlSource:MyBatis会将sql元素的内容解析并存储为SqlSource对象。SqlSource封装了SQL语句的源代码,可以在select、insert、update、delete等元素中通过引用来复用。
- resultMap与ResultMap:resultMap是MyBatis中最重要的元素之一,它定义了数据库查询结果与Java对象之间的映射关系。在内部,MyBatis会将resultMap解析成ResultMap对象。ResultMap包含了映射关系的详细信息,如id、result标签所定义的列与对象属性的映射。ResultMap还可以处理复杂的映射,如嵌套结果集、关联对象的映射等。
- select、insert、update、delete与MappedStatement:这些元素用于定义具体的SQL语句,并会被解析成MappedStatement对象。MappedStatement包含了SQL语句的所有信息,包括SQL语句的文本、输入参数的类型、输出结果的映射关系(引用ResultMap)、执行的SQL类型(如SELECT、INSERT等)等。
- parameterMap与ParameterMap(较少使用):尽管在现代MyBatis配置中不常用,但parameterMap还是MyBatis内部的一个重要结构。它会被解析成ParameterMap对象,包含了参数的类型和位置。然而,在现代MyBatis中,通常使用注解或直接在SQL中指定参数类型。
- cache与Cache:在XML映射文件中,cache元素用于定义二级缓存。MyBatis解析cache元素后,会生成一个Cache对象并将其与MappedStatement关联。
MyBatis的XML映射文件中不同映射文件id是否可以重复?
MyBatis的XML映射文件中,不同映射文件的id是否可以重复,取决于是否配置了namespace(命名空间)。
配置了namespace的情况
如果为不同的XML映射文件配置了namespace,那么在这些文件中id是可以重复的。MyBatis在解析和使用时会结合每个映射文件的namespace与id来形成唯一的标识符。因此,只要namespace不同,即使id相同也不会产生冲突。这种方式允许开发者在不同的映射文件中使用相同的id来标识不同的SQL语句,只要它们位于不同的namespace下。
未配置namespace的情况
如果没有为XML映射文件配置namespace,那么id是不能重复的。因为在没有namespace区分的情况下,相同的id会导致MyBatis解析时产生冲突,无法准确地定位到对应的SQL映射语句,进而影响程序的正常运行。
最佳实践
- 命名一致性:为了确保代码的可读性和可维护性,建议namespace应与Mapper接口的全限定名一致。
- id命名具有描述性:虽然id在不同namespace下可以重复,但最好让每个id具有描述性,明确其用途。例如,避免使用过于通用的id如
selectById
,而是使用更具描述性的名称如selectUserById
、selectOrderById
。 - 避免无意义的重复:在一个大型项目中,过多的重复id可能导致管理混乱。因此,尽量避免无意义的id重复,尤其是在不同的namespace中使用相同的SQL逻辑时。
Mybatis 映射文件中include 是否可以引用定义在标签后面的内容?
可以,虽然Mybatis 解析 Xml 映射文件是按照顺序解析的,但是,被引用的 B 标签依然可以定义在任何地方。原理是,Mybatis 解析 A 标签,发现 A 标签引用了 B 标签,如果 B 标签尚未解析到,此时,Mybatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,待所有标签解析完毕,Mybatis 会重新解析那些被标记为未解析的标签,此时再解析 A 标签时,B 标签已经存在,A 标签也就可以正常解析完成了。
MyBatis 动态SQL?
MyBatis 支持定制化 SQL、存储过程以及高级映射。MyBatis 提供了动态 SQL 的功能,使得在编写 SQL 语句时可以根据不同的条件动态地生成 SQL 语句,从而避免了大量的 SQL 重复编写。其执行原理为,使用OGNL(对象图导航语言)从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。
种动态sql标签。
if标签
条件判断,常常与 test 属性联合使用
当判断条件为 true 时,才会执行所包含的 SQL 语句。可多个 if 语句同时使用。
<select id="selectAllWebsite" resultMap="myResult">
select id,name,url from website where 1=1
<if test="name != null">
AND name like #{name}
</if>
<if test="url!= null">
AND url like #{url}
</if>
</select>
- choose、when和otherwise标签
- MyBatis 中动态语句 choose-when-otherwise 类似于 Java 中的 switch-case-default 语句。
<mapper namespace="net.biancheng.mapper.WebsiteMapper">
<select id="selectWebsite"
parameterType="net.biancheng.po.Website"
resultType="net.biancheng.po.Website">
SELECT id,name,url,age,country FROM website WHERE 1=1
<choose>
<when test="name != null and name !=''">
AND name LIKE CONCAT('%',#{name},'%')
</when>
<when test="url != null and url !=''">
AND url LIKE CONCAT('%',#{url},'%')
</when>
<otherwise>
AND age is not null
</otherwise>
</choose>
</select>
</mapper>
- where标签
- where 标签主要用来简化 SQL 语句中的条件判断。可以自动处理 AND/OR 条件,语法如下。
<select id="selectWebsite" resultType="net.biancheng.po.Website">
select id,name,url from website
<where>
<if test="name != null">
AND name like #{name}
</if>
<if test="url!= null">
AND url like #{url}
</if>
</where>
</select>
- set标签
- update 语句可以使用 set 标签动态更新列。set 标签可以为 SQL 语句动态的添加 set 关键字,剔除追加到条件末尾多余的逗号。
<update id="updateWebsite"
parameterType="net.biancheng.po.Website">
UPDATE website
<set>
<if test="name!=null">name=#{name}</if>
<if test="url!=null">url=#{url}</if>
</set>
WHERE id=#{id}
</update>
foreach标签
foreach 标签用于循环语句,它很好的支持了数据和 List、set 接口的集合,并对此提供遍历的功能。
foreach 标签主要有以下属性:
item:表示集合中每一个元素进行迭代时的别名。
index:指定一个名字,表示在迭代过程中每次迭代到的位置。
open:表示该语句以什么开始(既然是 in 条件语句,所以必然以
(
开始)。separator:表示在每次进行迭代之间以什么符号作为分隔符(既然是 in 条件语句,所以必然以
,
作为分隔符)。close:表示该语句以什么结束(既然是 in 条件语句,所以必然以
)
开始)。
使用 foreach 标签时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:
如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
<foreach item="item" index="index" collection="list|array|map key" open="(" separator="," close=")">
参数值
</foreach>
<select id="selectWebsite"
parameterType="net.biancheng.po.Website"
resultType="net.biancheng.po.Website">
SELECT id,name,url,age,country
FROM website WHERE age in
<foreach item="age" index="index" collection="list" open="("
separator="," close=")">
#{age}
</foreach>
</select>
bind 标签
bind 标签可以通过 OGNL 表达式自定义一个上下文变量。可以用来解决每个数据库的拼接函数或连接符号都不同这一问题。
bind 元素属性
value:对应传入实体类的某个字段,可以进行字符串拼接等特殊处理。
name:给对应参数取的别名。
<select id="selectWebsite" resultType="net.biancheng.po.Website">
<bind name="pattern_name" value="'%'+name+'%'" />
<bind name="pattern_url" value="'%'+url+'%'" />
SELECT id,name,url,age,country
FROM website
WHERE name like #{pattern_name}
AND url like #{pattern_url}
</select>
trim标签
使用 if+where 实现多条件查询,还有一个更为灵活的元素 trim 能够替代之前的做法,trim 一般用于去除 SQL 语句中多余的 AND 关键字、逗号
,
或者给 SQL 语句前拼接 where、set 等后缀,可用于选择性插入、更新、删除或者条件查询等操作。
trim 中属性:
prefix: 给SQL语句拼接的前缀,为 trim 包含的内容加上前缀
suffix; 给SQL语句拼接的后缀,为 trim 包含的内容加上后缀
prefixOverrides: 去除 SQL 语句前面的关键字或字符。
suffixOverrides: 去除 SQL 语句后面的关键字或者字符。
<trim prefix="前缀" suffix="后缀" prefixOverrides="忽略前缀字符" suffixOverrides="忽略后缀字符">
SQL语句
</trim>
<select id="selectWebsite" resultType="net.biancheng.po.Website">
SELECT id,name,url,age,country
FROM website
<trim prefix="where" prefixOverrides="and">
<if test="name != null and name !=''">
AND name LIKE CONCAT ('%',#{name},'%')
</if>
<if test="url!= null">
AND url like concat ('%',#{url},'%')
</if>
</trim>
</select>
注意事项
- 变量命名:在
<if>
、<choose>
等条件判断中,使用的变量名应与传入参数对象的属性名一致。 - 前缀和后缀处理:
<trim>
元素中的prefix
和suffix
属性用于添加前缀和后缀,而prefixOverrides
和suffixOverrides
用于删除不需要的前缀和后缀。 - SQL 注入防护:MyBatis 使用
#{}
进行参数占位,可以有效地防止 SQL 注入。
MyBatis的接口绑定有哪些实现方式?
MyBatis 的接口绑定(即 Mapper 接口的实现)可以通过多种方式来完成,每种方式都有其特点和适用场景。
基于 XML 的映射文件
这是最传统也是最常见的实现方式。你需要为每个 Mapper 接口创建一个对应的 XML 文件,在其中定义 SQL 语句以及如何将这些 SQL 查询的结果映射到 Java 对象。
Mapper 接口 (
UserMapper.java
)javapublic interface UserMapper { User selectUserById(int id); }
XML 映射文件 (
UserMapper.xml
)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> </mapper>
特点
灵活性高:可以方便地编写复杂的 SQL 语句,并且易于管理和维护。
与业务逻辑分离:SQL 代码放在 XML 文件中,便于团队中的不同角色分工合作(如数据库管理员负责 SQL)。
适合复杂查询:对于需要大量动态 SQL 或者复杂嵌套查询的场景特别有用。
注解方式
MyBatis 支持使用注解直接在 Mapper 接口中定义 SQL 语句。这种方式使得代码更加紧凑,减少了 XML 文件的数量,但同时也意味着所有的 SQL 都会集中到接口文件中。
javaimport org.apache.ibatis.annotations.*; @Mapper public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User selectUserById(int id); @Insert("INSERT INTO users (name, email) VALUES (#{name}, #{email})") @Options(useGeneratedKeys = true, keyProperty = "id") void insertUser(User user); @Update("UPDATE users SET name=#{name}, email=#{email} WHERE id=#{id}") int updateUser(User user); @Delete("DELETE FROM users WHERE id=#{id}") int deleteUser(int id); }
混合方式
有时候,你可能希望结合上述两种方式的优点,即在某些情况下使用 XML 定义复杂的 SQL 语句,而在其他简单的情况下则采用注解。MyBatis 是完全支持这种混合使用的。
Mapper 接口 (
UserMapper.java
)javaimport org.apache.ibatis.annotations.*; @Mapper public interface UserMapper { // 使用注解定义简单的 SQL 操作 @Select("SELECT * FROM users WHERE id = #{id}") User selectUserById(int id); // 复杂的 SQL 操作通过 XML 定义 List<User> getUsersWithConditions(Map<String, Object> params); }
XML 映射文件 (
UserMapper.xml
)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"> <!-- 定义复杂的 SQL 操作 --> <select id="getUsersWithConditions" parameterType="map" resultType="com.example.model.User"> SELECT * FROM users WHERE 1=1 <if test="name != null"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="age != null"> AND age = #{age} </if> </select> </mapper>
MyBatis 中XML文件对应的Mapper的工作原理?
Mapper 映射原理
XML 文件定义:
Mapper 的 XML 文件用于定义 SQL 语句和映射关系。
<mapper>
标签的namespace
属性指定了该 XML 文件对应的 Mapper 接口的全限定名。每一个
<mapper>
标签,都会被解析为一个MapperStatement
对象。SQL 语句通过
<select>
、<insert>
、<update>
、<delete>
等标签定义,每个 SQL 语句都有一个唯一的id
属性,用于标识该 SQL 语句。
Mapper 接口声明:
Mapper 接口是一个 Java 接口,用于声明与数据库交互的方法。
方法名必须与 XML 文件中定义的 SQL 语句的
id
属性值相匹配。方法的参数类型应与 SQL 语句中占位符的类型相匹配。
MyBatis 配置解析:
MyBatis 在启动时,会解析全局配置文件(如
mybatis-config.xml
)和各个 Mapper 的 XML 映射文件。解析过程中,MyBatis 会读取 XML 文件中的
namespace
和id
属性,并将它们与 Mapper 接口的全限定名和方法名进行匹配。匹配成功后,MyBatis 会将 SQL 语句和方法签名绑定起来,并存储在内部的配置对象中。
Mapper 调用原理
获取 SqlSession:
在 MyBatis 中,
SqlSession
是用于执行 SQL 语句和管理事务的核心接口。开发者通过
SqlSessionFactory
获取SqlSession
实例。
获取 Mapper 接口实例:
通过
SqlSession
的getMapper
方法,开发者可以获取 Mapper 接口的代理对象。MyBatis 使用动态代理技术为 Mapper 接口生成代理对象。
调用 Mapper 接口方法:
当开发者调用 Mapper 接口中的方法时,实际上是调用了 MyBatis 生成的代理对象的方法。
代理对象会根据方法名找到对应的 SQL 语句,并根据方法参数构建 SQL 语句的执行参数。
执行 SQL 语句:
MyBatis 根据找到的 SQL 语句和执行参数,执行数据库操作。
对于查询操作,MyBatis 会将结果集映射到 Java 对象中,并返回给开发者。
对于其他操作(如插入、更新、删除),MyBatis 会返回相应的操作结果(如影响的行数)。
注意事项
- Mapper 接口和 XML 文件中的方法名和 SQL 语句的
id
必须一致,否则 MyBatis 无法找到对应的 SQL 语句。 - 参数的类型和名称需要与 XML 文件中定义的占位符相匹配,以确保 SQL 语句能够正确执行。
- MyBatis 提供了丰富的配置选项和特性(如动态 SQL、结果映射等),开发者可以根据需要灵活使用。
MyBatis 中Mapper 接口里的方法能重载吗?
Mapper接口方法可以重载,但是需要满足以下条件:
- 仅有一个无参方法和一个有参方法
- 多个有参方法时,参数数量必须一致。且使用相同的 @Param ,或者使用param1
MyBatis如何在mapper中如何传递多个参数?
在MyBatis的Mapper中传递多个参数有几种常见的方法。以下是几种常用的方式:
使用
@Param
注解MyBatis提供了
@Param
注解,允许你给方法的参数命名,这样你就可以在XML映射文件中通过指定的名字来引用这些参数。Mapper 文件
javapublic interface UserMapper { User selectUserByIdAndUsername(@Param("id") int id, @Param("username") String username); }
XML映射文件:
xml<select id="selectUserByIdAndUsername" resultType="User"> SELECT * FROM users WHERE id = #{id} AND username = #{username} </select>
使用对象封装参数
如果有很多参数需要传递,或者想要避免使用
@Param
注解,你可以创建一个Java对象来封装这些参数。Mapper 文件
javapublic class UserQuery { private int id; private String username; // getters and setters } public interface UserMapper { User selectUserByQuery(UserQuery query); }
XML映射文件示例:
xml<select id="selectUserByQuery" resultType="User"> SELECT * FROM users WHERE id = #{query.id} AND username = #{query.username} </select>
创建了一个
UserQuery
类来封装查询参数,然后在XML文件中通过#{query.id}
和#{query.username}
来引用这些参数。
使用
Map
传递参数另一种方法是使用
java.util.Map
来传递参数。这种方法比较灵活,但可能会降低代码的可读性。Mapper 文件
javapublic interface UserMapper { User selectUserByMap(Map<String, Object> params); }
XML映射文件示例:
xml<select id="selectUserByMap" resultType="User"> SELECT * FROM users WHERE id = #{params.id} AND username = #{params.username} </select>
传递了一个
Map
对象作为参数,并在XML文件中通过#{params.id}
和#{params.username}
来引用这些参数。注意,params
是MyBatis默认使用的键前缀,用于从Map
中获取参数值。
顺序传参
#{0}代表接收的是Mapper 中的第一个参数,#{1}代表DAO层中第二参数,更多参数一致往后加即可。
XML映射文件示例:
xml<select id="selectUser"resultMap="BaseResultMap"> select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1} </select>
注意事项
- 当使用
@Param
注解时,确保XML映射文件中使用的参数名与注解中指定的名字一致。 - 当使用对象封装参数时,确保XML映射文件中使用的属性名与Java对象中的属性名一致。
- 当使用
Map
传递参数时,确保XML映射文件中使用的键名与Map
中存储的键名一致。
MyBatis的mapper接口调用时有哪些要求?
- Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
- Mapper.xml文件中的namespace即是mapper接口的类路径。
MyBatis是如何将执行结果封装为目标对象并返回?
MyBatis 将 SQL 执行结果封装为目标对象并返回的过程涉及到了多种映射形式,这些映射机制确保了查询结果能够正确地转换为 Java 对象。以下是 MyBatis 支持的主要映射形式:
自动映射 (Auto Mapping)
这是最简单的方式,适用于大多数情况。当查询结果集的列名与 Java 对象的属性名相匹配时(不区分大小写),MyBatis 可以自动将列值赋给相应的属性。
xml<select id="selectUserById" resultType="com.example.model.User"> SELECT id, name, email FROM users WHERE id = #{id} </select>
在这个例子中,假设
users
表有id
,name
, 和email
列,并且User
类也有相应名称的属性,那么 MyBatis 会自动将查询结果映射到User
对象。显式映射 (ResultMap)
对于更复杂的情况,如列名和属性名不一致、存在嵌套结构或集合等,可以使用
ResultMap
进行精确控制。ResultMap
允许你定义详细的映射规则,包括如何处理关联关系。xml<resultMap id="userResultMap" type="com.example.model.User"> <id property="userId" column="user_id"/> <result property="userName" column="full_name"/> <result property="userEmail" column="contact_email"/> </resultMap> <select id="selectUserById" resultMap="userResultMap"> SELECT user_id, full_name, contact_email FROM users WHERE id = #{id} </select>
特点
灵活性:可以自定义列名到属性名的映射。
支持复杂类型:可以处理嵌套的结果集和集合。
MyBatis实现一对一和一对多?
MyBatis 支持一对一、一对多等复杂的关系映射。这可以通过在 ResultMap
中定义 <association>
和 <collection>
标签来实现。
一对一关联映射
有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap
里面配置association节点配置一对一的类就可以完成;
<resultMap id="orderResultMap" type="com.example.model.Order">
<id property="orderId" column="order_id"/>
<association property="user" javaType="com.example.model.User">
<id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="userEmail" column="user_email"/>
</association>
</resultMap>
<select id="selectOrderWithUser" resultMap="orderResultMap">
SELECT o.id AS order_id, u.id AS user_id, u.name AS user_name, u.email AS user_email
FROM orders o JOIN users u ON o.user_id = u.id
WHERE o.id = #{id}
</select>
一对多关联映射
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap
里面的**collection**
节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
<resultMap id="userOrdersResultMap" type="com.example.model.User">
<id property="userId" column="user_id"/>
<collection property="orders" ofType="com.example.model.Order">
<id property="orderId" column="order_id"/>
<result property="orderDate" column="order_date"/>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="userOrdersResultMap">
SELECT u.id AS user_id, o.id AS order_id, o.order_date
FROM users u LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
嵌套查询
除了直接映射关联对象,还可以通过嵌套查询的方式来获取关联数据。这种方式适合于需要延迟加载关联数据或者避免笛卡尔积的问题。嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
<resultMap id="userResultMap" type="com.example.model.User">
<id property="userId" column="user_id"/>
<collection property="orders" select="selectOrdersByUserId" column="user_id"/>
</resultMap>
<select id="selectUserById" resultMap="userResultMap">
SELECT * FROM users WHERE id = #{id}
</select>
<select id="selectOrdersByUserId" resultType="com.example.model.Order">
SELECT * FROM orders WHERE user_id = #{userId}
</select>
MyBatis 中 resultType 和 resultMap 的区别?
resultType
- 用途:
resultType
用于直接指定查询结果应该被映射到的 Java 类型。 - 适用场景:适用于查询结果中的列名与 Java 对象属性名完全一致的简单场景。
- 配置方式:在
<select>
、<insert>
、<update>
或<delete>
标签中使用resultType
属性指定目标 Java 类型。
resultMap
- 用途:
resultMap
用于更复杂的映射场景,可以自定义列名到属性名的映射规则,还可以处理嵌套对象和集合类型。 - 适用场景:适用于查询结果中的列名与 Java 对象属性名不一致,或者需要映射到复杂类型(如嵌套对象、集合)的场景。
- 配置方式:首先定义一个
<resultMap>
元素,然后在 SQL 语句中通过resultMap
属性引用这个映射配置。
总结
resultType
:简单、直接,适用于列名和属性名一致的情况。resultMap
:灵活、复杂,适用于列名和属性名不一致、需要映射嵌套对象和集合的情况。
MyBatis中使用TypeHandler?
MyBatis 的 TypeHandler
是一个非常重要的概念,它用于在 Java 类型和 JDBC 类型之间进行转换。通过自定义 TypeHandler
,你可以控制如何将数据库中的值映射到 Java 对象的属性,以及如何将 Java 对象的属性转换为 SQL 查询中的参数。这对于处理特殊的数据类型(如枚举、日期格式等)或者需要自定义转换逻辑的情况特别有用。
什么是 TypeHandler
TypeHandler
是 MyBatis 中负责数据类型转换的接口。它定义了四个主要的方法:
setNonNullParameter
:用于将 Java 类型的参数设置到 PreparedStatement 中。getNullableResult
(ResultSet 版):用于从 ResultSet 中获取列值并将其转换为 Java 类型。getNullableResult
(CallableStatement 版):用于从 CallableStatement 中获取结果并将其转换为 Java 类型。getNullableResult
(ResultSet 和 columnIndex 版):与上面类似,但使用的是列索引而非列名。
内置的 TypeHandler
MyBatis 提供了一组内置的 TypeHandler
来处理常见的 Java 和 JDBC 类型之间的转换。例如:
IntegerTypeHandler
StringTypeHandler
BooleanTypeHandler
DateTypeHandler
自定义 TypeHandler
当内置的 TypeHandler
无法满足需求时,你可以创建自己的 TypeHandler
。这通常涉及到实现 org.apache.ibatis.type.TypeHandler
接口或继承 BaseTypeHandler<T>
抽象类,并重写相应的方法来定义自定义的转换逻辑。
示例:创建一个枚举类型的 TypeHandler
假设有一个枚举类 UserStatus
和一个包含状态字段的用户表,其中 status
列存储的是字符串形式的状态名称。我们可以创建一个 TypeHandler
来确保正确的映射。
- 枚举类
public enum UserStatus {
ACTIVE, INACTIVE, PENDING;
}
- 自定义 TypeHandler
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.name()); // 假设数据库中存储的是枚举名
}
@Override
public UserStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
String statusName = rs.getString(columnName);
return statusName == null ? null : UserStatus.valueOf(statusName);
}
@Override
public UserStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String statusName = rs.getString(columnIndex);
return statusName == null ? null : UserStatus.valueOf(statusName);
}
@Override
public UserStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String statusName = cs.getString(columnIndex);
return statusName == null ? null : UserStatus.valueOf(statusName);
}
}
注册自定义 TypeHandler
有几种方法可以注册自定义的 TypeHandler
,以使其在整个应用程序中可用。
方法一:在 XML 配置文件中注册
你可以在 mybatis-config.xml
文件中添加 <typeHandlers>
标签来注册 TypeHandler
。
<configuration>
<typeHandlers>
<typeHandler javaType="com.example.model.UserStatus" handler="com.example.typehandler.UserStatusTypeHandler"/>
</typeHandlers>
</configuration>
方法二:直接在 resultMap 或 resultType 中指定
如果你只想在特定的结果映射中使用某个 TypeHandler
,可以直接在 XML 映射文件中为该属性指定 typeHandler
属性。
<resultMap id="userResultMap" type="com.example.model.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="status" column="status" typeHandler="com.example.typehandler.UserStatusTypeHandler"/>
</resultMap>
方法三:使用注解
MyBatis 还支持使用 @MappedTypes
和 @MappedJdbcTypes
注解来简化 TypeHandler
的注册过程。
@MappedTypes(UserStatus.class)
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> {
// 实现细节...
}
使用场景
- 处理复杂类型:对于 Java 中没有直接对应的 JDBC 类型(如枚举、自定义对象等),可以编写
TypeHandler
来实现它们之间的转换。 - 自定义转换逻辑:如果默认的类型转换行为不符合你的需求,可以通过自定义
TypeHandler
来调整。 - 国际化和本地化:例如,在不同地区使用不同的日期格式时,可以编写专门的
TypeHandler
来处理这种差异。 - 性能优化:某些情况下,编写高效的
TypeHandler
可以减少不必要的对象创建或提高解析速度。
MyBatis是否可以映射到枚举类?
MyBatis 支持将查询结果映射到枚举类,主要的方式包括:
- 自动映射:当数据库值可以直接对应到枚举常量的名字时。
- 自定义 TypeHandler:当需要复杂转换逻辑时,例如根据数据库中的不同值来决定具体的枚举实例。
- 构造函数注入:通过定义实体类的构造函数并结合
resultMap
来实现更精确的对象创建。
MyBatis 当实体类中的属性名和表中的字段名不一致 ?
当 MyBatis 中实体类的属性名和数据库表中的字段名不一致时,你可以通过多种方式来确保正确的映射。
使用 `resultMap``
resultMap
是 MyBatis 提供的一种强大机制,允许你详细定义如何将查询结果映射到 Java 对象。它不仅支持简单的列到属性的映射,还可以处理复杂的数据结构,如嵌套对象、集合等。xml<resultMap id="userResultMap" type="com.example.model.User"> <id property="id" column="user_id"/> <result property="name" column="full_name"/> <result property="email" column="email_address"/> </resultMap> <select id="selectUserById" resultMap="userResultMap"> SELECT user_id, full_name, email_address FROM users WHERE id = #{id} </select>
使用别名(Alias)
如果你不想为每个查询都编写
resultMap
,可以在 SQL 查询中使用别名,使查询结果的列名与实体类的属性名相匹配。MyBatis 会自动将这些别名映射到相应的属性上。xml<select id="selectUserById" resultType="com.example.model.User"> SELECT user_id AS id, full_name AS name, email_address AS email FROM users WHERE id = #{id} </select>
自定义类型处理器 (TypeHandler)
对于更复杂的映射需求,比如需要对某些特定类型的值进行转换,可以编写一个自定义的
TypeHandler
。虽然这主要用于处理类型转换问题,但在某些情况下也可以用来解决名称不一致的问题。使用
@Results
和@Result
注解如果你偏好注解驱动的方式,MyBatis 还提供了
@Results
和@Result
注解,可以直接在 Mapper 接口中定义映射规则。这种方法将映射规则直接放在接口方法上,减少了 XML 文件的数量,但可能降低配置的灵活性。javapublic interface UserMapper { @Select("SELECT user_id, full_name, email_address FROM users WHERE id = #{id}") @Results({ @Result(property = "id", column = "user_id"), @Result(property = "name", column = "full_name"), @Result(property = "email", column = "email_address") }) User selectUserById(int id); }
使用
@Table
和@Column
注解(需额外依赖)如果你使用的是 MyBatis-Plus 或其他扩展库,可能会提供类似于 JPA 的
@Table
和@Column
注解,用于直接在实体类中指定表名和列名。不过需要注意的是,这是特定于某些扩展库的功能,并不是标准 MyBatis 的一部分。java@Table("users") public class User { @Column("user_id") private Integer id; @Column("full_name") private String name; @Column("email_address") private String email; // getters and setters }
MyBatis #{}和${}的区别?
在 MyBatis 中,#{}
和 ${}
都用于将参数值插入到 SQL 语句中,但它们的工作方式和适用场景有很大的区别。
#{}
占位符
预编译处理:
#{}
是 MyBatis 默认使用的占位符语法,它会将参数值作为预编译语句(PreparedStatement)的参数传递给数据库驱动程序。这意味着参数值会被视为一个单独的输入参数,而不是直接拼接到 SQL 语句中。安全性:由于
#{}
使用了 PreparedStatement 的机制,因此它可以有效防止 SQL 注入攻击。MyBatis 会自动处理特殊字符转义,确保用户输入不会破坏 SQL 语句结构。类型处理:
#{}
支持 Java 类型与 JDBC 类型之间的转换,并且可以利用 MyBatis 的TypeHandler
机制来定制这种转换。使用场景:
适用于大多数情况下的参数传递,如查询、插入、更新等操作。
当你需要确保 SQL 语句的安全性和正确性时,应该优先选择
#{}
。
${}
占位符
字符串替换:
${}
实际上是进行简单的字符串替换操作,它会直接将参数值插入到 SQL 语句中,而不会经过任何预编译或转义处理。风险:因为
${}
不会转义特殊字符,所以如果参数值包含恶意代码,则可能导致 SQL 注入攻击。灵活性:尽管存在安全风险,但在某些特定情况下,
${}
可以提供更大的灵活性,比如动态表名或列名等情况。然而,这些用法应当非常谨慎地使用,并尽量减少其出现频率。使用场景:
动态生成 SQL 片段,如表名、列名,order by 动态参数时或其他无法通过
#{}
处理的部分。注意:只有在绝对必要并且确认安全的情况下才应使用
${}
。
<select id="findUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
<select id="selectUsersByTableName" parameterType="string" resultType="User">
SELECT * FROM ${tableName} WHERE status = 'ACTIVE'
</select>
总结:
- 推荐做法:通常情况下,你应该总是使用
#{}
来传递参数,因为它更安全、更可靠。除非有明确的需求(如动态 SQL),否则避免使用${}
。 - SQL 注入防护:为了保护应用程序免受 SQL 注入攻击的影响,请尽可能使用
#{}
,并严格限制${}
的使用范围。 - 性能考虑:虽然
#{}
和${}
在大多数情况下对性能影响不大,但由于#{}
使用了 PreparedStatement,所以在大量重复执行相同 SQL 语句时,它可能会带来更好的性能表现。
MyBatis 模糊查询like语句该怎么写?
方法一:直接在 Java 代码中拼接通配符
在传递参数之前,在 Java 代码中手动添加通配符 %
,然后通过 #{}
占位符将参数插入到 SQL 语句中。这种方法简单直接,但需要注意的是,如果参数为空字符串,则可能导致意外的行为(例如匹配所有记录)。因此,可能需要额外的逻辑来处理这种情况。
// Java 代码
String title = "example";
title = "%" + title + "%"; // 手动添加通配符
userMapper.findBlogByTitle(title);
<!-- Mapper XML 文件 -->
<select id="findBlogByTitle" parameterType="string" resultType="Blog">
SELECT * FROM BLOG WHERE title LIKE #{title}
</select>
方法二:使用 <bind>
标签和 OGNL 表达式
利用 MyBatis 的 <bind>
标签可以在 XML 文件中创建变量,并结合 OGNL 表达式动态地为参数添加通配符。
<select id="findBlogByTitle" parameterType="map" resultType="Blog">
<bind name="pattern" value="'%' + title + '%'" />
SELECT * FROM BLOG WHERE title LIKE #{pattern}
</select>
在这种方式下,你可以直接从 Java 代码中传递不带通配符的参数值:
// Java 代码
Map<String, Object> params = new HashMap<>();
params.put("title", "example");
List<Blog> blogs = userMapper.findBlogByTitle(params);
方法三:使用 CONCAT
函数(数据库特定)
某些数据库支持 CONCAT
函数,可以用来在 SQL 语句中直接拼接通配符。这种方法依赖于具体的数据库实现,不是所有数据库都支持这种方式。
<select id="findBlogByTitle" parameterType="string" resultType="Blog">
SELECT * FROM BLOG WHERE title LIKE CONCAT('%', #{title}, '%')
</select>
<select id="findBlogByTitle" parameterType="map" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
</where>
</select>
注意事项
- SQL 注入风险:虽然上述方法都可以有效实现模糊查询,但在任何情况下都不应该直接拼接用户输入作为 SQL 语句的一部分,以防止 SQL 注入攻击。始终使用参数化查询或预编译语句。
- 性能考虑:使用
LIKE
模糊查询可能会导致索引失效,特别是在前缀带有通配符的情况下(如'%example'
)。对于大数据集,这可能会影响查询性能。应评估查询条件并考虑适当的索引策略。 - 空字符串处理:当参数为空字符串时,
LIKE '%'
会匹配所有记录。为了避免这种情况,可以在查询中添加额外的条件检查,或者在应用程序层面上处理空字符串。
MyBatis如何执行批量插入?
在MyBatis中执行批量插入操作通常涉及几种不同的方法:
使用
<foreach>
标签: 这是MyBatis中最常见和推荐的方法。你可以在MyBatis的映射文件中使用<foreach>
标签来迭代一个集合,并为每个元素生成一个INSERT语句。如果你使用的是MyBatis 3.x,你可以通过配置executorType
为BATCH
来确保这些INSERT语句以批处理的方式执行。在调用这个mapper方法时,你需要传递一个包含要插入的用户的List
。xml<insert id="batchInsertUsers" parameterType="list"> INSERT INTO users (name, email) VALUES <foreach collection="list" item="user" separator=","> (#{user.name}, #{user.email}) </foreach> </insert>
使用批处理执行器(Executor): 当你配置MyBatis的SqlSessionFactory时,你可以设置
executorType
为BATCH
。这将使得MyBatis使用批处理执行器来执行所有的SQL语句,包括INSERT、UPDATE和DELETE。但是,请注意,这并不会自动将多个INSERT语句合并为一个批处理请求;它只是确保每个SQL语句在执行时都被批处理。在这个例子中,每个INSERT语句仍然是单独的,但是它们是在一个事务中一起提交的,这通常比逐个提交更快。javaSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false); try { List<User> users = ...; // 你的用户列表 for (User user : users) { session.insert("namespace.insertUser", user); } session.commit(); // 提交事务,此时所有的INSERT语句会被一起执行 } finally { session.close(); }
手动管理批处理: 在某些情况下,你可能需要更细粒度的控制,比如手动管理JDBC批处理。这可以通过获取MyBatis的底层JDBC连接,然后使用该连接来创建批处理语句来实现。这种方法比较复杂,通常不推荐除非你有特殊的需求。
使用MyBatis Plus或其他扩展: 如果你使用的是MyBatis Plus或其他MyBatis的扩展库,它们可能提供了更高级的批量插入功能,比如批量插入并返回主键ID等。
注意事项:
当使用批处理时,确保你的数据库连接没有自动提交(
autoCommit=false
)。批处理通常用于大量数据的插入,因为它可以减少数据库往返的次数,从而提高性能。
如果你的INSERT语句依赖于数据库生成的ID(如自增主键),请确保你的MyBatis配置或数据库设置正确处理了这些ID的返回。
MyBatis如何获取自动生成的(主)键值?
使用
useGeneratedKeys
和keyProperty
属性对于支持自动生成主键(如 MySQL 的
AUTO_INCREMENT
)的数据库,可以在<insert>
标签中设置useGeneratedKeys="true"
和keyProperty
属性。keyProperty
指定了哪个属性应该接收生成的主键值。Mapper 接口
public interface UserMapper { int insertUser(User user); }
XML 映射文件
xml<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> INSERT INTO users (name, email) VALUES (#{name}, #{email}) </insert>
在这个例子中,当执行插入操作时,MyBatis 会自动将数据库生成的主键值赋给
User
对象的id
属性。注意事项
useGeneratedKeys
:必须设置为true
,表示启用获取自动生成的键。keyProperty
:指定实体类中的哪个属性应该接收生成的主键值。keyColumn
(可选):如果表中有多个列可以作为主键,或者你想明确指出哪个列是主键,则可以使用keyColumn
属性来指定。
使用
selectKey
元素对于不直接支持自动生成主键的数据库(如某些版本的 Oracle),或者你需要在插入之前先获取主键值(例如通过序列),可以使用
<selectKey>
元素。<selectKey>
允许你在插入语句前后执行查询以获取键值,并将其赋给实体对象的某个属性。Mapper 接口
javapublic interface UserMapper { int insertUser(User user); }
XML 映射文件
<insert id="insertUser"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> SELECT user_seq.NEXTVAL FROM dual </selectKey> INSERT INTO users (id, name, email) VALUES (#{id}, #{name}, #{email}) </insert>
在这个例子中,
<selectKey>
在插入操作之前执行,从序列user_seq
获取下一个值,并将其赋给User
对象的id
属性。然后,这个值被用作插入语句的一部分。selectKey
参数说明keyProperty
:指定实体类中的哪个属性应该接收生成的主键值。resultType
:指定selectKey
查询返回的结果类型。order
:可以是BEFORE
或AFTER
,指定了selectKey
是在插入语句之前还是之后执行。通常情况下,如果你是从序列获取主键值,则应设置为BEFORE
;如果是依赖于数据库自动生成的主键,则应设置为AFTER
。
使用 MyBatis-Plus 扩展库
如果你正在使用 MyBatis-Plus,它提供了一些简化的方法来处理主键生成。例如,你可以使用注解
@TableId
来指定主键生成策略。javaimport com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.IdType; public class User { @TableId(type = IdType.AUTO) private Long id; // getters and setters }
在这个例子中,
IdType.AUTO
表示使用数据库的自增机制来生成主键。MyBatis-Plus 还支持其他类型的主键生成策略,如 UUID、雪花算法等。
总结
useGeneratedKeys
和keyProperty
:适用于支持自动生成主键的数据库,如 MySQL 的AUTO_INCREMENT
。selectKey
:适用于需要显式获取主键值的情况,如 Oracle 序列或其他不直接支持自动生成主键的数据库。- MyBatis-Plus:如果你正在使用这个扩展库,它可以简化主键生成的配置。
Mybatis如何进行分页的?
方法一:手动编写分页 SQL
直接在 Mapper XML 文件中编写带有 LIMIT
和 OFFSET
的 SQL 语句,适用于简单的场景。
<select id="selectUsers" parameterType="map" resultType="User">
SELECT * FROM users
ORDER BY id
LIMIT #{offset}, #{limit}
</select>
在 Java 代码中调用时,传递分页参数:
Map<String, Object> params = new HashMap<>();
params.put("offset", (page - 1) * pageSize);
params.put("limit", pageSize);
List<User> users = userMapper.selectUsers(params);
这种方法简单明了,但对于不同数据库可能需要调整 SQL 语法,并且难以处理复杂的分页需求。
方法二:使用 MyBatis 分页插件
MyBatis 提供了一些第三方分页插件,如 PageHelper、MyBatis-Spring-Boot-Starter 等,这些插件可以自动为 SQL 语句添加分页逻辑,极大地方便了开发工作。
使用 PageHelper 插件
- 引入依赖:如果你使用的是 Maven 构建工具,可以在
pom.xml
中添加 PageHelper 的依赖。
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
- 配置插件:在 MyBatis 配置文件或 Spring Boot 的
application.properties
中启用 PageHelper 插件。
# application.properties
mybatis.configuration.map-underscore-to-camel-case=true
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
- 编写分页查询:使用 PageHelper 的静态方法来设置分页参数,然后执行查询。
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
// 设置分页参数
PageHelper.startPage(page, pageSize);
// 执行查询
List<User> users = userMapper.selectAll();
// 获取分页信息
PageInfo<User> pageInfo = new PageInfo<>(users);
- 自定义分页结果:如果你需要更详细的分页信息,比如总记录数、总页数等,可以通过
PageInfo
对象获取。
long total = pageInfo.getTotal();
int pages = pageInfo.getPages();
这种方法不仅简化了分页逻辑,还提供了丰富的分页信息,适合大多数应用场景。
方法三:使用 RowBounds
MyBatis 内置了一个叫做 RowBounds
的类,它可以用于限制查询结果的数量和偏移量。不过需要注意的是,RowBounds
只是对查询结果进行了裁剪,并不会优化底层 SQL 语句,因此它并不总是最高效的分页方式。
import org.apache.ibatis.session.RowBounds;
// 创建 RowBounds 实例
RowBounds rowBounds = new RowBounds((page - 1) * pageSize, pageSize);
// 执行查询
List<User> users = sqlSession.selectList("selectAllUsers", null, rowBounds);
虽然这种方式简单易用,但不推荐在大数据集上使用,因为它可能会导致性能问题。
方法四:结合存储过程
对于某些复杂的需求,可以直接在数据库端创建存储过程来进行分页查询,然后通过 MyBatis 调用该存储过程。这可以充分利用数据库的性能优势,但在移植性和维护性方面可能会有所牺牲。
注意事项
- SQL 注入防护:无论采用哪种方式,都应确保参数化查询以防止 SQL 注入攻击。
- 数据库兼容性:不同的数据库有不同的分页语法,例如 Oracle 使用
ROWNUM
,PostgreSQL 使用LIMIT
和OFFSET
。选择合适的分页方法时要考虑目标数据库的支持情况。 - 性能优化:尽量减少不必要的字段选择,利用索引加速查询,并评估是否需要对分页结果进行缓存。
- 测试充分:实现分页功能后,务必进行全面的单元测试和集成测试,以确保系统行为符合预期。
MyBatis 中实现动态数据源切换?
在MyBatis中实现动态数据源切换,通常需要结合Spring框架进行配置。以下是实现动态数据源切换的一般步骤:
配置数据源
配置多个数据源。例如,在
application.yml
或application.properties
中配置多个数据源信息。yamlspring: datasource: primary: url: jdbc:mysql://localhost:3306/primary_db username: root password: password driver-class-name: com.mysql.cj.jdbc.Driver secondary: url: jdbc:mysql://localhost:3306/secondary_db username: root password: password driver-class-name: com.mysql.cj.jdbc.Driver
创建数据源配置类
使用Spring的
@Configuration
和@Bean
注解创建多个数据源配置类,并创建一个动态数据源类。配置 MyBatis SqlSessionFactory:将
dynamicDataSource
注入到 MyBatis 的SqlSessionFactory
中。javaimport org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration public class DataSourceConfig { @Bean(name = "primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } @Bean public DataSource routingDataSource() { AbstractRoutingDataSource routingDataSource = new DynamicRoutingDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("primary", primaryDataSource()); targetDataSources.put("secondary", secondaryDataSource()); routingDataSource.setTargetDataSources(targetDataSources); routingDataSource.setDefaultTargetDataSource(primaryDataSource()); return routingDataSource; } @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("routingDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/*.xml")); return sessionFactory.getObject(); } }
实现动态数据源切换逻辑
创建一个类继承
AbstractRoutingDataSource
,并实现determineCurrentLookupKey
方法,该方法用于决定当前使用哪个数据源。创建线程局部变量管理器:用来保存当前线程使用的数据源标识。
javaimport org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicRoutingDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSourceType(String dataSourceType) { CONTEXT_HOLDER.set(dataSourceType); } public static String getDataSourceType() { return CONTEXT_HOLDER.get(); } public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); } @Override protected Object determineCurrentLookupKey() { return getDataSourceType(); } }
使用AOP或手动切换数据源
你可以使用Spring AOP或手动调用
DynamicRoutingDataSource
的方法来切换数据源。方式一:使用AOP和注解切换数据源
定义 Aspect
javaimport org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class DataSourceAspect { @Before("@annotation(targetDataSource)") public void changeDataSource(TargetDataSource targetDataSource) { DynamicRoutingDataSource.setDataSourceType(targetDataSource.value()); } }
自定义注解
import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String value(); }
在服务方法中使用注解
javaimport org.springframework.stereotype.Service; @Service public class MyService { @TargetDataSource("secondary") public void useSecondaryDataSource() { // 使用secondary数据源的逻辑 } @TargetDataSource("primary") public void usePrimaryDataSource() { // 使用primary数据源的逻辑 } }
清理上下文,确保在每次调用后清理数据源上下文,避免内存泄漏。
javaimport org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class DataSourceCleanUpAspect { @AfterReturning(pointcut = "@annotation(targetDataSource)") public void cleanUpDataSource(TargetDataSource targetDataSource) { DynamicRoutingDataSource.clearDataSourceType(); } }
方式二:使用AOP around切换数据源
java@Aspect @Component public class DataSourceAspect { @Around("execution(* com.example..*Controller.*(..))") public Object around(ProceedingJoinPoint point) throws Throwable { try { // 设置数据源类型逻辑 DataSourceContextHolder.setDataSourceType(determineDataSource(point)); return point.proceed(); } finally { // 清除数据源类型 DataSourceContextHolder.clearDataSourceType(); } } private String determineDataSource(ProceedingJoinPoint point) { // 根据业务逻辑确定数据源类型 return "dataSource1"; // 示例代码,实际应根据业务逻辑判断 } }
方法二:使用 ShardingSphere
或其他中间件
对于更复杂的分库分表场景,可以考虑使用像 Apache ShardingSphere 这样的分布式数据库中间件。它不仅支持动态数据源切换,还提供了丰富的分片策略和其他高级特性。
注意事项
- 事务管理:确保在同一个事务内始终使用同一个数据源,避免跨数据源事务带来的复杂性。
- 性能影响:频繁地切换数据源可能会带来一定的性能开销,因此应评估是否真的有必要这样做。
- 测试充分:实现动态数据源切换后,务必进行全面的单元测试和集成测试,以确保系统行为符合预期。
MyBatis的插件运行原理?
MyBatis 插件机制的核心原理是基于 Java 的动态代理(JDK 动态代理或 CGLIB)来实现的。它采用责任链模式,允许开发者通过拦截特定的执行点(如 SQL 执行、参数处理、结果集映射等),从而扩展或修改 MyBatis 的行为。
Mybatis自定义插件针对Mybatis四大对象进行拦截,具体拦截方式为:
- Executor:拦截执行器的方法(log记录)
- StatementHandler :拦截SQL语法构建的处理
- ParameterHandler :拦截参数的处理
- ResultSetHandler :拦截结果集的处理
插件接口 (Interceptor
)
所有 MyBatis 插件都必须实现 org.apache.ibatis.plugin.Interceptor
接口,该接口定义了三个方法:
Object intercept(Invocation invocation)
:这是插件的主要逻辑所在。每当被拦截的方法被执行时,都会调用此方法。你可以在这里添加自定义逻辑,比如分页、日志记录等。Object plugin(Object target)
:用于创建一个代理对象来包装原始目标对象(即被拦截的对象)。返回的代理对象会替换原始对象,因此所有的方法调用都将经过这个代理。void setProperties(Properties properties)
:用于设置插件的配置属性。这些属性可以在 MyBatis 配置文件中指定,并通过反射传递给插件实例。
签名注解 (@Intercepts
和 @Signature
)
为了告诉 MyBatis 哪些方法应该被拦截,你需要在插件类上使用 @Intercepts
注解,并在其内部使用一个或多个 @Signature
注解来指明具体的拦截点。每个 @Signature
注解包含三个关键信息:
type
:要拦截的目标类型,可以是Executor
,StatementHandler
,ParameterHandler
, 或ResultSetHandler
。method
:要拦截的目标类型中的具体方法名。args
:目标方法的参数列表,用于精确匹配需要拦截的方法。
// 这段代码表示该插件将拦截 `Executor` 类型下的 `query` 方法,并且只针对具有特定参数类型的 `query` 方法进行拦截。
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
代理对象生成
当 MyBatis 初始化时,它会扫描所有注册的插件,并为每一个被标记为拦截的目标对象创建一个代理对象。这个过程涉及到以下几个步骤:
- 解析插件配置:读取 MyBatis 配置文件中的
<plugins>
部分,找到所有声明的插件及其配置属性。 - 实例化插件:根据配置创建插件的实例,并调用
setProperties()
方法传入配置属性。 - 构建代理链:对于每一个被拦截的目标对象(如
Executor
),MyBatis 会检查是否有一个或多个插件对其进行了拦截。如果有,则按照顺序依次调用各个插件的plugin()
方法,构建出一个代理链。每个插件都会返回一个新的代理对象,直到最终形成完整的代理链。 - 替换原始对象:用构建好的代理链替代原始目标对象,这样所有对原始对象的方法调用实际上都会经过代理链中的插件逻辑。
方法调用流程
一旦代理对象准备好,实际的方法调用流程如下:
- 调用发起:应用程序代码调用某个 MyBatis API 方法(如
selectList()
)。 - 代理拦截:由于存在代理链,实际调用的是代理对象上的相应方法。此时,代理对象会触发第一个插件的
intercept()
方法。 - 插件逻辑执行:在
intercept()
方法中,你可以执行任何你想要的操作,比如修改参数、记录日志、执行额外的业务逻辑等。 - 继续执行:调用
invocation.proceed()
来继续执行链中的下一个拦截器或者原始方法。这一步骤是可选的,如果你不想让后续操作发生,可以选择不调用proceed()
。 - 结果返回:最终的结果从链底返回上来,可能还会经过其他插件的后处理逻辑,最后到达应用程序代码。
线程安全与性能考量
- 线程安全:由于代理对象和插件实例可能会被多个线程共享,因此它们内部的状态应当保持不变或采取适当的同步措施以保证线程安全。
- 性能影响:虽然插件提供了强大的功能扩展能力,但不当使用可能会引入额外的性能开销。特别是在高并发场景下,过多的插件或复杂的拦截逻辑可能导致显著的性能下降。因此,在开发过程中要注意评估插件对系统整体性能的影响。
Mybatis使用自定义插件?
使用自定义插件是 MyBatis 提供的一种强大机制,允许开发者通过拦截特定的执行点来扩展或修改 MyBatis 的行为。以下是创建和使用自定义插件的详细步骤,包括从编写插件到将其集成到应用程序中的全过程。
创建自定义插件
实现
Interceptor
接口所有 MyBatis 插件都必须实现
org.apache.ibatis.plugin.Interceptor
接口,并且通常会使用@Intercepts
和@Signature
注解来指定要拦截的方法。javaimport org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.util.Properties; @Intercepts({ @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) }) public class CustomPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在这里添加你想要执行的逻辑 System.out.println("Before executing SQL..."); Object result = invocation.proceed(); // 继续执行链中的下一个拦截器或目标方法 System.out.println("After executing SQL..."); return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以在这里设置一些属性,例如从配置文件中读取 String someProperty = properties.getProperty("someProperty", "defaultValue"); System.out.println("CustomPlugin property: " + someProperty); } }
在这个例子中,
CustomPlugin
拦截了Executor.query()
方法,在 SQL 执行前后打印日志信息。此外,它还展示了如何通过setProperties()
方法接收来自配置文件的属性。配置插件属性(可选)
如果你希望在插件中使用某些配置属性,可以在
mybatis-config.xml
或者 Spring Boot 的application.properties
文件中为插件指定这些属性。xml<plugins> <plugin interceptor="com.example.CustomPlugin"> <property name="someProperty" value="customValue"/> </plugin> </plugins>
或者在
application.properties
中:propertiesmybatis.configuration.plugins[0].interceptor=com.example.CustomPlugin mybatis.configuration.plugins[0].properties.someProperty=customValue
二、注册自定义插件
根据你的项目结构,有几种不同的方式可以注册自定义插件。
使用 XML 配置文件
如果你的应用程序没有使用 Spring 或 Spring Boot,那么可以通过 MyBatis 的 XML 配置文件 (
mybatis-config.xml
) 来注册插件。xml<configuration> <!-- 其他配置 --> <plugins> <plugin interceptor="com.example.CustomPlugin"> <property name="someProperty" value="customValue"/> </plugin> </plugins> <!-- 其他配置 --> </configuration>
使用 Spring 或 Spring Boot 配置类
使用 Spring Boot 自动配置:
如果你使用的是 Spring Boot,并且所使用的插件提供了 Starter 支持(如 PageHelper),你可以直接在
pom.xml
或build.gradle
中添加依赖,并在application.properties
或application.yml
中配置相关属性。Spring Boot 会自动扫描并注册这些插件。使用自定义配置类:
如果需要手动注册插件,可以通过 Java 配置类来进行。
javaimport com.example.CustomPlugin; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.example.mapper") public class MyBatisConfig { @Bean public CustomPlugin customPlugin() { return new CustomPlugin(); } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); // 添加插件 sessionFactory.setPlugins(new Interceptor[]{customPlugin()}); return sessionFactory.getObject(); } }
注意事项
- 线程安全:确保插件是线程安全的,尤其是在多线程环境中。避免在插件内部使用共享可变状态,除非采取适当的同步措施。
- 性能影响:虽然插件提供了强大的功能扩展能力,但不当使用可能会引入额外的性能开销。因此,在开发过程中要注意评估插件对系统整体性能的影响。
- 兼容性:不同版本的 MyBatis 可能会有不同的 API 或者行为变化,所以在升级 MyBatis 版本时要检查插件是否仍然兼容。
- 事务管理:如果插件涉及到数据库操作,请务必考虑如何正确处理事务边界,避免破坏原有事务的一致性和隔离性。
- 插件顺序:如果有多个插件,它们的顺序可能会影响执行结果。MyBatis会按照配置文件中定义的顺序依次调用插件。
- 异常处理:在
intercept
方法中,如果抛出异常,需要确保这些异常被正确捕获和处理,以免影响MyBatis的正常运行。