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

yumo6661个月前 (03-23)技术文章14

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 查询问题,提供更好的整体性能。 但如果你的场景确实非常注重初始查询速度,并且关联对象很少被访问,那么嵌套查询也是一个可行的选择。

相关文章

B端|一文讲透「筛选」功能背后的逻辑-索引

在做筛选功能是一般都要加载几秒,这种问题是如何产生的?这篇文章,作者为我们解答了这个问题,一起来看看。在做筛选功能时,总会遇到响应慢的问题:为什么点击筛选之后,要loading几秒?这类问题又不是功能...

为什么大厂不建议使用多表join?

前言:在数据库中,JOIN 是一种操作,用于将多个表中的数据联接起来。通过 JOIN 操作,可以根据表之间的关联关系,将相关联的数据合并到一起,以便进行更复杂的查询和分析。那么为什么不推荐使用多表进行...