领域驱动设计之实体使用Mybatis进行对象关系映射

原创
01/10 14:37
阅读数 26

在采用领域驱动设计的代码中,实体是用来描述业务概念的重要载体,为了更好的表达对业务概念的描述,因此实体在设计上往往是一个是复杂的结构,但是,用于存储实体数据的数据库都是基于行、列的关系表结构,因此实体结构和关系表结构并不匹配。为解决此种不匹配的情况,myBatis提供了强大、灵活的对象和关系表结构映射能力。本文通过几种示例介绍mybatis的对象关系映射能力。

1. 简单对象关系表映射,这种情况,对象结构和数据库表结构完全一致,也是最简单的映射

对象结构

@Data
public class Test1Object {
    private Long id;
    private String field1;
    private Integer field2;
    private Boolean field3;
    private Long field4;
    private Date field5;
    private int field6;
    private long field7;
    private boolean field8;
}

表结构

映射结构

<mapper namespace="Test1ObjectMapper">
    <resultMap id="Test1Object" type="cn.easylib.orm.objects.Test1Object">
        <result column="id" property="id"/>
        <result column="field_1" property="field1"/>
        <result column="field_2" property="field2"/>
        <result column="field_3" property="field3"/>
        <result column="field_4" property="field4"/>
        <result column="field_5" property="field5"/>
        <result column="field_6" property="field6"/>
        <result column="field_7" property="field7"/>
        <result column="field_8" property="field8"/>
    </resultMap>
    <select id="findById" resultMap="Test1Object">
        select * from test1_object where id=#{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into test1_object(field_1,
                                 field_2,
                                 field_3,
                                 field_4,
                                 field_5,
                                 field_6,
                                 field_7,
                                 field_8)
        values (#{field1},
                #{field2},
                #{field3},
                #{field4},
                #{field5},
                #{field6},
                #{field7},
                #{field8})
    </insert>
</mapper>

2. 通过实体够构造函数映射实体

对象结构

@Getter
@Setter(AccessLevel.PRIVATE)
public class Test2Object {

    public Test2Object(String field1,
                       Integer field2,
                       Boolean field3,
                       Long field4,
                       Date field5,
                       int field6,
                       long field7,
                       boolean field8) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
        this.field5 = field5;
        this.field6 = field6;
        this.field7 = field7;
        this.field8 = field8;
    }

    public Test2Object() {

    }

    private Long id;
    private String field1;
    private Integer field2;
    private Boolean field3;
    private Long field4;
    private Date field5;
    private int field6;
    private long field7;
    private boolean field8;
}

表结构

映射结构

<mapper namespace="Test2ObjectMapper">
    <resultMap id="Test2Object_1" type="cn.easylib.orm.objects.Test2Object">
        <constructor>
            <arg column="field_1" javaType="string"/>
            <arg column="field_2" javaType="int"/>
            <arg column="field_3" javaType="boolean"/>
            <arg column="field_4" javaType="long"/>
            <arg column="field_5" javaType="java.util.Date" />
            <arg column="field_6" javaType="_int"/>
            <arg column="field_7" javaType="_long"/>
            <arg column="field_8" javaType="_boolean"/>
        </constructor>
        <result column="id" property="id"/>
    </resultMap>
    <resultMap id="Test2Object_2" type="cn.easylib.orm.objects.Test2Object">
        <result column="id" property="id"/>
        <result column="field_1" property="field1"/>
        <result column="field_2" property="field2"/>
        <result column="field_3" property="field3"/>
        <result column="field_4" property="field4"/>
        <result column="field_5" property="field5"/>
        <result column="field_6" property="field6"/>
        <result column="field_7" property="field7"/>
        <result column="field_8" property="field8"/>
    </resultMap>
    <select id="findById_1" resultMap="Test2Object_1">
        select *
        from test2_object
        where id = #{id}
    </select>
    <select id="findById_2" resultMap="Test2Object_2">
        select *
        from test2_object
        where id = #{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into test2_object(field_1,
                                 field_2,
                                 field_3,
                                 field_4,
                                 field_5,
                                 field_6,
                                 field_7,
                                 field_8)
        values (#{field1},
                #{field2},
                #{field3},
                #{field4},
                #{field5},
                #{field6},
                #{field7},
                #{field8})
    </insert>
</mapper>

resultMap id="Test2Object_1" 为使用构造函数进行映射的方式,另外在普通result的映射中,mybatis支持私有set字段映射能力。

3. 复杂对象单表映射方式

对象结构

@Data
public class Test3Object {

    private Long id;
    private String field1;
    private long field7;
    private boolean field8;
    private Test3ObjectValue objectValue;
}
@Data
public class Test3ObjectValue {
    private Integer field2;
    private Boolean field3;
    private Long field4;
    private Date field5;
    private int field6;
}

Test3Object是由5个字段组成,其中objectValue字段为一个复杂类结构,test3Object对象映射到一个数据库表中

表结构

映射结构

<mapper namespace="Test3ObjectMapper">
    <resultMap id="Test3Object_1" type="cn.easylib.orm.objects.Test3Object">
        <result column="id" property="id"/>
        <result column="field_1" property="field1"/>
        <result column="field_7" property="field7"/>
        <result column="field_8" property="field8"/>
        <result column="field_2" property="objectValue.field2"/>
        <result column="field_3" property="objectValue.field3"/>
        <result column="field_4" property="objectValue.field4"/>
        <result column="field_5" property="objectValue.field5"/>
        <result column="field_6" property="objectValue.field6"/>
    </resultMap>

    <resultMap id="Test3Object_2" type="cn.easylib.orm.objects.Test3Object">
        <result column="id" property="id"/>
        <result column="field_1" property="field1"/>
        <result column="field_7" property="field7"/>
        <result column="field_8" property="field8"/>
        <association property="objectValue" resultMap="Test3ObjectMapper.Test3ObjectValue"/>
    </resultMap>

    <resultMap id="Test3ObjectValue" type="cn.easylib.orm.objects.Test3ObjectValue">
        <result column="field_2" property="field2"/>
        <result column="field_3" property="field3"/>
        <result column="field_4" property="field4"/>
        <result column="field_5" property="field5"/>
        <result column="field_6" property="field6"/>
    </resultMap>

    <select id="findById_1" resultMap="Test3Object_1">
        select *
        from test3_object
        where id = #{id}
    </select>
    <select id="findById_2" resultMap="Test3Object_2">
        select *
        from test2_object
        where id = #{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into test3_object(field_1,
                                 field_2,
                                 field_3,
                                 field_4,
                                 field_5,
                                 field_6,
                                 field_7,
                                 field_8)
        values (#{field1},
                #{objectValue.field2},
                #{objectValue.field3},
                #{objectValue.field4},
                #{objectValue.field5},
                #{objectValue.field6},
                #{field7},
                #{field8})
    </insert>
</mapper>

映射结构中展示了两种映射方式,resultMap id="Test3Object_1" 的方式为,使用result将字段采购点“.”导航的方式映射,另一种方式为resultMap id="Test3Object_2"的方式,采用关键字association方式,此种方式的好处在于,若有多处相同的映对象的映射,映射关系可以复用。

4. 复杂对象集合类型字段一对多两张表映射

对象结构

@Data
public class Test4Object {

    private Long id;
    private String field1;
    private long field7;
    private boolean field8;
    private List<Test4ObjectValue> objectValue;
}
@Data
public class Test4ObjectValue {
    private Integer field2;
    private Boolean field3;
    private Long field4;
    private Date field5;
    private int field6;
}

以上对象结构Test4Object由5表字段组成,其中objectValue为复杂对象的集合类型,该对象结构将映射到两张表中,其中Test4Object存储到test4_object表中,objectValue集合字段存储到test4_object_value表中。

表结构

test4_object表

test4_object_value表

在test4_object_value表中,test4_object_id字段表示的test4_object的主键ID,两张通过该建立引用关系。

映射关系

<mapper namespace="Test4ObjectMapper">
    <resultMap id="Test4Object_1" type="cn.easylib.orm.objects.Test4Object">
        <result column="id" property="id"/>
        <result column="field_1" property="field1"/>
        <result column="field_7" property="field7"/>
        <result column="field_8" property="field8"/>
        <association property="field8" select="Test4ObjectMapper.select_field8" column="id" fetchType="lazy"/>
        <collection property="objectValue"  select="Test4ObjectMapper.select_test4ObjectValue" column="id" fetchType="lazy"/>
    </resultMap>
    <resultMap id="Test4ObjectValue" type="cn.easylib.orm.objects.Test4ObjectValue">
        <result column="field_2" property="field2"/>
        <result column="field_3" property="field3"/>
        <result column="field_4" property="field4"/>
        <result column="field_5" property="field5"/>
        <result column="field_6" property="field6"/>
    </resultMap>


    <select id="select_field8" resultType="_boolean">
        select field_8
        from test4_object
        where id = #{id}
    </select>

    <select id="findById_1" resultMap="Test4Object_1">
        select id,field_1,field_7
        from test4_object
        where id = #{id}
    </select>
    <select id="select_test4ObjectValue" resultMap="Test4ObjectValue">
        select *
        from test4_object_value
        where test4_object_id = #{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into test4_object(field_1,
                                 field_7,
                                 field_8)
        values (#{field1},
                #{field7},
                #{field8})
    </insert>
    <insert id="insertObjectValue">
        insert into test4_object_value(test4_object_id,
                                       field_2,
                                       field_3,
                                       field_4,
                                       field_5,
                                       field_6)
        values (
                #{id},
                #{objectValue.field2},
                #{objectValue.field3},
                #{objectValue.field4},
                #{objectValue.field5},
                #{objectValue.field6}
               )
    </insert>
</mapper>

在映射关系中,resultMap里使用了collection来进行集合类型字段处理,另外,可以设置collection里fetchType=lazy,来实现延迟加载的效果。

在test4_object_value表中有test4_object_id字段来保持对test4_object的引用,但是在对象Test4Object中,已经描述了Test4Object和Test4ObjectValue的关系,因此在Test4ObjectValue并没有(也没有必要)Test4Object的Id字段。因此在进行insert插入数据时,可以采用以下方式的代码实现

  public void insert(Test4Object test4Object) {

        this.sqlSessionTemplate.insert("Test4ObjectMapper.insert", test4Object);

        test4Object.getObjectValue().forEach(t -> {

            HashMap<String, Object> longTest4ObjectValueHashMap = new HashMap<>();
            longTest4ObjectValueHashMap.put("id", test4Object.getId());
            longTest4ObjectValueHashMap.put("objectValue", t);

            this.sqlSessionTemplate.insert("Test4ObjectMapper.insertObjectValue", longTest4ObjectValueHashMap);

        });

    }

重点在HasnMap的使用上。

以上介绍了一些基本的地象关系映射,仅需要进行需要XML配置即可以实现,若要实现更复杂和灵活的映射能力,可以通过实现MyBatis提供的org.apache.ibatis.type.BaseTypeHandler 抽象类实现更为灵活的映射。

1. 枚举类字段映射

对象结构

@Data
public class Test5Object {

    private Long id;
    private String field2;
    private long field3;
    private boolean field4;
    private Test5ObjectEnum test5ObjectEnum;
}

public enum Test5ObjectEnum {
    Value1(1),
    Value2(2);
    Test5ObjectEnum(int value) {
        this.value = value;
    }
    @Getter
    private final int value;
    public static Test5ObjectEnum parseValue(int value) {
        return Arrays.stream(Test5ObjectEnum.values()).filter(s -> s.value == value).findFirst().orElse(null);
    }
}

在Test5Object对象结构中,test5ObjectEnum字段类型为枚举类型,在枚举类型中定义了 value字段为枚举值,因此在存储需要将该值进行存储

表结构

映射结构

<mapper namespace="Test5ObjectMapper">
    <resultMap id="Test5Object_1" type="cn.easylib.orm.objects.Test5Object">
        <result column="id" property="id"/>
        <result column="field_2" property="field2"/>
        <result column="field_3" property="field3"/>
        <result column="field_4" property="field4"/>
        <result column="test5_object_enum" property="test5ObjectEnum" typeHandler="cn.easylib.orm.repository.typehandler.TestObject5EnumTypeHandler"/>
    </resultMap>
    <select id="findById_1" resultMap="Test5Object_1">
        select *
        from test5_object
        where id = #{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into test5_object(field_2,
                                 field_3,
                                 field_4,test5_object_enum)
        values (#{field2},
                #{field3},
                #{field4},
                #{test5ObjectEnum,typeHandler=cn.easylib.orm.repository.typehandler.TestObject5EnumTypeHandler})
    </insert>
</mapper>

在映射结构中,通过对枚举字段指定typeHandler属性,来绑一个解析器TypeHandlder,用于来通该字段进行对象和存储之间的转换。

枚举TypeHandler实现

package cn.easylib.orm.repository.typehandler;

import cn.easylib.orm.objects.Test5ObjectEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestObject5EnumTypeHandler extends BaseTypeHandler<Test5ObjectEnum> {
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Test5ObjectEnum testObject5Enum, JdbcType jdbcType) throws SQLException {

        preparedStatement.setInt(i, testObject5Enum.getValue());

    }

    @Override
    public Test5ObjectEnum getNullableResult(ResultSet resultSet, String s) throws SQLException {

        int anInt = resultSet.getInt(s);
        return Test5ObjectEnum.parseValue(anInt);


    }

    @Override
    public Test5ObjectEnum getNullableResult(ResultSet resultSet, int i) throws SQLException {
        int anInt = resultSet.getInt(i);
        return Test5ObjectEnum.parseValue(anInt);
    }

    @Override
    public Test5ObjectEnum getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        int anInt = callableStatement.getInt(i);
        return Test5ObjectEnum.parseValue(anInt);
    }
}
以上为枚举TypeHandler的自定义转换实现,其中setNonNullParameter为将对象枚举字段类型转换成用于存储的数字,另外的几个getNullableResult用于将存储转换成对象的枚举类型。

2. 将复杂对象字映射成表中一个字段

对象结构

@Data
public class Test6Object {

    private Long id;
    private String field2;
    private long field3;
    private boolean field4;

    private Test6ObjectValue test6ObjectValue;
}

在对象结构中test6ObjectValue为复杂类型字段和之前的映射方式,本次将test6ObjectValue字段通过自定义TypeHandler映射到表的一个字段中,并以json的格式进行存储。

表结构 映射结构

<?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="Test6ObjectMapper">
    <resultMap id="Test6Object_1" type="cn.easylib.orm.objects.Test6Object">
        <result column="id" property="id"/>
        <result column="field_2" property="field2"/>
        <result column="field_3" property="field3"/>
        <result column="field_4" property="field4"/>
        <result column="test6_object_value" property="test6ObjectValue" typeHandler="cn.easylib.orm.repository.typehandler.TestObject6ValueTypeHandler"/>
    </resultMap>
    <select id="findById_1" resultMap="Test6Object_1">
        select *
        from test6_object
        where id = #{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into test6_object(field_2,
                                 field_3,
                                 field_4,test6_object_value)
        values (#{field2},
                #{field3},
                #{field4},
                #{test6ObjectValue,typeHandler=cn.easylib.orm.repository.typehandler.TestObject6ValueTypeHandler})
    </insert>
</mapper>

枚举TypeHandler实现

package cn.easylib.orm.repository.typehandler;

import cn.easylib.orm.objects.Test6ObjectValue;
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestObject6ValueTypeHandler extends BaseTypeHandler<Test6ObjectValue> {
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Test6ObjectValue test6ObjectValue, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i, JSON.toJSONString(test6ObjectValue));
    }
    @Override
    public Test6ObjectValue getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return JSON.parseObject(resultSet.getString(s), Test6ObjectValue.class);
    }
    @Override
    public Test6ObjectValue getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return JSON.parseObject(resultSet.getString(i), Test6ObjectValue.class);

    }
    @Override
    public Test6ObjectValue getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return JSON.parseObject(callableStatement.getString(i), Test6ObjectValue.class);
    }
}

以上介绍几种mybatis提供对象和关系表之间的映射能力,BaseTypeHandler的自定义解析能力,还可以实现更为复杂映射,例如,地字段进行加密、解密、压缩,拆分等。myBatis提供此种能力,使实体结构和关系表结构构达到充分的解耦,实体结构能够更好地描述业务,而不受存储结构的影响,而且存储结构也可以实现的更加简单和灵活,总之,Mybatis提供的ORM能力,对实现良好领域驱动设计实模型,提供了强有力支撑。

DDD开发框架 https://gitee.com/lixiaojing/easy-domain

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部