Implementing One-to-One Mappings in MyBatis

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.

Tags: MyBatis java sql ORM Database Mapping

Posted on Fri, 04 Sep 2026 16:29:48 +0000 by jminscoe