MyBatis version 3.4.6
test case.
public class MetaObjectTest {
class B {
private String name;
public B(String name) {
this.name = name;
}
public String getName(){
return this.name;
}
}
class A {
private List<B> result = new ArrayList<B>(1);
{
result.add(null);
}
public String get(){
return ((B) result.get(0)).getName();
}
}
@Test
public void testSetValue() {
String key = "result[0].name";
String value = "zz";
A a = new A();
MetaObject metaObject = SystemMetaObject.forObject(a);
metaObject.setValue(key, value);
Assert.assertEquals(a.get(), value);
}
}
error message:
java.lang.UnsupportedOperationException
at org.apache.ibatis.reflection.wrapper.CollectionWrapper.set(CollectionWrapper.java:43)
at org.apache.ibatis.reflection.MetaObject.setValue(MetaObject.java:140)
at org.apache.ibatis.reflection.MetaObject.setValue(MetaObject.java:138)
at com.MetaObjectTest.testSetValue(MetaObjectTest.java:42)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Comment From: harawata
What do you expect?
As result[0]
is null
, it is impossible to set its property, isn't it?
B b = null;
b.setName("zz"); // FAIL
Comment From: xiaoheng1
In my opinion, BeanWrapper.instantiatePropertyValue method can init the result[0]. but BeanWrapper.instantiatePropertyValue method does not take into account the actual type when obtaining the corresponding type of prop.getName. result[0] is a instance of B.
so i think we should modefy the BeanWrapper.instantiatePropertyValue method.
public MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory) {
MetaObject metaValue;
Class<?> type = getSetterType(prop.getName());
if (prop.getIndex() != null && Collection.class.isAssignableFrom(type)) {
Type returnType = getGenericGetterType(prop.getName());
if (returnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
if (actualTypeArguments != null && actualTypeArguments.length == 1) {
returnType = actualTypeArguments[0];
if (returnType instanceof Class) {
type = (Class<?>) returnType;
} else if (returnType instanceof ParameterizedType) {
type = (Class<?>) ((ParameterizedType) returnType).getRawType();
}
}
}
}
try {
Object newObject = objectFactory.create(type);
metaValue = MetaObject.forObject(newObject, metaObject.getObjectFactory(), metaObject.getObjectWrapperFactory(), metaObject.getReflectorFactory());
set(prop, newObject);
} catch (Exception e) {
throw new ReflectionException("Cannot set value of property '" + name + "' because '" + name + "' is null and cannot be instantiated on instance of " + type.getName() + ". Cause:" + e.toString(), e);
}
return metaValue;
}
Comment From: harawata
Hi @xiaoheng1 ,
Could you post a test that includes a mapper statement and an example result set? I couldn't think of any scenario that requires that code.
Comment From: xiaoheng1
Hi @harawata , This is just when I look at the source code, by analyzing the code, I find that it can't get the type of result [0]. I do n’t have an actual use scenario here.
Comment From: harawata
Thank you for the reply, @xiaoheng1 !
MetaObject is one of the internal classes that are designed to support actual MyBatis usages. If we improve internal classes to support unnecessary use cases, they will get more complex and become slower, so we should avoid it.
In case you want to use MetaObject as a utility class in your project, you are welcome to copy and modify it (Apache License 2 is fairly permissive).
Comment From: xiaoheng1
@harawata Thank you very much for your answers, I probably understand the design intent.
Comment From: harawata
@xiaoheng1 Thank you for trying to improve MyBatis!