在采用领域驱动设计的代码中,实体是用来描述业务概念的重要载体,为了更好的表达对业务概念的描述,因此实体在设计上往往是一个是复杂的结构,但是,用于存储实体数据的数据库都是基于行、列的关系表结构,因此实体结构和关系表结构并不匹配。为解决此种不匹配的情况,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能力,对实现良好领域驱动设计实模型,提供了强有力支撑。