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

相关文章

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

将Mybatis升级为Mybatis-Plus的详细过程

《将Mybatis升级为Mybatis-Plus的详细过程》本文详细介绍了在若依管理系统(v3.8.8)中将MyBatis升级为MyBatis-Plus的过程,旨在提升开发效率,通过本文,开发者可实现... 目录说明流程增加依赖修改配置文件注释掉MyBATisConfig里面的Bean代码生成使用IDEA生

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Spring Boot结成MyBatis-Plus最全配置指南

《SpringBoot结成MyBatis-Plus最全配置指南》本文主要介绍了SpringBoot结成MyBatis-Plus最全配置指南,包括依赖引入、配置数据源、Mapper扫描、基本CRUD操... 目录前言详细操作一.创建项目并引入相关依赖二.配置数据源信息三.编写相关代码查zsRArly询数据库数

Spring Boot 整合 MyBatis 连接数据库及常见问题

《SpringBoot整合MyBatis连接数据库及常见问题》MyBatis是一个优秀的持久层框架,支持定制化SQL、存储过程以及高级映射,下面详细介绍如何在SpringBoot项目中整合My... 目录一、基本配置1. 添加依赖2. 配置数据库连接二、项目结构三、核心组件实现(示例)1. 实体类2. Ma

Mybatis从3.4.0版本到3.5.7版本的迭代方法实现

《Mybatis从3.4.0版本到3.5.7版本的迭代方法实现》本文主要介绍了Mybatis从3.4.0版本到3.5.7版本的迭代方法实现,包括主要的功能增强、不兼容的更改和修复的错误,具有一定的参考... 目录一、3.4.01、主要的功能增强2、selectCursor example3、不兼容的更改二、

mybatis-plus分页无效问题解决

《mybatis-plus分页无效问题解决》本文主要介绍了mybatis-plus分页无效问题解决,原因是配置分页插件的版本问题,旧版本和新版本的MyBatis-Plus需要不同的分页配置,感兴趣的可... 昨天在做一www.chinasem.cn个新项目使用myBATis-plus分页一直失败,后来经过多方

mybatis-plus 实现查询表名动态修改的示例代码

《mybatis-plus实现查询表名动态修改的示例代码》通过MyBatis-Plus实现表名的动态替换,根据配置或入参选择不同的表,本文主要介绍了mybatis-plus实现查询表名动态修改的示... 目录实现数据库初始化依赖包配置读取类设置 myBATis-plus 插件测试通过 mybatis-plu

MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析

《MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析》本文将详细讲解MyBatis-Plus中的lambdaUpdate用法,并提供丰富的案例来帮助读者更好地理解和应... 目录深入探索MyBATis-Plus中Service接口的lambdaUpdate用法及示例案例背景