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-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st

解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题

《解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题》本文主要讲述了在使用MyBatis和MyBatis-Plus时遇到的绑定异常... 目录myBATis-plus-boot-starpythonter与mybatis-spring-b

Spring Boot 中整合 MyBatis-Plus详细步骤(最新推荐)

《SpringBoot中整合MyBatis-Plus详细步骤(最新推荐)》本文详细介绍了如何在SpringBoot项目中整合MyBatis-Plus,包括整合步骤、基本CRUD操作、分页查询、批... 目录一、整合步骤1. 创建 Spring Boot 项目2. 配置项目依赖3. 配置数据源4. 创建实体类

Mybatis拦截器如何实现数据权限过滤

《Mybatis拦截器如何实现数据权限过滤》本文介绍了MyBatis拦截器的使用,通过实现Interceptor接口对SQL进行处理,实现数据权限过滤功能,通过在本地线程变量中存储数据权限相关信息,并... 目录背景基础知识MyBATis 拦截器介绍代码实战总结背景现在的项目负责人去年年底离职,导致前期规

MyBatis框架实现一个简单的数据查询操作

《MyBatis框架实现一个简单的数据查询操作》本文介绍了MyBatis框架下进行数据查询操作的详细步骤,括创建实体类、编写SQL标签、配置Mapper、开启驼峰命名映射以及执行SQL语句等,感兴趣的... 基于在前面几章我们已经学习了对MyBATis进行环境配置,并利用SqlSessionFactory核

MyBatis延迟加载的处理方案

《MyBatis延迟加载的处理方案》MyBatis支持延迟加载(LazyLoading),允许在需要数据时才从数据库加载,而不是在查询结果第一次返回时就立即加载所有数据,延迟加载的核心思想是,将关联对... 目录MyBATis如何处理延迟加载?延迟加载的原理1. 开启延迟加载2. 延迟加载的配置2.1 使用

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

Spring+MyBatis+jeasyui 功能树列表

java代码@EnablePaging@RequestMapping(value = "/queryFunctionList.html")@ResponseBodypublic Map<String, Object> queryFunctionList() {String parentId = "";List<FunctionDisplay> tables = query(parent

Mybatis中的like查询

<if test="templateName != null and templateName != ''">AND template_name LIKE CONCAT('%',#{templateName,jdbcType=VARCHAR},'%')</if>

JavaWeb【day09】--(Mybatis)

1. Mybatis基础操作 学习完mybatis入门后,我们继续学习mybatis基础操作。 1.1 需求 需求说明: 根据资料中提供的《tlias智能学习辅助系统》页面原型及需求,完成员工管理的需求开发。 通过分析以上的页面原型和需求,我们确定了功能列表: 查询 根据主键ID查询 条件查询 新增 更新 删除 根据主键ID删除 根据主键ID批量删除