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复杂对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Mybatis是如何解析配置文件的

    详解Mybatis是如何解析配置文件的

    这篇文章主要介绍了详解Mybatis是如何解析配置文件的,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Mybatis逆向工程实现连接MySQL数据库

    Mybatis逆向工程实现连接MySQL数据库

    本文主要介绍了Mybatis逆向工程实现连接MySQL数据库,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • SpringBoot @Scope与@RefreshScope注解使用详解

    SpringBoot @Scope与@RefreshScope注解使用详解

    spring的bean管理中,每个bean都有对应的scope。在BeanDefinition中就已经指定scope,默认的RootBeanDefinition的scope是prototype类型,使用@ComponentScan扫描出的BeanDefinition会指定是singleton,最常使用的也是singleton
    2022-11-11
  • 给JavaBean赋默认值并且转Json字符串的实例

    给JavaBean赋默认值并且转Json字符串的实例

    这篇文章主要介绍了给JavaBean赋默认值并且转Json字符串的实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Hibernate环境搭建与配置方法(Hello world配置文件版)

    Hibernate环境搭建与配置方法(Hello world配置文件版)

    这篇文章主要介绍了Hibernate环境搭建与配置方法,这里演示Hello world配置文件版的具体实现步骤与相关代码,需要的朋友可以参考下
    2016-03-03
  • Windows环境IDEA下Ranger1.2.0源码编译详细流程

    Windows环境IDEA下Ranger1.2.0源码编译详细流程

    本文给大家讲解Windows环境IDEA下Ranger1.2.0源码编译过程,通过配置Tomcat,发布 security-admin-web项目,编译启动tomcat即可完成,需要的朋友参考下
    2021-06-06
  • Spring中的@ExceptionHandler注解详解与应用示例

    Spring中的@ExceptionHandler注解详解与应用示例

    本文详细介绍了Spring框架中的@ExceptionHandler注解的用法,包括基本用法、全局异常处理、结合@ResponseStatus注解以及返回值类型,通过示例展示了如何使用@ExceptionHandler注解处理不同类型的异常,并提供定制化的异常处理响应,需要的朋友可以参考下
    2024-11-11
  • Java并发编程-volatile可见性详解

    Java并发编程-volatile可见性详解

    这篇文章主要介绍了Java并发编程-volatile可见性详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Java面向对象的三大特征

    Java面向对象的三大特征

    这篇文章主要给大家结合相关示例介绍了Java面向对象的三大特征:封装、继承、多态,非常的实用,有需要的小伙伴可以参考下。
    2015-06-06
  • Java程序中Doc文档注释示例教程

    Java程序中Doc文档注释示例教程

    这篇文章主要为大家介绍了Java程序中Doc文档注释的示例教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-10-10

最新评论