In MyBatis, handling relationships between tables is a common requirement. A one-to-one relationship occurs when one record in a table is associated with exactly one record in another table. For example, every resident has exact one unique identification card. This guide demonstrates how to implement this using two different strategies: Nested Results and Nested Queries.
Database Schema Setup
To illustrate the relationship, we will create two tables: t_id_card and t_resident. The resident table contains a foreign key pointing to the identification card.
CREATE DATABASE IF NOT EXISTS `identity_db` DEFAULT CHARACTER SET utf8mb4;
USE `identity_db`;
-- Table for ID Cards
CREATE TABLE `t_id_card` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`card_number` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- Table for Residents
CREATE TABLE `t_resident` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`card_id` INT(11) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_card` FOREIGN KEY (`card_id`) REFERENCES `t_id_card` (`id`)
) ENGINE=InnoDB;
-- Initial Data
INSERT INTO `t_id_card` (`id`, `card_number`) VALUES (1, '110101199001011234');
INSERT INTO `t_id_card` (`id`, `card_number`) VALUES (2, '310101199505055678');
INSERT INTO `t_resident` (`id`, `name`, `card_id`) VALUES (1, 'Alice Smith', 1);
INSERT INTO `t_resident` (`id`, `name`, `card_id`) VALUES (2, 'Bob Johnson', 2);
Domain Model Classes
Define the Java entities that represent these tables. The Resident class includes a reference to an IDCard object.
public class IDCard {
private Integer id;
private String cardNumber;
// Getters and Setters
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getCardNumber() { return cardNumber; }
public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; }
@Override
public String toString() {
return "IDCard{id=" + id + ", cardNumber='" + cardNumber + "'}";
}
}
public class Resident {
private Integer id;
private String name;
private IDCard idCard;
// Getters and Setters
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public IDCard getIdCard() { return idCard; }
public void setIdCard(IDCard idCard) { this.idCard = idCard; }
@Override
public String toString() {
return "Resident{id=" + id + ", name='" + name + "', idCard=" + idCard + "}";
}
}
Mapper Interface
Create an interface to define the data access methods.
public interface ResidentMapper {
// Method for Nested Query approach
Resident getResidentByIdSelect(int id);
// Method for Nested Result approach
Resident getResidentByIdJoin(int id);
}
MyBatis Mapping Configuration
There are two primary ways to map the IDCard into the Resident object using the <association> tag.
1. Nested Results (Join Query)
This approach uses a single SQL JOIN statement. It is efficient because it executes only one database call.
<!-- ResultMap for Join Query -->
<resultMap id="residentJoinMap" type="Resident">
<id property="id" column="rid"/>
<result property="name" column="rname"/>
<association property="idCard" javaType="IDCard">
<id property="id" column="cid"/>
<result property="cardNumber" column="card_num"/>
</association>
</resultMap>
<select id="getResidentByIdJoin" resultMap="residentJoinMap">
SELECT
r.id as rid, r.name as rname,
c.id as cid, c.card_number as card_num
FROM t_resident r
LEFT JOIN t_id_card c ON r.card_id = c.id
WHERE r.id = #{id}
</select>
2. Nested Query (Select Statement)
This approach triggers a second SQL statement to fetch the associated object. It is useful for supporting lazy loading.
<!-- Separate query for the IDCard -->
<select id="findIDCardById" resultType="IDCard">
SELECT id, card_number as cardNumber FROM t_id_card WHERE id = #{id}
</select>
<resultMap id="residentSelectMap" type="Resident">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 'column' is the foreign key passed to the findIDCardById query -->
<association property="idCard"
column="card_id"
select="findIDCardById" />
</resultMap>
<select id="getResidentByIdSelect" resultMap="residentSelectMap">
SELECT id, name, card_id FROM t_resident WHERE id = #{id}
</select>
Execution and Testing
To retrieve the data, initialize the SqlSession and call the mapper methods. The Nested Select approach will show two separate SQL eexcution logs, while the Nested Result approach will show only one join query.
try (SqlSession session = MyBatisUtils.getSqlSession()) {
ResidentMapper mapper = session.getMapper(ResidentMapper.class);
// Testing Join Query
Resident resJoin = mapper.getResidentByIdJoin(1);
System.out.println("Join Result: " + resJoin);
// Testing Nested Select Query
Resident resSelect = mapper.getResidentByIdSelect(1);
System.out.println("Select Result: " + resSelect);
}
When using Nested Queries, MyBatis provides the option to enable lazy loading in the global configuration (lazyLoadingEnabled), which means the second query for the IDCard will only be executed if the getIdCard() method is actually called in the Java code.