本文目录导读:

在Java中实现一对一查询(One-to-One Relationship)通常有两种常见方式:ORM框架(如MyBatis或JPA/Hibernate) 或 纯JDBC手动关联,下面分别给出案例。
使用 MyBatis 实现一对一查询
假设有两张表:user(用户)和 id_card(身份证),每个用户对应一张身份证。
数据库表结构
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
CREATE TABLE id_card (
id INT PRIMARY KEY AUTO_INCREMENT,
card_number VARCHAR(18) UNIQUE,
user_id INT UNIQUE, -- 一对一关键:唯一约束
FOREIGN KEY (user_id) REFERENCES user(id)
);
Java实体类
public class User {
private int id;
private String name;
private IdCard idCard; // 一对一关联
// getter/setter
}
public class IdCard {
private int id;
private String cardNumber;
private int userId;
// getter/setter
}
MyBatis Mapper(XML方式)
UserMapper.xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 方式1:嵌套结果(推荐,性能较好) -->
<select id="getUserWithIdCard" resultMap="userWithCardMap">
SELECT
u.id, u.name,
c.id as card_id, c.card_number, c.user_id
FROM user u
LEFT JOIN id_card c ON u.id = c.user_id
WHERE u.id = #{id}
</select>
<resultMap id="userWithCardMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 一对一关联:使用 association -->
<association property="idCard" javaType="IdCard">
<id property="id" column="card_id"/>
<result property="cardNumber" column="card_number"/>
<result property="userId" column="user_id"/>
</association>
</resultMap>
<!-- 方式2:嵌套查询(N+1问题风险,谨慎使用) -->
<select id="getUserWithIdCardByNested" resultMap="userWithCardNestedMap">
SELECT * FROM user WHERE id = #{id}
</select>
<resultMap id="userWithCardNestedMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="idCard" column="id"
select="com.example.mapper.IdCardMapper.findById"/>
</resultMap>
</mapper>
调用示例
User user = userMapper.getUserWithIdCard(1); System.out.println(user.getName() + " - " + user.getIdCard().getCardNumber());
使用 JPA/Hibernate 实现一对一查询
实体类注解
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private IdCard idCard;
// getter/setter
}
@Entity
@Table(name = "id_card")
public class IdCard {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "card_number")
private String cardNumber;
@OneToOne
@JoinColumn(name = "user_id", unique = true)
private User user;
// getter/setter
}
Repository查询
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
// 自动支持延迟加载,可直接调用
}
// 使用
User user = userRepository.findById(1).orElse(null);
System.out.println(user.getIdCard().getCardNumber()); // 自动加载身份证
纯 JDBC 手动实现(适合简单场景)
public User getUserWithIdCard(int userId) {
String sql = "SELECT u.*, c.* FROM user u LEFT JOIN id_card c ON u.id = c.user_id WHERE u.id = ?";
User user = null;
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, userId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
IdCard card = new IdCard();
card.setId(rs.getInt("c.id"));
card.setCardNumber(rs.getString("card_number"));
card.setUserId(rs.getInt("user_id"));
user.setIdCard(card);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return user;
}
三种方式对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| MyBatis | 灵活控制SQL,性能可控 | 需要写XML映射 | 复杂查询、SQL优化敏感 |
| JPA/Hibernate | 开发快,自动维护关联 | 学习曲线略高,延迟加载可能N+1 | 标准CRUD、快速开发 |
| 纯JDBC | 无框架依赖 | 代码重复多,难维护 | 极简单需求或框架限制 |
注意事项
- 数据库层面:一对一的本质是
外键 + 唯一约束(或主键即外键) - 性能考虑:
- MyBatis嵌套结果(JOIN一次)优于嵌套查询(多次查询)
- JPA建议使用
@EntityGraph或JOIN FETCH避免N+1
- 双向关联:避免JSON序列化循环,使用
@JsonIgnore或DTO
选择哪种方式取决于项目的技术栈和具体需求,大多数Java项目会用 MyBatis 或 JPA,很少再用纯JDBC。