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映射器配置小结

《mybatis映射器配置小结》本文详解MyBatis映射器配置,重点讲解字段映射的三种解决方案(别名、自动驼峰映射、resultMap),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定... 目录select中字段的映射问题使用SQL语句中的别名功能使用mapUnderscoreToCame

mybatis-plus如何根据任意字段saveOrUpdateBatch

《mybatis-plus如何根据任意字段saveOrUpdateBatch》MyBatisPlussaveOrUpdateBatch默认按主键判断操作类型,若需按其他唯一字段(如agentId、pe... 目录使用场景方法源码方法改造首先在service层定义接口service层接口实现总结使用场景my

MyBatis ParameterHandler的具体使用

《MyBatisParameterHandler的具体使用》本文主要介绍了MyBatisParameterHandler的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录一、概述二、源码1 关键属性2.setParameters3.TypeHandler1.TypeHa

MyBatis-plus处理存储json数据过程

《MyBatis-plus处理存储json数据过程》文章介绍MyBatis-Plus3.4.21处理对象与集合的差异:对象可用内置Handler配合autoResultMap,集合需自定义处理器继承F... 目录1、如果是对象2、如果需要转换的是List集合总结对象和集合分两种情况处理,目前我用的MP的版本

MyBatis-Plus 与 Spring Boot 集成原理实战示例

《MyBatis-Plus与SpringBoot集成原理实战示例》MyBatis-Plus通过自动配置与核心组件集成SpringBoot实现零配置,提供分页、逻辑删除等插件化功能,增强MyBa... 目录 一、MyBATis-Plus 简介 二、集成方式(Spring Boot)1. 引入依赖 三、核心机制

MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)

《MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)》本文给大家介绍MyBatis的xml中字符串类型判空与非字符串类型判空处理方式,本文给大家介绍的非常详细,对大家的学习或... 目录完整 Hutool 写法版本对比优化为什么status变成Long?为什么 price 没事?怎

Mybatis-Plus 3.5.12 分页拦截器消失的问题及快速解决方法

《Mybatis-Plus3.5.12分页拦截器消失的问题及快速解决方法》作为Java开发者,我们都爱用Mybatis-Plus简化CRUD操作,尤其是它的分页功能,几行代码就能搞定复杂的分页查询... 目录一、问题场景:分页拦截器突然 “失踪”二、问题根源:依赖拆分惹的祸三、解决办法:添加扩展依赖四、分页

MyBatis流式查询两种实现方式

《MyBatis流式查询两种实现方式》本文详解MyBatis流式查询,通过ResultHandler和Cursor实现边读边处理,避免内存溢出,ResultHandler逐条回调,Cursor支持迭代... 目录MyBATis 流式查询详解:ResultHandler 与 Cursor1. 什么是流式查询?

mybatis用拦截器实现字段加解密全过程

《mybatis用拦截器实现字段加解密全过程》本文通过自定义注解和MyBatis拦截器实现敏感信息加密,处理Parameter和ResultSet,确保数据库存储安全且查询结果解密可用... 目录前言拦截器的使用总结前言根据公司业务需要,灵活对客户敏感信息进行加解密,这里采用myBATis拦截器进行简单实

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入