MyBatis 详细使用说明文档:从环境配置到复杂查询的全面指南
MyBatis 详细使用说明文档:从环境配置到复杂查询的全面指南
引言
MyBatis 是一个一流的持久层框架,专为简化 Java 应用程序中的 SQL 操作而设计。它支持自定义 SQL、存储过程和高级映射,旨在消除大部分 JDBC 样板代码,并实现参数的自动设置及结果的自动检索。与传统的全功能对象关系映射(ORM)框架(如 Hibernate)不同,MyBatis 不直接将 Java 对象映射到数据库表,而是将 Java 方法与 SQL 语句关联起来,通过 XML 描述符或注解进行配置。
MyBatis 的核心优势在于提供了对 SQL 执行的完全控制,这使得它非常适合处理复杂的查询、与遗留数据库或非规范化数据库交互,以及需要精细性能调优的场景。它支持使用内置的 XML 语法或 Apache Velocity 插件动态构建 SQL 语句。此外,MyBatis 能够与 Spring Framework 和 Google Guice 等依赖注入框架无缝集成,从而简化了业务代码的开发,并支持声明式数据缓存。MyBatis 在持久层框架中占据着独特的地位,它在原始 JDBC 的灵活性与全功能 ORM 的抽象之间提供了一个平衡点。这种设计允许开发者充分利用数据库的全部功能,包括存储过程、视图、复杂查询和供应商特定功能,同时避免了全功能 ORM 有时可能带来的开销或阻抗失配问题。这种高度的灵活性和对 SQL 的精细控制是 MyBatis 在特定应用场景中成为优选的关键原因。
MyBatis 核心架构与组件
MyBatis 的架构围绕几个核心组件构建,这些组件协同工作以实现数据持久化。理解这些组件的职责和生命周期对于正确设计和实现 MyBatis 应用程序至关重要。
SqlSessionFactory
每个 MyBatis 应用程序都围绕 SqlSessionFactory 的实例展开。
SqlSessionFactory 实例可以通过 SqlSessionFactoryBuilder 获取,它能够从 XML 配置文件(如 mybatis-config.xml)或自定义的 Configuration 类实例构建。通常,建议将 XML 配置文件作为类路径资源加载,以简化部署和管理。
SqlSessionFactory 实例在应用程序的整个生命周期内都应存在,其作用域通常是应用程序级别。它不应被频繁地销毁或重建,因为这被视为一种“不良实践”,可能导致不必要的资源开销。配置 XML 文件包含了 MyBatis 系统的核心设置,包括用于获取数据库连接的DataSource 和用于事务管理的 TransactionManager,以及映射器(Mapper)的注册信息。
SqlSession
SqlSession 实例是从 SqlSessionFactory 获取的。它包含了执行数据库 SQL 命令所需的所有方法,例如
selectOne、getMapper 等。
SqlSession 实例是非线程安全的,这意味着它不应在多个线程之间共享。其最佳作用域是请求或方法作用域,例如在 Web 应用程序中,它应与 HTTP 请求的生命周期保持一致。
至关重要的是,SqlSession 实例在使用完毕后必须始终关闭,通常在 finally 块中或使用 try-with-resources 语句,以确保数据库资源得到正确释放,避免连接泄漏。当 MyBatis 与 Spring 框架集成时(通过 MyBatis-Spring 模块),通常会使用
SqlSessionTemplate。
SqlSessionTemplate 是线程安全的,并且能够根据 Spring 的事务配置自动管理会话的生命周期(包括提交、回滚和关闭),同时将 MyBatis 异常转换为 Spring 的DataAccessException。这种设计抽象了 SqlSession 的手动管理,显著简化了在 Spring 环境中的开发,同时确保了线程安全和事务一致性。
Mapper 接口
Mapper 接口是用户定义的 Java 接口,用于绑定到映射的 SQL 语句。这些接口的实例通过
SqlSession 获取,例如 session.getMapper(BlogMapper.class)。Mapper 实例的最佳作用域是方法作用域,它们在使用后可以被丢弃,无需显式关闭。
SQL 操作可以直接通过注解(如 @Select、@Insert、@Update、@Delete)定义在 Mapper 接口的方法上。此外,SQL 也可以通过 XML 映射文件定义。MyBatis 会自动查找并加载与 Mapper 接口同名的 XML 文件(例如,对于
BlogMapper.class,会加载 BlogMapper.xml),如果该文件存在的话。
SqlSessionFactory 作为应用程序级别的单例,负责创建 SqlSession 实例,而 SqlSession 则作为会话或请求级别的对象,负责执行具体的数据库操作。Mapper 接口作为 SqlSession 的一个门面,提供了类型安全的 SQL 操作接口。这种清晰的职责划分促进了模块化和可测试性。SqlSession 的非线程安全特性要求开发者在多线程环境中谨慎管理其生命周期,而 SqlSessionTemplate 的引入则正是为了解决 Spring 环境下这一管理复杂性,通过框架的介入提供了线程安全的抽象和自动化的事务管理,这对于构建健壮的企业级应用程序至关重要。
第一部分:环境配置
正确配置 MyBatis 环境是开始任何项目的基础。本节将详细介绍 Maven/Gradle 依赖、核心配置文件 mybatis-config.xml 的各项元素,以及事务管理和缓存机制的配置。
Maven/Gradle 依赖配置
为了在 Java 项目中使用 MyBatis,首先需要将 MyBatis 库添加到项目的类路径中。这通常通过构建工具(如 Maven 或 Gradle)来完成。
Maven 依赖示例:
对于 Maven 项目,可以将以下依赖项添加到 pom.xml 文件中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.16</version>
</dependency>
Gradle 依赖示例:
虽然 MyBatis 官方文档的入门部分未直接提供 Gradle 依赖的详细信息,但通过查阅其他资源和常见的构建实践,可以确定 Gradle 的配置方式。
对于基础 MyBatis 库,可以在 build.gradle 文件中添加:
implementation 'org.mybatis:mybatis:3.5.16' // 使用最新稳定版本
如果项目使用 Spring Boot 框架,通常会选择 MyBatis-Spring Boot Starter,它提供了更便捷的集成和自动配置:
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' // 示例版本
在配置项目依赖时,始终建议查阅 Maven Central 等中央仓库,以获取 MyBatis 及其相关模块的最新稳定版本。正确的依赖管理是任何 Java 项目稳定性的基石,它确保了所有必要的库都已正确引入,并且版本之间没有冲突。特别是,
mybatis-spring-boot-starter 的广泛使用表明,在现代 Java 开发中,与 Spring Boot 的集成是主流实践,这极大地简化了 MyBatis 的设置和自动配置流程,减少了手动配置的复杂性。开发者需要全面了解核心库之外的生态系统,并利用构建工具的优势来高效管理项目依赖。
MyBatis 核心配置文件 mybatis-config.xml 详解
mybatis-config.xml 是 MyBatis 框架的中心配置文件,它定义了 MyBatis 的全局行为,包括数据库连接、事务管理、类型别名、映射器注册等关键设置。
基本结构:
一个典型的 mybatis-config.xml 文件具有以下基本结构,其中包含了一系列可配置的子元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
properties 元素
properties 元素允许将配置中的值外部化,并在整个配置文件中进行替换,从而增强了配置的灵活性和可维护性。属性可以从类路径资源(例如
config.properties 文件)加载,也可以直接在 properties 元素内部定义。
示例:
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
自 MyBatis3.4.2 版本起,properties 元素还支持为占位符指定默认值,例如 ${username:ut_user},但此功能默认是禁用的。在属性解析的优先级上,首先读取
properties 元素内部定义的属性,然后是 resource 或 url 属性指定的外部文件中的属性(如果存在重复则覆盖),最后是作为参数传递给 SqlSessionFactoryBuilder.build() 方法的属性(其优先级最高,会覆盖所有其他同名属性)。这种分层配置机制使得开发者可以根据部署环境灵活调整数据库连接等敏感信息。
settings 元素
settings 元素包含了修改 MyBatis 运行时行为的关键配置项,它们对性能、映射行为和整体功能产生重要影响。
以下表格列出了几个关键的 settings 属性及其描述:
| Setting Name | Description | Valid Values | Default |
|---|---|---|---|
cacheEnabled | 全局启用或禁用所有映射器中配置的任何缓存。 | true, false | true |
lazyLoadingEnabled | 全局启用或禁用延迟加载。启用时,所有关联都将延迟加载。此值可通过 fetchType 属性在特定关联上覆盖。 | true, false | false |
aggressiveLazyLoading | 启用时,任何方法调用都将加载对象的所有延迟属性。否则,每个属性按需加载。 | true, false | false (3.4.1 及以前为 true) |
useColumnLabel | 使用列标签而不是列名。不同的驱动程序在此方面表现不同。 | true, false | true |
useGeneratedKeys | 允许 JDBC 支持生成键。需要兼容的驱动程序。此设置如果设置为 true,则强制使用生成键。 | true, false | false |
autoMappingBehavior | 指定 MyBatis 如何自动将列映射到字段/属性。NONE 禁用自动映射,PARTIAL 仅自动映射没有嵌套结果映射的结果,FULL 自动映射任何复杂度的结果映射。 | NONE, PARTIAL, FULL | PARTIAL |
defaultExecutorType | 配置默认执行器。SIMPLE 执行器不执行特殊操作。REUSE 执行器重用预处理语句。BATCH 执行器重用语句并批量更新。 | SIMPLE, REUSE, BATCH | SIMPLE |
defaultStatementTimeout | 设置驱动程序等待数据库响应的秒数。 | 任何正整数 | 未设置 (null) |
mapUnderscoreToCamelCase | 启用从经典数据库列名 A_COLUMN 到驼峰式 Java 属性名 aColumn 的自动映射。 | true, false | false |
localCacheScope | MyBatis 使用本地缓存防止循环引用并加速重复的嵌套查询。默认情况下 (SESSION),会话期间执行的所有查询都将被缓存。如果 localCacheScope=STATEMENT,本地会话将仅用于语句执行,两个不同调用之间不会共享数据。 | SESSION, STATEMENT | SESSION |
这些设置并非孤立的标志,它们共同决定了 MyBatis 的行为方式,并对应用程序的性能、资源使用和代码复杂性产生直接影响。例如,lazyLoadingEnabled 与 association 和 collection 元素上的 fetchType 属性结合使用,可以对 N+1 查询问题提供细粒度的控制,从而优化数据加载策略。
mapUnderscoreToCamelCase 对于遵循 Java 驼峰命名约定的开发者来说是一个巨大的便利,当处理数据库中常见的下划线命名列时,它减少了在简单情况下显式定义 resultMap 的需要。此外,defaultFetchSize(虽然未在表格中列出,但研究材料提及其重要性)可以显著提高大型结果集的性能,通过减少数据库往返次数来优化数据传输。因此,开发者不应盲目接受默认值,而应根据应用程序的具体需求和数据库特性,理解并调优这些设置,以实现最佳的性能和可维护性。一个经过良好调优的
mybatis-config.xml 配置能够显著提升应用程序的效率。
typeAliases 元素
typeAliases 元素允许开发者为 Java 类型定义更短的名称,从而避免在 XML 配置中使用完全限定的类名,提高了配置文件的可读性和简洁性。
示例:
<typeAliases>
<typeAlias alias="Student" type="mybatis.Student"/>
<package name="domain.blog"/>
</typeAliases>
MyBatis 提供了许多内置的常见 Java 类型别名,例如 string、int、list、map 等,可以直接使用。此外,也可以配置 MyBatis 扫描整个包以查找类型别名;如果 bean 上没有@Alias 注解,则默认使用其非大写非限定类名作为别名。
typeHandlers 元素
typeHandlers 元素用于注册自定义的类型处理器。MyBatis 使用类型处理器在设置 PreparedStatement 参数和从 ResultSet 中检索值时,进行 Java 类型和 JDBC 类型之间的转换。
示例:
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
<package name="org.mybatis.example"/>
</typeHandlers>
可以注册单个自定义 TypeHandler 实现,也可以配置 MyBatis 扫描指定包以自动发现所有 TypeHandler 实现。
environments 元素
environments 元素允许为多个数据库环境(例如,开发、测试、生产)配置 MyBatis,从而方便在不同环境中切换数据库连接配置。
default 属性指定了默认加载的环境 ID。每个
<environment> 元素都必须有一个唯一的 id。
在每个 environment 中,需要配置 transactionManager 和 dataSource。
-
transactionManager(事务管理器):- 此元素配置了 MyBatis 如何管理事务。
- 主要有两种内置类型:
JDBC(直接使用 JDBC 的提交和回滚功能)和MANAGED(MyBatis 将事务管理委托给容器,例如 Java EE 应用服务器)。 - 示例:
<transactionManager type="JDBC"/>。
-
dataSource(数据源):- 此元素配置了 JDBC
Connection对象的来源。 - 主要有三种内置类型:
UNPOOLED(每次数据库操作都打开和关闭连接,适用于简单应用但性能较慢)、POOLED(维护数据库连接池,减少连接创建和认证时间,通常推荐用于生产环境)和JNDI(从 JNDI 获取连接,适用于容器管理的数据源)。 - 在
dataSource内部,需要配置数据库连接属性,如driver、url、username和password。
示例:
<dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> - 此元素配置了 JDBC
databaseIdProvider 元素
databaseIdProvider 元素允许 MyBatis 根据不同的数据库厂商执行不同的 SQL 语句,这在支持多种数据库环境时非常有用。
type="DB_VENDOR" 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。通过
property 子元素,可以将较长的数据库产品名称映射为更短、更易管理的 databaseId 值,例如将 SQL Server 映射为 sqlserver。
mappers 元素
mappers 元素告诉 MyBatis 去哪里查找映射的 SQL 语句定义,这些定义可以存在于 XML 文件中,也可以通过注解直接定义在 Java 接口中。
映射器可以通过以下方式注册:
resource:引用类路径下的 XML 映射文件,例如<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>。url:引用完全限定路径的 XML 映射文件。class:注册一个 Mapper 接口,MyBatis 会自动查找并加载其对应的 XML 文件(如果存在)。package:注册指定包中的所有 Mapper 接口,例如<package name="org.mybatis.builder"/>。
事务管理
事务管理是任何数据库驱动应用程序的关键组成部分,它确保了数据的一致性和完整性。MyBatis 提供了灵活的事务管理选项,既支持手动控制,也支持与外部框架(如 Spring)集成以实现声明式事务。
MyBatis 核心支持 JDBC 和 MANAGED 两种事务管理器。当使用
JDBC 类型时,开发者需要在应用程序代码中手动调用 SqlSession 的 commit() 和 rollback() 方法来显式控制事务的提交和回滚。
SqlSession 的 openSession() 方法可以接受一个 autoCommit 参数,用于控制是否自动提交事务。
在现代企业应用程序开发中,事务管理的范式已经从手动控制转向了框架提供的声明式管理。例如,与 Spring 框架集成时,MyBatis-Spring 模块中的 SqlSessionTemplate 会根据 Spring 的事务配置自动管理会话的生命周期,包括提交、回滚和关闭,从而消除了手动调用事务方法的需要。Spring 的 @Transactional 注解可以用于服务层方法上,以定义事务边界,这需要 Spring 上下文中配置 DataSourceTransactionManager。类似地,MyBatis-Guice 也提供了 @Transactional 注解,用于基于 AOP 的事务管理。
这种从手动到声明式事务管理的转变,显著减少了样板代码,提高了代码的一致性和可维护性,并实现了关注点分离。虽然 MyBatis 核心库提供了手动处理事务的能力,但对于大多数实际应用,特别是使用 Spring 等框架的应用,依赖框架的声明式事务能力是更优越的方法,因为它将事务管理的复杂性从业务逻辑中解耦,使得开发者能够更专注于核心业务功能的实现。事务管理的选择直接影响代码的整洁度、可维护性和错误处理机制。
缓存机制
MyBatis 具有两层缓存机制,旨在提高数据检索性能和减少数据库负载:本地(一级)缓存和可选的二级缓存。
一级缓存 (First-Level Cache)
一级缓存是 MyBatis 的默认行为,它始终启用,并绑定到单个 SqlSession 实例的生命周期。这意味着在同一个
SqlSession 实例中,如果多次执行相同的 SQL 语句和参数,MyBatis 将直接返回第一次查询的缓存结果,从而避免了冗余的数据库调用。
一级缓存会在当前事务结束时(或者当 autoCommit=true 时,在每个语句执行结束时)被清除。此外,任何
insert、update 或 delete 语句的执行也会导致一级缓存被清除。一级缓存的范围可以通过
localCacheScope 设置进行配置,默认为 SESSION 级别(即在整个 SqlSession 期间有效),也可以设置为 STATEMENT 级别(仅在当前语句执行期间有效)。需要注意的是,一级缓存中的对象永远不会在不同的事务之间共享。
二级缓存 (Second-Level Cache)
二级缓存是可选的,它允许数据在多个 SqlSession 实例之间甚至不同应用程序之间共享。要启用二级缓存,只需在映射器 XML 文件中添加一个
<cache/> 元素。
一个简单的 <cache/> 声明具有以下默认效果:
- 该映射器文件中所有
select语句的结果都将被缓存。 - 该映射器文件中所有
insert、update和delete语句都将刷新缓存。 - 缓存将使用最近最少使用(LRU)算法进行淘汰。
- 缓存不会基于时间进行刷新(即没有
flushInterval)。 - 缓存将存储1024 个对列表或对象的引用。
- 缓存被视为读/写缓存,这意味着返回的对象是副本,可以安全地修改,而不会影响缓存中的原始数据。
可以通过在 <cache> 元素中添加属性来定制这些默认行为:
| Attribute | Description | Valid Values | Default |
|---|---|---|---|
eviction | 淘汰策略。 | LRU, FIFO, SOFT, WEAK | LRU |
flushInterval | 缓存刷新的时间间隔(毫秒)。 | 正整数 | 未设置(仅通过 DML 刷新) |
size | 存储的最大引用数量。 | 正整数 | 1024 |
readOnly | 如果为 true,则向所有调用者返回缓存对象的相同实例(性能更高,但对象不能被修改)。如果为 false,则返回缓存对象的副本(更安全,但较慢)。 | true, false | false |
type | 自定义缓存实现(必须实现 org.apache.ibatis.cache.Cache 接口)。 | 完全限定类名 | 内置 |
二级缓存是事务性的,它在 SqlSession 提交时更新,或者在回滚但没有执行 flushCache=true 的 insert/delete/update 语句时更新。
缓存优化策略
为了充分利用 MyBatis 的缓存机制并避免潜在问题,可以采取以下优化策略:
- L1 缓存范围的合理配置:根据应用程序的并发需求和数据一致性要求,合理配置
localCacheScope为SESSION或STATEMENT。 - L2 缓存用于读多写少数据:对读操作频繁且不经常更改的数据启用二级缓存,以减少数据库访问。
- 过期时间:为二级缓存设置适当的
flushInterval,以平衡数据新鲜度和缓存命中率。 - 分布式环境下的关键考量:MyBatis 内置的二级缓存不适用于分布式环境,因为不同的应用程序实例之间无法通信以清除陈旧缓存,这极易导致数据不一致。在这种情况下,必须使用外部缓存服务(如 Ehcache、Redis)来管理共享缓存。关于 MyBatis 缓存“无用”的观点 凸显了内置缓存机制在现代分布式架构中的局限性。在分布式设置中依赖默认的二级缓存将不可避免地导致数据陈旧。这强调了在设计分布式系统时,必须引入外部的、能够跨实例同步的缓存解决方案。
defaultFetchSize的调整:在mybatis-config.xml中调整defaultFetchSize设置可以显著提高大型结果集的性能,通过减少数据库连接和数据传输的频率来优化数据获取(例如,推荐值在-5000 之间)。然而,需要注意内存溢出(OOM)的风险,对于过大的数据集,应始终实现适当的分页逻辑。- SQL 优化:缓存仅是性能优化的一部分。索引的合理添加、使用
EXPLAIN命令分析 SQL 执行计划以识别低效操作,以及使用健壮的连接池(如 HikariCP、Druid)来复用数据库连接,都是与缓存互补的性能优化策略。
MyBatis 的缓存机制为性能提升提供了强大的工具,但开发者必须深入理解其工作原理、适用场景以及在分布式环境中的局限性。简单地启用缓存并非万能药,不当的配置可能引入严重的数据一致性问题。
第二部分:单表操作
MyBatis 在单表操作方面提供了简洁而强大的能力,允许开发者通过 POJO、Mapper 接口和 XML 映射文件来定义和执行 CRUD(创建、读取、更新、删除)操作。
POJO 类定义
POJO(Plain Old Java Object)是 Java 应用程序中用于表示数据库表记录的普通 Java 对象。它们通常包含与数据库表列对应的属性,以及相应的 getter 和 setter 方法。MyBatis 通过这些 POJO 类来映射数据库查询结果或作为操作的参数。
示例:Student POJO 类
public class Student {
private int id;
private String name;
private String branch;
private int percentage;
private int phone;
private String email;
public Student() {}
public Student(int id, String name, String branch, int percentage, int phone, String email) {
this.id = id;
this.name = name;
this.branch = branch;
this.percentage = percentage;
this.phone = phone;
this.email = email;
}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getBranch() { return branch; }
public void setBranch(String branch) { this.branch = branch; }
public int getPercentage() { return percentage; }
public void setPercentage(int percentage) { this.percentage = percentage; }
public int getPhone() { return phone; }
public void setPhone(int phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public String toString() {
return "Id = " + id + " - Name = " + name + " - Branch = " + branch +
" - Percentage = " + percentage + " - Phone = " + phone +
" - Email = " + email;
}
}
Mapper 接口定义
Mapper 接口是 MyBatis 中定义 SQL 操作的 Java 接口。它提供了类型安全的访问数据库的方法,并且可以与 XML 映射文件或注解结合使用来定义 SQL 语句。
示例:StudentMapper 接口
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface StudentMapper {
// 查询所有学生
@Select("SELECT * FROM STUDENT")
List<Student> getAllStudents();
// 根据ID查询学生
@Select("SELECT * FROM STUDENT WHERE ID = #{id}")
Student getStudentById(int id);
// 插入学生记录
@Insert("INSERT INTO STUDENT(NAME, BRANCH, PERCENTAGE, PHONE, EMAIL) VALUES(#{name}, #{branch}, #{percentage}, #{phone}, #{email})")
void insertStudent(Student student);
// 更新学生记录
@Update("UPDATE STUDENT SET NAME=#{name}, BRANCH=#{branch}, PERCENTAGE=#{percentage}, PHONE=#{phone}, EMAIL=#{email} WHERE ID=#{id}")
void updateStudent(Student student);
// 根据ID删除学生记录
@Delete("DELETE FROM STUDENT WHERE ID = #{id}")
void deleteStudent(int id);
}
上述示例展示了如何使用 MyBatis 的注解来定义 SQL 操作。MyBatis 提供了 @Select、@Insert、@Update 和 @Delete 等注解,可以直接将 SQL 语句与 Mapper 接口中的方法关联起来。这种方式对于简单的 SQL 语句非常方便,减少了 XML 配置的需要。
XML 映射文件结构
Mapper XML 文件是 MyBatis 中定义映射 SQL 语句的重要组成部分,它提供了比注解更强大的功能,特别是在处理复杂映射和动态 SQL 时。
MyBatis Mapper XML 文件包含以下主要元素,它们应按特定顺序定义:
cache: 配置给定命名空间的缓存。cache-ref: 引用另一个命名空间的缓存配置。resultMap: 描述如何从数据库结果集中加载对象的复杂且强大的元素。sql: 可重用的 SQL 代码片段。insert: 映射的 INSERT 语句。update: 映射的 UPDATE 语句。delete: 映射的 DELETE 语句。select: 映射的 SELECT 语句。
<mapper> 元素
<mapper> 元素是所有 SQL 映射语句的容器。它包含一个 namespace 属性,该属性通常设置为 Mapper 接口的完全限定名,以实现接口与 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.StudentMapper">
</mapper>
<resultMap> 元素
resultMap 元素是 MyBatis 中最重要和强大的功能之一,它旨在消除大量用于从 ResultSet 中检索数据的 JDBC 样板代码,并支持 JDBC 不直接支持的功能。对于简单情况,MyBatis 可以自动将列映射到 HashMap 键或 JavaBean 属性,只要列名与属性名完全匹配。
-
id 与 result 元素
id 和 result 是最基本的映射元素。它们都将单个列值映射到简单数据类型的单个属性或字段。关键区别在于
id元素将结果标记为标识符属性,这有助于提高缓存和嵌套结果映射的性能。id和result元素支持以下属性:property: 要映射列结果的字段或属性。支持复杂的属性导航(例如address.street.number)。column: 数据库中的列名或别名列标签。javaType: 完全限定的 Java 类名或类型别名。如果映射到HashMap,则需要此属性以确保正确的TypeHandler。jdbcType: JDBC 类型。仅在插入、更新或删除时用于可空列。typeHandler: 覆盖特定映射的默认类型处理器。
-
constructor 元素
constructor 元素用于将结果注入到类的构造函数中,这对于不可变类或偏好构造函数注入而非公共方法的场景非常有用。MyBatis 通过匹配
arg元素的javaType与构造函数参数的类型和顺序来识别构造函数。自.4.3 版本起,可以通过指定每个参数的name属性来允许arg元素以任意顺序排列,前提是项目使用-parameters编译器选项编译或使用了@Param注解。示例:
<constructor> <idArg column="id" javaType="int"/> <arg column="username" javaType="String"/> <arg column="age" javaType="_int"/> </constructor> -
处理列名与属性名不匹配
当数据库列名与 JavaBean 属性名不完全匹配时,可以使用 SQL SELECT 子句中的别名来使标签匹配,或者定义一个外部 resultMap 来显式映射不匹配的名称。
示例:
<resultMap id="userResultMap" type="User"> <id property="id" column="user_id" /> <result property="username" column="user_name"/> <result property="password" column="hashed_password"/> </resultMap>
<select> 元素
select 语句是 MyBatis 中最常用的元素之一,因为它专注于查询和结果映射。
基本示例:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
此示例定义了一个名为 selectPerson 的语句,它接受一个 int(或 Integer)参数,并返回一个 HashMap,其中列名作为键映射到行值。#{id} 符号指示 MyBatis 创建一个 PreparedStatement 参数,类似于 JDBC 中的 ?。
select 元素属性:
select 元素支持多种属性来配置其行为,包括 id(唯一标识符)、parameterType(输入参数类型)、resultType(期望返回类型)或 resultMap(命名结果映射引用)、flushCache(是否刷新缓存)、useCache(是否使用缓存)、timeout(超时时间)、fetchSize(抓取大小)和 statementType(JDBC 语句类型)。
<insert> 元素
insert 元素用于定义映射的 INSERT 语句。
基本示例:
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
insert 元素属性:
insert 元素支持 id、parameterType、flushCache、timeout 和 statementType 等通用属性。
特定属性:
useGeneratedKeys: 如果为true,MyBatis 将使用 JDBC 的getGeneratedKeys方法来检索数据库内部生成的键(例如,自增字段)。keyProperty: 标识 MyBatis 将设置生成键值的属性。可以是以逗号分隔的列表,用于多个生成列。keyColumn: 设置生成键的列名。在某些数据库中(例如 PostgreSQL),如果键列不是第一列,则需要此属性。
selectKey 元素(用于键生成):
对于不支持自动生成列类型或 JDBC 驱动程序不支持的数据库,可以使用 selectKey 元素来生成键。
示例:
<insert id="insertAuthor">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
insert into Author
(id, username, password, email,bio, favourite_section)
values
(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>
在此示例中,selectKey 语句首先运行,设置 Author 的 id 属性,然后调用 insert 语句。
selectKey 元素支持 keyProperty、keyColumn、resultType、order(BEFORE 或 AFTER)和 statementType 属性。
<update> 元素
update 元素用于定义映射的 UPDATE 语句。
基本示例:
<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
update 元素支持与 insert 类似的通用属性。
<delete> 元素
delete 元素用于定义映射的 DELETE 语句。
基本示例:
<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>
delete 元素支持与 insert 类似的通用属性。
Java 代码执行单表操作
在配置好 MyBatis 的环境和映射文件后,就可以在 Java 代码中执行单表操作了。
构建 SqlSessionFactory
SqlSessionFactory 是 MyBatis 应用程序的核心,用于创建 SqlSession 实例。它通常在应用程序启动时构建一次,并作为单例存在。
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml"; // 假设配置文件在类路径下
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error initializing SqlSessionFactory: " + e.getMessage());
}
}
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
获取 SqlSession 和 Mapper 实例
SqlSession 包含了执行 SQL 命令所需的所有方法。由于 SqlSession 是非线程安全的,每次操作都应获取新的实例并在完成后关闭。Mapper 接口实例则从
SqlSession 获取。
import org.apache.ibatis.session.SqlSession;
public class StudentService {
public Student getStudent(int id) {
// 使用 try-with-resources 确保 SqlSession 自动关闭
try (SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession()) {
StudentMapper mapper = session.getMapper(StudentMapper.class);
return mapper.getStudentById(id);
}
}
public void addStudent(Student student) {
try (SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession()) {
StudentMapper mapper = session.getMapper(StudentMapper.class);
mapper.insertStudent(student);
session.commit(); // 提交事务
}
}
//... 其他 CRUD 方法类似
}
执行 CRUD 操作
通过 SqlSession 获取的 Mapper 实例,可以直接调用其方法来执行对应的 SQL 操作。对于 insert、update 和 delete 操作,在执行后通常需要手动调用 session.commit() 来提交事务,除非 SqlSession 是在自动提交模式下打开的。
public class MainApp {
public static void main(String args) {
StudentService studentService = new StudentService();
// 插入学生
Student newStudent = new Student(0, "张三", "计算机",90,1381234567, "zhangsan@example.com");
studentService.addStudent(newStudent);
System.out.println("学生插入成功: " + newStudent.getName());
// 查询学生
Student student = studentService.getStudent(1); // 假设ID为1
if (student!= null) {
System.out.println("查询到学生: " + student.toString());
} else {
System.out.println("未找到学生。");
}
// 更新学生
if (student!= null) {
student.setPercentage(95);
studentService.updateStudent(student);
System.out.println("学生更新成功: " + student.getName());
}
// 删除学生
// studentService.deleteStudent(1); // 假设ID为1
// System.out.println("学生删除成功。");
}
}
通过上述步骤,可以清晰地看到 MyBatis 如何通过 POJO、Mapper 接口和 XML 映射文件(或注解)协同工作,实现对数据库单表的便捷、高效操作。这种分层设计使得数据访问逻辑与业务逻辑分离,提高了代码的可维护性和可测试性。
第三部分:多表查询与复杂映射
MyBatis 在处理多表查询和复杂对象映射方面提供了强大的功能,包括动态 SQL、关联映射(一对一)和集合映射(一对多)。这些特性使得 MyBatis 能够灵活地适应各种复杂的数据库结构和业务需求。
动态 SQL
动态 SQL 是 MyBatis 的一个非常强大的特性,它允许开发者根据不同的条件动态地构建 SQL 语句,从而避免了硬编码多个相似查询的需要。MyBatis 使用基于 OGNL(Object-Graph Navigation Language)的表达式,并提供了多种元素来构建动态 SQL 语句。
if 元素
if 元素用于根据条件有选择地包含 SQL 片段。
示例:可选的文本搜索
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = 'ACTIVE'
<if test="title!= null">
AND title like #{title}
</if>
</select>
如果 title 参数不为 null,则 AND title like #{title} 条件将被添加到 SQL 语句中。否则,该条件将被忽略,返回所有活跃的博客。
choose, when, otherwise 元素
choose、when 和 otherwise 元素类似于 Java 中的 switch 语句,允许在多个条件中选择一个来应用。
示例:多条件搜索
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title!= null">
AND title like #{title}
</when>
<when test="author!= null and author.name!= null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured =
</otherwise>
</choose>
</select>
此语句将首先检查 title 是否存在,如果存在则按标题搜索;否则,检查 author 是否存在,如果存在则按作者名搜索;如果两者都不存在,则返回特色博客。
where, set, trim 元素
这些元素有助于处理动态 SQL 中常见的挑战,例如多余的 WHERE、AND/OR 关键字,以及 UPDATE 语句中多余的逗号。
-
where:where元素会自动插入 "WHERE" 关键字,前提是其包含的标签返回了任何内容。此外,如果内容以 "AND" 或 "OR" 开头,它会自动将其去除。示例:动态
WHERE子句<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state!= null"> state = #{state} </if> <if test="title!= null"> AND title like #{title} </if> <if test="author!= null and author.name!= null"> AND author_name like #{author.name} </if> </where> </select> -
set:set元素用于动态UPDATE语句,它会自动添加SET关键字并消除多余的逗号。示例:动态
UPDATE语句<update id="updateAuthorIfNecessary"> update Author <set> <if test="username!= null">username=#{username},</if> <if test="password!= null">password=#{password},</if> <if test="email!= null">email=#{email},</if> <if test="bio!= null">bio=#{bio}</if> </set> where id=#{id} </update> -
trim:trim元素提供了更细粒度的控制,可以自定义前缀、后缀以及需要覆盖的文本。where和set元素实际上是trim元素的特殊应用。示例(等同于
where):<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>示例(等同于
set):<trim prefix="SET" suffixOverrides=","> ... </trim>
foreach 元素
foreach 元素用于迭代集合(如列表、数组或 Map),常用于构建 SQL 中的 IN 条件。
示例:构建 IN 条件
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
foreach 元素允许指定集合、迭代变量(item 和 index)、起始字符串(open)、结束字符串(close)和分隔符(separator),并智能地避免添加多余的分隔符。
sql 与 include 元素
sql 元素用于定义可重用的 SQL 代码片段,这些片段可以通过 include 元素在其他语句中引用。这有助于减少 SQL 重复并提高可维护性。
include 元素支持在引用时通过 property 子元素进行参数化。
示例:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
bind 元素
bind 元素允许从 OGNL 表达式创建一个变量并将其绑定到上下文,这在需要预处理参数值时非常有用。
示例:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
多数据库厂商支持
如果配置了 databaseIdProvider,MyBatis 会提供一个 _databaseId 变量,可以在动态 SQL 中使用,从而根据当前数据库厂商执行不同的 SQL 逻辑。这对于支持多种数据库环境的应用程序非常重要。
示例:
<insert id="insert">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
<if test="_databaseId == 'oracle'">
select seq_users.nextval from dual
</if>
<if test="_databaseId == 'db2'">
select nextval for seq_users from sysibm.sysdummy1"
</if>
</selectKey>
insert into users values (#{id}, #{name})
</insert>
可插拔脚本语言
自 MyBatis.2 版本起,MyBatis 支持可插拔的脚本语言,允许开发者使用除默认 XML 之外的其他语言编写动态 SQL 查询。这需要实现
LanguageDriver 接口,并通过 mybatis-config.xml 或 @Lang 注解进行配置。
关联映射 (Association Mapping - One-to-One)
association 元素用于处理“一对一”类型的关系,例如一个博客有一个作者。这意味着多个结果将汇总到这个复杂类型中。
association 元素支持 property、javaType、jdbcType 和 typeHandler 等属性。MyBatis 提供了两种主要方式来加载关联:嵌套查询和嵌套结果。
1. 嵌套查询 (Nested Select for Association)
这种方法通过执行另一个映射的 SQL 语句来检索关联的复杂类型。
示例:
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
在这种模式下,selectBlog 查询首先加载博客数据,然后 MyBatis 会使用博客的 author_id 作为参数,调用 selectAuthor 查询来加载作者信息。虽然这种方法简单直观,但它可能导致“N+1 查询问题”,即对于一个包含 N 条记录的列表,会执行 N+1 次数据库查询(1 次主查询 + N 次关联查询),这在处理大量数据时可能影响性能。MyBatis 可以通过延迟加载来缓解即时开销,但遍历加载的列表时仍会触发所有单独的加载。
2. 嵌套结果 (Nested Results / Join Mapping for Association)
这种方法使用单个 SQL 查询(通常包含 JOIN 操作)来同时检索主对象及其关联对象的数据,然后将连接结果中的重复子集映射到相应的对象。这种方式可以避免 N+1 查询问题,提高查询效率。
示例(使用 JOIN):
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" resultMap="authorResult" />
</resultMap>
<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>
在此示例中,blogResult 的 author 关联委托给 authorResult resultMap 来从连接的 ResultSet 中加载 Author 实例。
id 元素在嵌套结果映射中至关重要,它用于唯一标识结果并提高性能。
3. 多结果集 (Multiple ResultSets for Association)
自 MyBatis 3.2.3 版本起,可以使用存储过程或返回多个 ResultSet 的多语句来避免 JOIN 操作。
示例:存储过程返回两个结果集
SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}
映射语句与 resultSets 属性:
<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
{call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>
<resultMap id="blogResult" type="Blog">
<id property="id" column="id" />
<result property="title" column="title"/>
<association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
</association>
</resultMap>
这里,author 关联将从名为 authors 的 ResultSet 中填充。
集合映射 (Collection Mapping - One-to-Many)
collection 元素用于处理“一对多”类型的关系,例如一个博客有多个帖子。在 Java 类中,这通常表示为一个
List 或 Set。collection 元素的工作方式与 association 几乎相同,但增加了 ofType 属性,用于指定集合中元素的类型。
1. 嵌套查询 (Nested Select for Collection)
与关联映射类似,这种方法使用单独的 select 语句来加载集合中的项目。
示例:
<resultMap id="blogResult" type="Blog">
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
此配置将“一个包含 Post 类型的 ArrayList 集合的帖子”映射到 Blog 对象。javaType="ArrayList" 通常不是必需的,因为 MyBatis 可以推断出集合类型。
2. 嵌套结果 (Nested Results / Join Mapping for Collection)
这种方法使用单个 SQL 查询(通常包含 JOIN 操作)来同时检索主对象及其集合中的项目数据。
示例(使用 JOIN):
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>
同样,id 元素对于正确的映射和性能至关重要。
3. 多结果集 (Multiple ResultSets for Collection)
与关联映射类似,也可以使用存储过程或返回多个 ResultSet 的多语句来填充集合。
示例:存储过程返回两个结果集
SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM POST WHERE BLOG_ID = #{id}
映射语句与 resultSets 属性:
<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
{call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
</select>
<resultMap id="blogResult" type="Blog">
<id property="id" column="id" />
<result property="title" column="title"/>
<collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
</collection>
</resultMap>
这里,posts 集合将从名为 posts 的 ResultSet 中填充。
MyBatis 允许任意深度和组合的关联和集合映射。在选择最佳方法时,建议进行性能考量并进行彻底的测试(特别是单元测试),以确保应用程序的最佳行为。
结论与建议
MyBatis 作为一款强大的持久层框架,通过其灵活的 SQL 映射能力,为 Java 应用程序提供了高效且可控的数据访问解决方案。本说明文档从环境配置、单表操作到复杂的多表查询和映射,全面阐述了 MyBatis 的核心特性和使用方法。
核心要点总结:
- 架构清晰,职责分离: MyBatis 的核心围绕
SqlSessionFactory(应用级单例,负责构建)、SqlSession(会话/请求级,负责执行)和 Mapper 接口(类型安全的操作接口)展开。这种设计促进了模块化和可维护性。 - 灵活的配置选项:
mybatis-config.xml提供了丰富的配置选项,从数据源和事务管理器到类型别名和运行时行为调优。properties和settings元素尤其重要,它们直接影响应用程序的性能和开发便利性,如mapUnderscoreToCamelCase简化了命名转换,defaultFetchSize优化了数据抓取效率。 - SQL 控制的强大: MyBatis 允许开发者直接编写 SQL,并通过 XML 映射文件或注解进行绑定。这种对 SQL 的完全控制,使其在处理复杂查询、性能调优或与特定数据库功能交互时表现出色。
- 动态 SQL 的灵活性: 动态 SQL 元素(如
if、choose、where、set、foreach、bind、sql/include)极大地增强了 SQL 语句的适应性,能够根据运行时条件动态构建查询,减少了冗余代码。 - 复杂映射能力:
resultMap元素是 MyBatis 的核心优势之一,它能够处理复杂的一对一(association)和一对多(collection)关系。通过嵌套查询、嵌套结果(JOIN)或多结果集等方式,MyBatis 能够将复杂的数据库结果集映射到丰富的 Java 对象模型中。 - 事务与缓存的考量: MyBatis 提供了手动事务管理,但现代实践更倾向于与 Spring 等框架集成,利用其声明式事务能力简化开发。内置的二级缓存虽然有助于性能提升,但在分布式环境中存在数据一致性挑战,通常需要集成外部缓存解决方案。
实践建议:
- 理解组件生命周期: 务必正确管理
SqlSession的生命周期,确保其在每次使用后关闭,避免资源泄漏。在 Spring 环境中,优先使用SqlSessionTemplate。 - 优化配置: 深入理解
mybatis-config.xml中的各项settings属性,并根据实际应用场景进行调优。例如,合理设置defaultFetchSize可以显著提升大数据量查询的性能。 - 选择合适的 SQL 映射方式: 对于简单查询,注解方式简洁高效;对于复杂查询、动态 SQL 或多表映射,XML 映射文件提供了更强大的控制和可维护性。
- 谨慎使用二级缓存: 在分布式系统中,切勿依赖 MyBatis 内置的二级缓存,因为它无法保证数据一致性。应考虑集成 Ehcache、Redis 等外部分布式缓存方案。
- 持续性能监控: 定期使用
EXPLAIN分析 SQL 查询的执行计划,并结合连接池优化和索引策略,确保数据库操作的高效性。 - 遵循最佳实践: 避免在 SQL 中包含过多的业务逻辑,保持 SQL 的可读性和可测试性。对于动态 SQL,优先使用
where、set等内置元素,并合理利用foreach处理集合参数。
MyBatis 提供了一个强大的工具集,使开发者能够有效地管理 Java 应用程序中的数据持久化层。通过深入理解其核心概念和灵活运用其各项特性,开发者可以构建出高性能、可维护且适应性强的数据库驱动应用程序。