MyBatis MetaObject

2023-11-11 12:38
文章标签 mybatis metaobject

本文主要是介绍MyBatis MetaObject,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 众所周知,mybatis是一个半orm框架,所谓的半,是指Mybatis可以帮助开发者完成结果集到对象的单向映射,本文暂且不论mybatis这样做的原因是什么,和全ORM框架相比有哪些优势,本文着重和大家分析讨论R到O的过程中,Mybatis操作Bean属性、字段的原理。

想要知道mybatis怎么将一个result映射成object,并为其赋值,那么本文中给大家分享的MetaObject则是重中之重,就是它完成了对Bean的属性、字段的解析、实例化和赋值。只有搞清楚了MetaObject的原理和作用,才能掌握mybatisR到O整个过程的细节,才能在使用mybatis的过程中,无论是查错还是扩展,做到不慌不忙,胸有成竹。

MetaObject内部封装了如下几个对象:

  private final Object originalObject;private final ObjectWrapper objectWrapper;private final ObjectFactory objectFactory;private final ObjectWrapperFactory objectWrapperFactory;private final ReflectorFactory reflectorFactory;

originalObject:被操作对象。

objectWrapper:对象包装器,负责包装originalObject。

objectFactory:对象工厂,负责生产originalObject。

objectWrapperFactory:对外接口,开发者可指定该对象的实现,改变metaobject的默认包装器,来达到定制化包装器的目的。

reflectorFactory:reflecor工厂,负责生产、缓存Refector对象。

在MetaObject中,还有一个很重要的对象叫属性分词器,PropertyTokenizer,它负责解析属性表达式,MetaObject根据分词器解析的结果来判断该属性是简单属性还是嵌套属性,是一般类型还是数组类型。

俗话说,众人拾柴火焰高,正是由以上几个对象的通力合作,才能完成对复杂属性的解析。

接下来我把官方的测试用例一一拆解后重写,目的是为了简化Test Case Bean属性的构建,让大家可以更直观,更清楚的搞明白它的测试用例要测的点是什么,搞明白了它的测试点,也就掌握了它的用法,测试用例分解结束后我会在接下来的文章中用给源码注释的方法尽量注明每个属性,每个方法的用途,同时还会尝试为关键方法画流程图的方式帮助大家理解它核心方法的执行原理。由于作者也是位初学者,如有错误还请各位多多指正,谢谢。

Bean:

public class ShouldGetAndSetField {/*** 无getter/setter 叫字段* 有getter/setter 叫属性*  假设我们现在要操作一个私有字段* */private String  simpleField;}

Test Case:1

  @Test@DisplayName("get/set目标对象的私有字段值")void shouldGetAndSetField() {ShouldGetAndSetField field = new ShouldGetAndSetField();MetaObject meta = SystemMetaObject.forObject(field);meta.setValue("privateField", "foo");assertEquals("foo", meta.getValue("privateField"));}

Test Result:

2.操作Bean的嵌套字段

Bean:

public class ShouldGetAndSetNestedField {private String privateField;private ShouldGetAndSetNestedField  shouldGetAndSetNestedField;}

Test Case:

 @Test@DisplayName("get/set目标对象的嵌套私有字段值")void shouldGetAndSetNestedField() {ShouldGetAndSetNestedField field = new ShouldGetAndSetNestedField();MetaObject meta = SystemMetaObject.forObject(field);meta.setValue("shouldGetAndSetNestedField.privateField", "Nested");assertEquals("Nested", meta.getValue("shouldGetAndSetNestedField.privateField"));}

Test Result:

3.操作Bean的属性

Bean:

public class ShouldGetAndSetProperty {private String property;public String getProperty() {return property;}public void setProperty(String property) {this.property = property;}
}

Test Case:

  @Test@DisplayName("get/set目标对象的属性值")void shouldGetAndSetProperty() {ShouldGetAndSetProperty property = new ShouldGetAndSetProperty();MetaObject meta = SystemMetaObject.forObject(property);meta.setValue("property", "jarrah");assertEquals("jarrah", meta.getValue("property"));}

Test Result:

4.操作Bean的嵌套Bean属性值

Parent Bean:

public class ShouldGetAndSetNestedProperty {private NestedProperty nestedProperty;
}

Child Bean:

public class NestedProperty {private String property;public String getProperty() {return property;}public void setProperty(String property) {this.property = property;}
}

Test Case:

@Test@DisplayName("get/set目标对象的嵌套属性值")void shouldGetAndSetNestedProperty() {ShouldGetAndSetNestedProperty property = new ShouldGetAndSetNestedProperty();MetaObject meta = SystemMetaObject.forObject(property);meta.setValue("nestedProperty.property", "jarrah");assertEquals("jarrah", meta.getValue("nestedProperty.property"));}

Test Result:

5.以map.key的方式设置/获取为map中的值

Bean:

public class ShouldGetAndSetMapPair {private Map<String,String> map=new HashMap<>();}

Test Case:

@Test@DisplayName("以map.key的方式设置/获取为map中的值")void shouldGetAndSetMapPair() {ShouldGetAndSetMapPair parir = new ShouldGetAndSetMapPair();MetaObject meta = SystemMetaObject.forObject(parir);meta.setValue("map.key", "jarrah");assertEquals("jarrah", meta.getValue("map.key"));}

Test Result:

6.用下标的方式访问map元素

Bean:

public class ShouldGetAndSetMapPairUsingArraySyntax {private Map<String,String> map=new HashMap<>();
}

Test Case:

@Test@DisplayName("用下标的方式访问map元素")void shouldGetAndSetMapPairUsingArraySyntax() {ShouldGetAndSetMapPairUsingArraySyntax index = new ShouldGetAndSetMapPairUsingArraySyntax();MetaObject meta = SystemMetaObject.forObject(index);meta.setValue("map[key]", "jarrah");assertEquals("jarrah", meta.getValue("map[key]"));}

Test Result:

7.用map.key的方式操作嵌套对象map的元素

Parent Bean:

public class ShouldGetAndSetNestedMapPair {private NestedMapPair  nestedMapPair;
}

Child Bean:

public class NestedMapPair {private Map<String,String> map;
}

Test Case:

 @Test@DisplayName("用map.key的方式操作嵌套对象map的元素")void shouldGetAndSetNestedMapPair() {ShouldGetAndSetNestedMapPair nested = new ShouldGetAndSetNestedMapPair();MetaObject meta = SystemMetaObject.forObject(nested);meta.setValue("nestedMapPair.map.key", "jarrah");assertEquals("jarrah", meta.getValue("nestedMapPair.map.key"));}

Test Result:

8.用下标的方式访问嵌套对象中map的元素

Parent Bean:

public class ShouldGetAndSetNestedMapPairUsingArraySyntax {private NestedMapPairUsingArraySyntax nestedMapPairUsingArraySyntax;
}

Child Bean:

public class NestedMapPairUsingArraySyntax {private Map<String,String> map;
}

Test Case:

失败原因分析:

NestedMapPairUsingArraySyntax.map没有初始化,导致BaseWrapper中,setCollectionValue方法的collection instance of Map 条件没有命中,转而走了 else 
  protected void setCollectionValue(PropertyTokenizer prop, Object collection, Object value) {if (collection instanceof Map) {((Map) collection).put(prop.getIndex(), value);} else {int i = Integer.parseInt(prop.getIndex());if (collection instanceof List) {((List) collection).set(i, value);

Integer.parseInt(”key“); 那肯定失败了,按以往经验,没有被实例化的对象字段/属性会被MetaObject自动初始化,但自动初始化的前提是必须是嵌套属性,关键代码:

 public void setValue(String name, Object value) {PropertyTokenizer prop = new PropertyTokenizer(name);if (prop.hasNext()) { //如果是嵌套属性,需要把父属性实例化为 MetaObjectMetaObject metaValue = metaObjectForProperty(prop.getIndexedName());if (metaValue == SystemMetaObject.NULL_META_OBJECT) {if (value == null) {// don't instantiate child path if value is null //如果父属性是null,说明没有被实例化,// 并且value也是null,则什么也不做return;} else {metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);//如果父属性为null,但value有值,//则实例化父属性}}metaValue.setValue(prop.getChildren(), value);//嵌套属性,实例化父属性后,为其子属性赋值} else {objectWrapper.set(prop, value);//简单属性,直接委托objectWrapper赋值}}

当prop.hasNext方法为true时,才有可能执行instantiatePropertyValue方法来实例化嵌套对象,但因为map[key]无法被解析成嵌套属性,所以prop.hasNext方法为false,即

跳过了instantiatePropertyValue方法

解决方案1:

实例化该map。

解决方案二:

使用map.key的方式访问

解决方案三:

扩展

public class NestedMapPairUsingArraySyntax {private Map<String,String> map=new HashMap<>();
}

执行测试:

注:使用map[key]的方式操作map元素时,应初始化map对象。

9.用数组下标的方式访问集合元素

Bean:

public class ShouldGetAndSetListItem {private List<String> itemList;}

Test Case:

  @Test@DisplayName("通过下标的方式操作数组元素")void shouldGetAndSetListItem() {ShouldGetAndSetListItem list = new ShouldGetAndSetListItem();MetaObject meta = SystemMetaObject.forObject(list);meta.setValue("itemList[0]", "jarrah");assertEquals("jarrah", meta.getValue("itemlist[0]"));}

Test Result:

失败,失败原因同map[key],

在Bean 里面初始化该list:

public class ShouldGetAndSetListItem {private List<String> itemList=new ArrayList<>();
}

重新执行测试:

又失败了,失败原因也出在 map[key]失败原因的关键代码处;

82行,空集合调用set方法会引发数组越界,看回官方的测试:

执行一下:

竟然通过了,打开RichType:

原来RichType在初始化的时候添加了个元素,那上面的操作是不是把 “bar”给替换掉了?改官方测试验证一下:

  @Testvoid shouldGetAndSetListItem() {RichType rich = new RichType();MetaObject meta = SystemMetaObject.forObject(rich);meta.setValue("richList[0]", "foo");assertEquals("foo", meta.getValue("richList[0]"));//这是我自己加的assertTrue(rich.getRichList().size()==1);assertEquals(rich.getRichList().get(0),"foo");}

执行:

确实是被替换掉了,因此可以得出结论,对于Array|List类型的属性,只能做替换操作,不能做添加操作,仔细一想,有index一定是替换操作,否则,index就失去了作用,因此这里也应该时作者有意为之,那么大家在用下标操作集合时,要注意这一点。

10.get/set父类字段值

Parent Bean:

public class Parent {private String address;
}

Child Bean:

public class Children extends Parent{}

Test Case:

  @Test@DisplayName("get/set父类字段值")void shouldGetAndSetParentField(){Children children=new Children();MetaObject meta=SystemMetaObject.forObject(children);meta.setValue("address","502");assertEquals(meta.getValue("address"),"502");}

Test Result:

 

这篇关于MyBatis MetaObject的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/390110

相关文章

MyBatis-Plus 中 nested() 与 and() 方法详解(最佳实践场景)

《MyBatis-Plus中nested()与and()方法详解(最佳实践场景)》在MyBatis-Plus的条件构造器中,nested()和and()都是用于构建复杂查询条件的关键方法,但... 目录MyBATis-Plus 中nested()与and()方法详解一、核心区别对比二、方法详解1.and()

MyBatis ResultMap 的基本用法示例详解

《MyBatisResultMap的基本用法示例详解》在MyBatis中,resultMap用于定义数据库查询结果到Java对象属性的映射关系,本文给大家介绍MyBatisResultMap的基本... 目录MyBATis 中的 resultMap1. resultMap 的基本语法2. 简单的 resul

Mybatis的分页实现方式

《Mybatis的分页实现方式》MyBatis的分页实现方式主要有以下几种,每种方式适用于不同的场景,且在性能、灵活性和代码侵入性上有所差异,对Mybatis的分页实现方式感兴趣的朋友一起看看吧... 目录​1. 原生 SQL 分页(物理分页)​​2. RowBounds 分页(逻辑分页)​​3. Page

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

Mybatis Plus Join使用方法示例详解

《MybatisPlusJoin使用方法示例详解》:本文主要介绍MybatisPlusJoin使用方法示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录1、pom文件2、yaml配置文件3、分页插件4、示例代码:5、测试代码6、和PageHelper结合6

MyBatis设计SQL返回布尔值(Boolean)的常见方法

《MyBatis设计SQL返回布尔值(Boolean)的常见方法》这篇文章主要为大家详细介绍了MyBatis设计SQL返回布尔值(Boolean)的几种常见方法,文中的示例代码讲解详细,感兴趣的小伙伴... 目录方案一:使用COUNT查询存在性(推荐)方案二:条件表达式直接返回布尔方案三:存在性检查(EXI

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可

Mybatis嵌套子查询动态SQL编写实践

《Mybatis嵌套子查询动态SQL编写实践》:本文主要介绍Mybatis嵌套子查询动态SQL编写方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、实体类1、主类2、子类二、Mapper三、XML四、详解总结前言MyBATis的xml文件编写动态SQL

Mybatis Plus JSqlParser解析sql语句及JSqlParser安装步骤

《MybatisPlusJSqlParser解析sql语句及JSqlParser安装步骤》JSqlParser是一个用于解析SQL语句的Java库,它可以将SQL语句解析为一个Java对象树,允许... 目录【一】jsqlParser 是什么【二】JSqlParser 的安装步骤【三】使用场景【1】sql语

mybatis的mapper对应的xml写法及配置详解

《mybatis的mapper对应的xml写法及配置详解》这篇文章给大家介绍mybatis的mapper对应的xml写法及配置详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录前置mapper 对应 XML 基础配置mapper 对应 xml 复杂配置Mapper 中的相