MyBatis实现一对一有几种方式?具体怎么操作的?

createh51周前 (03-27)技术教程1

MyBatis 实现一对一关系主要有两种方式,这两种方式都依赖于 标签在 resultMap 中的配置。 核心区别在于如何获取关联对象的数据:

1. 嵌套查询 (Nested Select/Lazy Loading):

  • 原理: 先查询主表数据,然后在需要访问关联对象时,再执行一个单独的 SQL 查询去获取关联对象的数据。这是一种延迟加载 (lazy loading) 的方式。
  • 适用场景: 当一对一关系不是每次都需要加载,或者关联对象的加载开销较大,希望提高初始查询性能时。
  • 操作步骤:
  • a) 数据库表结构 (示例):
  • sql复制代码
  • -- 用户表 (主表) CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50), -- ... 其他用户字段 address_id INT UNIQUE -- 外键关联地址表 ); -- 地址表 (关联表) CREATE TABLE addresses ( id INT PRIMARY KEY AUTO_INCREMENT, street VARCHAR(100), city VARCHAR(50), -- ... 其他地址字段 user_id INT UNIQUE -- 反向外键 (可选,根据关系方向决定是否需要) ); -- 假设 users.address_id 关联 addresses.id
  • b) Java 实体类:
  • java复制代码
  • public class User { private Integer id; private String username; // ... 其他用户字段 private Address address; // 一对一关联 Address 对象 // Getters and Setters // ... } public class Address { private Integer id; private String street; private String city; // ... 其他地址字段 // 可以选择是否需要 User 属性,取决于关系方向和需求 // private User user; // Getters and Setters // ... }
  • c) Mapper XML 配置:
  • xml复制代码
  • <mapper namespace="com.example.mapper.UserMapper"> <resultMap id="UserResultMap" type="com.example.model.User"> <id property="id" column="user_id"/> <result property="username" column="username"/> <association property="address" javaType="com.example.model.Address" column="address_id" <!--users 表的 address_id 传递给 select 查询 --> select="com.example.mapper.AddressMapper.getAddressById"/> </resultMap> <select id="getUserById" parameterType="int" resultMap="UserResultMap"> SELECT u.id as user_id, u.username, u.address_id FROM users u WHERE u.id = #{id} </select> </mapper> <mapper namespace="com.example.mapper.AddressMapper"> <resultMap id="AddressResultMap" type="com.example.model.Address"> <id property="id" column="address_id"/> <result property="street" column="street"/> <result property="city" column="city"/> </resultMap> <select id="getAddressById" parameterType="int" resultMap="AddressResultMap"> SELECT a.id as address_id, a.street, a.city FROM addresses a WHERE a.id = #{id} </select> </mapper>
  • d) 关键配置解释:
    • : 定义 User 实体类的 address 属性是一对一关联。
    • javaType="com.example.model.Address": 指定关联属性的 Java 类型。
    • column="address_id": 指定从 users 表查询结果中,哪个列的值 (这里是 address_id) 将作为参数传递给 select 属性指定的查询。
    • select="com.example.mapper.AddressMapper.getAddressById": 指定用于查询关联 Address 对象的 Mapper 接口和方法。MyBatis 会根据 column 传递的 address_id 值,调用 AddressMapper.getAddressById 方法执行查询。

2. 连接查询 (Join Query/Eager Loading):

  • 原理: 使用 SQL 的 JOIN 语句,一次性查询出主表和关联表的数据。这是一种立即加载 (eager loading) 的方式。
  • 适用场景: 当一对一关系通常都需要加载,或者希望减少数据库查询次数,提高性能时。
  • 操作步骤:
  • a) 数据库表结构 (同上例)
  • b) Java 实体类 (同上例)
  • c) Mapper XML 配置:
  • xml复制代码
  • <mapper namespace="com.example.mapper.UserMapper"> <resultMap id="UserResultMap" type="com.example.model.User"> <id property="id" column="user_id"/> <result property="username" column="username"/> <association property="address" javaType="com.example.model.Address" resultMap="AddressResultMap"/> </resultMap> <resultMap id="AddressResultMap" type="com.example.model.Address"> <id property="id" column="address_id"/> <result property="street" column="address_street"/> <result property="city" column="address_city"/> </resultMap> <select id="getUserWithAddressById" parameterType="int" resultMap="UserResultMap"> SELECT u.id as user_id, u.username, a.id as address_id, a.street as address_street, a.city as address_city FROM users u LEFT JOIN addresses a ON u.address_id = a.id WHERE u.id = #{id} </select> </mapper>
  • d) 关键配置解释:
    • : 定义 User 实体类的 address 属性是一对一关联,并直接引用 AddressResultMap。
    • resultMap="AddressResultMap": 指定使用 AddressResultMap 来映射查询结果中的地址相关列到 Address 对象。
    • columnPrefix="address_" (在 AddressResultMap 中,可选但推荐): 如果查询结果中,地址相关的列名都带有相同的前缀 (例如 address_id, address_street 等),可以使用 columnPrefix 属性来简化 AddressResultMap 的配置。 例如,如果设置 columnPrefix="address_", AddressResultMap 中的 会自动映射到查询结果中的 address_id 列, 会自动映射到 address_street 列,以此类推。 但更推荐在 SQL 中使用别名,更清晰易懂。
    • SQL JOIN 语句: 在 getUserWithAddressById 查询中,使用了 LEFT JOIN (或其他合适的 JOIN 类型) 将 users 表和 addresses 表连接起来,一次性查询出所有需要的数据。
    • 列别名 (重要): 由于连接查询会返回多个表的列,为了避免列名冲突,并且方便 resultMap 映射,强烈建议在 SQL 查询中使用列别名,例如 a.id as address_id, a.street as address_street 等。

选择哪种方式?

  • 性能考虑:
    • 嵌套查询 (Lazy Loading): 初始查询速度快,只查询主表数据。但如果后续需要访问关联对象,会触发额外的数据库查询,可能导致 “N+1 查询问题” (如果批量查询用户,每个用户都需要单独查询地址)。
    • 连接查询 (Eager Loading): 一次性查询所有数据,减少数据库查询次数,性能通常更好,尤其是在经常需要访问关联对象的情况下。但初始查询可能较慢,返回的数据量也可能更大。
  • 数据访问模式:
    • 如果应用程序中,大部分情况下只需要用户信息,而很少需要地址信息,那么嵌套查询可能更合适。
    • 如果应用程序中,用户和地址信息经常一起使用,那么连接查询可能更合适。
  • 复杂性:
    • 嵌套查询: 配置相对简单,SQL 语句也比较简单。
    • 连接查询: SQL 语句可能更复杂 (需要 JOIN 和别名),resultMap 配置也可能稍微复杂一些 (需要处理列别名或 columnPrefix)。

总结:

特性

嵌套查询 (Nested Select)

连接查询 (Join Query)

加载方式

延迟加载 (Lazy Loading)

立即加载 (Eager Loading)

查询次数

1 + N (可能)

1

初始查询速度

整体性能

可能较差 (N+1 问题)

通常更好

配置复杂度

简单

稍复杂

SQL 复杂度

简单

稍复杂

适用场景

关联对象非必需, 初始性能敏感

关联对象常用, 整体性能敏感

在实际项目中,你需要根据具体的业务需求、数据访问模式和性能要求来选择合适的一对一关联实现方式。 通常来说,连接查询 (Join Query) 在大多数情况下是更推荐的选择,因为它能避免 N+1 查询问题,提供更好的整体性能。 但如果你的场景确实非常注重初始查询速度,并且关联对象很少被访问,那么嵌套查询也是一个可行的选择。

相关文章

100个Java工具类之69:提供HTTP实体处理功能的EntityUtils

在Java开发中,org.apache.http.util.EntityUtils是一个非常实用的工具类,尤其在进行HTTP请求和响应处理时。它是Apache HttpClient库的一部分,提供了一...

java编程语言中实体类属性自动生成lombok插件_v1

大家好,欢迎来到人工智复,我们的使命是互相勉励,坚定信念,认准自己的方向,坚持到底。//1. 依赖 org.projectlombok lombok 1.14.8 //2....

JPA实体类注解,看这篇就全会了

基本注解@Entity标注于实体类声明语句之前,指出该 Java 类为实体类,将映射到指定的数据库表。name(可选):实体名称。 缺省为实体类的非限定名称。该名称用于引用查询中的实体。不与 @Tab...

mybatis根据表逆向自动化生成代码:自动生成实体类、mapper文件

若采用mybatis框架,数据库新建表,手动编写的话,需要编写大量的实体类、mapper文件、mapper.xml文件,都是一些重复且有规律的工作。我们可以引用插件,然后做配置,自动生成这些文件,提供...

Java 实体映射工具 MapStruct

简介: 让你的DO(业务实体对象),DTO(数据传输对象)数据转换更简单强大前言 在软件架构中,分层式结构是最常见,各层之间有其独立且隔离的业务逻辑,也因而各层有自己的输入输出对象,也就是代码中见到各...

Java的抽象类与举例说明

#暑期创作大赛#1.抽象类我们知道类是产生对象的模板;那么我们可以将抽象类理解为是产生 实体类的模板。在 Java 中可以专门创建一种父类,它的子类必须遵循父类设定的规则,但父类又不能 直接创建对象,...