MyBatis复杂对象处理的两种方式示例代码文档

 更新时间:2025年12月12日 17:17:09   作者:蛐蛐蜉蝣耶  
本文档详细介绍了如何在MyBatis中处理复杂对象的两种方式:使用MyBatis-PlusTypeHandler处理嵌套对象(自动序列化为JSON存储)和使用关联对象映射处理多个字段,文中还涵盖了自定义Mapper方法、事务管理和关联数据操作的注意事项,感兴趣的朋友跟随小编一起看看吧

MyBatis复杂对象处理示例代码文档

概述

本文档详细介绍了如何在 MyBatis中处理复杂对象的两种方式:

  1. 使用 MyBatis-Plus TypeHandler 处理嵌套对象(自动序列化为 JSON 存储)
  2. 使用关联对象映射处理多个字段

数据库表结构

文件路径: db/schema.sql

-- 创建演示表结构
drop table demo_entity;
-- 主表,使用TypeHandler方式存储复杂对象
CREATE TABLE `demo_entity` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(100) NOT NULL COMMENT '名称',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `config_data` varchar(1000) DEFAULT NULL COMMENT '配置数据(JSON格式,由TypeHandler处理)',
  `street` varchar(200) DEFAULT NULL COMMENT '街道地址',
  `city` varchar(100) DEFAULT NULL COMMENT '城市',
  `zip_code` varchar(20) DEFAULT NULL COMMENT '邮政编码',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='演示实体表';
select * from demo_entity;

实体类设计

文件路径: DemoEntity.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName(value = "demo_entity",autoResultMap = true)
public class DemoEntity implements Serializable {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @TableField("name")
    private String name;
    @TableField("create_time")
    private LocalDateTime createTime;
    // 方式一:使用TypeHandler处理复杂对象
    @TableField(value = "config_data", typeHandler = FastjsonTypeHandler.class)
    private ConfigData configData;
    // 方式二:关联对象(通过其他字段映射)
    // @TableField(exist = false)是由于mybatis-plus的原因,
    // 当实体类中存在该字段时,会默认认为该字段是关联字段,会自动填充关联对象。
    @TableField(exist = false)
    private AddressInfo addressInfo;
    @Data
    public static class ConfigData implements Serializable {
        private String theme;
        private List<String> permissions;
        private Boolean enabled;
    }
    @Data
    public static class AddressInfo implements Serializable {
        private String street;
        private String city;
        private String zipCode;
    }
}

Mapper 接口

文件路径: DemoEntityMapper.java

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.credithc.risk.entity.DemoEntity;
import org.apache.ibatis.annotations.Param;
public interface DemoEntityMapper extends BaseMapper<DemoEntity> {
    /**
     * 自定义插入方法,处理关联对象
     */
    int insertWithAddress(@Param("entity") DemoEntity entity);
    /**
     * 自定义更新方法,处理关联对象
     */
    int updateWithAddress(@Param("entity") DemoEntity entity);
    /**
     * 根据ID查询包含关联对象的完整实体
     */
    DemoEntity selectWithAddressById(@Param("id") Long id);
}

Mapper XML 映射文件

文件路径: DemoEntityMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="DemoEntityMapper">
    <resultMap id="BaseResultMap" type="DemoEntity">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="createTime" column="create_time"/>
        <result property="configData" column="config_data"
                typeHandler="com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler"/>
        <!-- 关联对象映射 -->
        <association property="addressInfo" javaType="DemoEntity$AddressInfo">
            <result property="street" column="street"/>
            <result property="city" column="city"/>
            <result property="zipCode" column="zip_code"/>
        </association>
    </resultMap>
    <insert id="insertWithAddress" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO demo_entity (name, create_time, config_data,street,city, zip_code)
        VALUES (#{entity.name}, #{entity.createTime},
                #{entity.configData, typeHandler=com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler},
                #{entity.addressInfo.street},#{entity.addressInfo.city},#{entity.addressInfo.zipCode})
    </insert>
    <insert id="updateWithAddress">
        UPDATE demo_entity
        SET name = #{entity.name},
            create_time = #{entity.createTime},
            config_data = #{entity.configData, typeHandler=com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler},
            street = #{entity.addressInfo.street},
            city = #{entity.addressInfo.city},
            zip_code = #{entity.addressInfo.zipCode}
        WHERE id = #{entity.id}
    </insert>
    <select id="selectWithAddressById" resultMap="BaseResultMap">
        SELECT
            id,name,create_time,config_data,
            street,
            city,
            zip_code
        FROM demo_entity
        WHERE id = #{id}
    </select>
</mapper>

Service 层接口与实现

Service 接口

文件路径: DemoEntityService.java

import com.baomidou.mybatisplus.extension.service.IService;
import com.credithc.risk.entity.DemoEntity;
public interface DemoEntityService extends IService<DemoEntity> {
    /**
     * 保存Demo实体,同时处理两种不同类型的复杂字段
     */
    DemoEntity saveDemoEntity(DemoEntity entity);
    /**
     * 获取包含关联地址信息的完整实体
     */
    DemoEntity getDemoEntityWithAddress(Long id);
}

Service 实现类

文件路径: DemoEntityServiceImpl.java

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.credithc.risk.dao.DemoEntityMapper;
import com.credithc.risk.entity.DemoEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
@Service
public class DemoEntityServiceImpl extends ServiceImpl<DemoEntityMapper, DemoEntity> implements DemoEntityService {
    @Resource
    private DemoEntityMapper demoEntityMapper;
    @Override
    @Transactional
    public DemoEntity saveDemoEntity(DemoEntity entity) {
        // 保存主实体(TypeHandler方式自动处理configData)
        if (entity.getId() == null) {
            entity.setCreateTime(LocalDateTime.now());
            // demoEntityMapper.insert(entity);此方法为mybatis-plus提供的方法,会自动处理configData,但不会处理addressInfo
            // 下面的方法会自动处理configData和addressInfo
            demoEntityMapper.insertWithAddress(entity);
        } else {
            // demoEntityMapper.updateById(entity);此方法为mybatis-plus提供的方法,会自动处理configData,但不会处理addressInfo
            // 下面的方法会自动处理configData和addressInfo
            demoEntityMapper.updateWithAddress(entity);
        }
        return entity;
    }
    @Override
    public DemoEntity getDemoEntityWithAddress(Long id) {
        // demoEntityMapper.selectById(id);此方法为mybatis-plus提供的方法,会自动处理configData,但不会处理addressInfo
        // 下面的方法会自动处理configData和addressInfo
        return demoEntityMapper.selectWithAddressById(id);
    }
}

测试用例

文件路径: DemoEntityServiceTest.java

import com.credithc.risk.entity.DemoEntity;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class DemoEntityServiceTest {
    @Resource
    private DemoEntityService demoEntityService;
    @Test
    public void testSaveAndRetrieveEntityWithTypeHandler() {
        // 创建测试实体
        DemoEntity entity = new DemoEntity();
        entity.setName("Test Entity With TypeHandler");
        // 设置TypeHandler处理的复杂对象
        DemoEntity.ConfigData configData = new DemoEntity.ConfigData();
        configData.setTheme("dark");
        configData.setPermissions(Arrays.asList("read", "write", "delete"));
        configData.setEnabled(true);
        entity.setConfigData(configData);
        // 保存实体
        DemoEntity savedEntity = demoEntityService.saveDemoEntity(entity);
        // 验证保存成功
        assertNotNull(savedEntity.getId());
        assertEquals("Test Entity With TypeHandler", savedEntity.getName());
        assertNotNull(savedEntity.getConfigData());
        assertEquals("dark", savedEntity.getConfigData().getTheme());
        assertEquals(3, savedEntity.getConfigData().getPermissions().size());
        assertTrue(savedEntity.getConfigData().getEnabled());
        // 验证从数据库重新加载的数据
        DemoEntity reloadedEntity = demoEntityService.getById(savedEntity.getId());
        assertNotNull(reloadedEntity);
        assertEquals("Test Entity With TypeHandler", reloadedEntity.getName());
        assertNotNull(reloadedEntity.getConfigData());
        assertEquals("dark", reloadedEntity.getConfigData().getTheme());
        assertEquals(3, reloadedEntity.getConfigData().getPermissions().size());
        assertTrue(reloadedEntity.getConfigData().getEnabled());
    }
    @Test
    public void testSaveAndRetrieveEntityWithAssociation() {
        // 创建测试实体
        DemoEntity entity = new DemoEntity();
        entity.setName("Test Entity With Association");
        entity.setCreateTime(LocalDateTime.now());
        // 设置关联对象
        DemoEntity.AddressInfo addressInfo = new DemoEntity.AddressInfo();
        addressInfo.setStreet("123 Main St");
        addressInfo.setCity("Beijing");
        addressInfo.setZipCode("100000");
        entity.setAddressInfo(addressInfo);
        // 保存实体
        DemoEntity savedEntity = demoEntityService.saveDemoEntity(entity);
        // 验证保存成功
        assertNotNull(savedEntity.getId());
        assertEquals("Test Entity With Association", savedEntity.getName());
        assertNotNull(savedEntity.getAddressInfo());
        assertEquals("123 Main St", savedEntity.getAddressInfo().getStreet());
        assertEquals("Beijing", savedEntity.getAddressInfo().getCity());
        assertEquals("100000", savedEntity.getAddressInfo().getZipCode());
        // 验证从数据库重新加载的数据(包含关联对象)
        DemoEntity reloadedEntity = demoEntityService.getDemoEntityWithAddress(savedEntity.getId());
        assertNotNull(reloadedEntity);
        assertEquals("Test Entity With Association", reloadedEntity.getName());
        assertNotNull(reloadedEntity.getAddressInfo());
        assertEquals("123 Main St", reloadedEntity.getAddressInfo().getStreet());
        assertEquals("Beijing", reloadedEntity.getAddressInfo().getCity());
        assertEquals("100000", reloadedEntity.getAddressInfo().getZipCode());
    }
}

技术要点说明

1. TypeHandler 方式处理复杂对象

  • 在实体类中使用 @TableField 注解配合 typeHandler 参数指定处理器
  • MyBatis-Plus 提供了 FastjsonTypeHandler 来自动将复杂对象序列化为 JSON 字符串存储到数据库
  • 适用于单个复杂对象字段的处理

2. 关联对象映射方式

  • 使用 @TableField(exist = false) 标记非数据库字段
  • 在 XML 映射文件中使用 <association> 标签进行关联对象映射
  • 适用于需要将复杂对象的属性拆分存储到多个数据库字段的情况

3. 自定义 Mapper 方法

为了处理关联对象,我们自定义了以下方法:

  • insertWithAddress: 插入实体及关联对象数据
  • updateWithAddress: 更新实体及关联对象数据
  • selectWithAddressById: 查询实体及关联对象数据
  • 关联数据的操作切勿使用mybatis-plus默认方法可能会不生效

4. 事务管理

在 Service 实现类中使用了 @Transactional 注解确保数据一致性。

在MyBatis生态中,当处理数据库中的关联关系时,Association和Collection是两个非常重要的概念。它们能够帮助我们高效地获取和处理与主实体相关的其他实体信息。无论是一对一(Association)还是一对多(Collection)的关系,再加上MyBatis-Plus TypeHandler模式,可以根据具体业务需求选择合适的方式,正确地使用它们可以让我们的持久层代码更加清晰和高效。

到此这篇关于MyBatis复杂对象处理示例代码文档的文章就介绍到这了,更多相关MyBatis复杂对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解领域驱动设计之事件驱动与CQRS

    详解领域驱动设计之事件驱动与CQRS

    这篇文章分析了如何应用事件来分离软件核心复杂度。探究CQRS为什么广泛应用于DDD项目中,以及如何落地实现CQRS框架。当然我们也要警惕一些失败的教训,利弊分析以后再去抉择正确的应对之道
    2021-06-06
  • MybatisPlus lambdaQueryWrapper中常用方法的使用

    MybatisPlus lambdaQueryWrapper中常用方法的使用

    本文主要介绍了MybatisPlus lambdaQueryWrapper中常用方法的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Java中的泛型详解

    Java中的泛型详解

    这篇文章主要介绍了Java中的泛型详解,本文讲解了泛型类或接口、从泛型类派生子类、伪泛型、类型通配符、通配符的上限、通配符的下限、擦除和转换等内容,需要的朋友可以参考下
    2015-04-04
  • SpringBoot Java通过API的方式调用腾讯智能体(腾讯元宝)代码示例

    SpringBoot Java通过API的方式调用腾讯智能体(腾讯元宝)代码示例

    这篇文章主要介绍了SpringBoot Java通过API的方式调用腾讯智能体(腾讯元宝)的相关资料,详细说明参数获取及动态处理方法,提供结果字符串转数组技巧,需要的朋友可以参考下
    2025-06-06
  • java 根据前端返回的字段名进行查询数据

    java 根据前端返回的字段名进行查询数据

    本文介绍了如何在Java中使用SpringDataJPA实现动态查询功能,以便根据前端传递的字段名动态构建查询语句,通过创建实体类、Repository接口、构建动态查询、在Service层和Controller中使用动态查询,实现了前后端分离架构中的灵活查询需求
    2024-11-11
  • 第三方包jintellitype实现Java设置全局热键

    第三方包jintellitype实现Java设置全局热键

    本文主要介绍了,在java中使用第三方插件包jintellitype来实现全局热键,非常的简单,但是很实用,有需要的朋友可以参考下,欢迎一起来参与改进此项目
    2014-09-09
  • 详解JAVA使用Comparator接口实现自定义排序

    详解JAVA使用Comparator接口实现自定义排序

    这篇文章主要介绍了JAVA使用Comparator接口实现自定义排序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • spring boot中使用http请求的示例代码

    spring boot中使用http请求的示例代码

    本篇文章主要介绍了spring boot中 使用http请求的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • Java中进程与线程的区别

    Java中进程与线程的区别

    这篇文章主要介绍了Java进程与线程的区别,进程(Process)是操作系统分配资源的基本单位,线程(Thread)是操作系统能够进行运算调度的基本单位,下文更多两者区别。需要的小伙伴可以参考一下
    2022-05-05
  • Feign实现多文件上传,Open Feign多文件上传问题及解决

    Feign实现多文件上传,Open Feign多文件上传问题及解决

    这篇文章主要介绍了Feign实现多文件上传,Open Feign多文件上传问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11

最新评论