MyBatis MetaObject

本文深入探讨MyBatis框架中MetaObject的作用及其内部机制,包括如何操作Bean的字段、属性及嵌套对象等,揭示MyBatis如何完成从数据库查询结果到Java对象的映射。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 众所周知,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()) { //如果是嵌套属性,需要把父属性实例化为 MetaObject
      MetaObject 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”给替换掉了?改官方测试验证一下:

  @Test
  void 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:

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值