Skip to content

Commit b789646

Browse files
committed
Fixes #307. Create an instance when all the values for an object are
null but there is at least one defered value.
1 parent f61151b commit b789646

File tree

9 files changed

+367
-13
lines changed

9 files changed

+367
-13
lines changed

src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
*/
6262
public class DefaultResultSetHandler implements ResultSetHandler {
6363

64-
private static final Object NO_VALUE = new Object();
64+
private static final Object DEFERED = new Object();
6565

6666
private final Executor executor;
6767
private final Configuration configuration;
@@ -368,18 +368,24 @@ private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap,
368368
boolean foundValues = false;
369369
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
370370
for (ResultMapping propertyMapping : propertyMappings) {
371-
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
371+
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
372+
if (propertyMapping.getNestedResultMapId() != null) {
373+
// the user added a column attribute to a nested result map, ignore it
374+
column = null;
375+
}
372376
if (propertyMapping.isCompositeResult()
373377
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
374378
|| propertyMapping.getResultSet() != null) {
375379
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
376380
// issue #541 make property optional
377381
final String property = propertyMapping.getProperty();
378382
// issue #377, call setter on nulls
379-
if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) {
380-
if (value != null || !metaObject.getSetterType(property).isPrimitive()) {
381-
metaObject.setValue(property, value);
382-
}
383+
if (value != DEFERED
384+
&& property != null
385+
&& (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()))) {
386+
metaObject.setValue(property, value);
387+
}
388+
if (value != null || value == DEFERED) {
383389
foundValues = true;
384390
}
385391
}
@@ -392,11 +398,8 @@ private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject
392398
if (propertyMapping.getNestedQueryId() != null) {
393399
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
394400
} else if (propertyMapping.getResultSet() != null) {
395-
addPendingChildRelation(rs, metaResultObject, propertyMapping);
396-
return NO_VALUE;
397-
} else if (propertyMapping.getNestedResultMapId() != null) {
398-
// the user added a column attribute to a nested result map, ignore it
399-
return NO_VALUE;
401+
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
402+
return DEFERED;
400403
} else {
401404
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
402405
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
@@ -648,17 +651,19 @@ private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObj
648651
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
649652
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
650653
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
651-
Object value = NO_VALUE;
654+
Object value = null;
652655
if (nestedQueryParameterObject != null) {
653656
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
654657
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
655658
final Class<?> targetType = propertyMapping.getJavaType();
656659
if (executor.isCached(nestedQuery, key)) {
657660
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
661+
value = DEFERED;
658662
} else {
659663
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
660664
if (propertyMapping.isLazy()) {
661665
lazyLoader.addLoader(property, metaResultObject, resultLoader);
666+
value = DEFERED;
662667
} else {
663668
value = resultLoader.loadResult();
664669
}

src/test/java/org/apache/ibatis/submitted/call_setters_on_nulls/CallSettersOnNullsTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,12 @@ public void shouldCallNullOnMapForSingleColumnWithResultMap() {
107107
try {
108108
Mapper mapper = sqlSession.getMapper(Mapper.class);
109109
List<Map<String, Object>> oneColumns = mapper.getNameOnlyMapped();
110-
Assert.assertNotNull(oneColumns.get(1));
110+
// Assert.assertNotNull(oneColumns.get(1));
111+
// TEST changed after fix for #307
112+
// When callSetterOnNull is true, setters are called with null values
113+
// but if all the values for an object are null
114+
// the object itself should be null (same as default behaviour)
115+
Assert.assertNull(oneColumns.get(1));
111116
} finally {
112117
sqlSession.close();
113118
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.apache.ibatis.submitted.nestedresulthandler_multiple_association;
2+
3+
public class Binome<T, U> {
4+
private T one;
5+
private U two;
6+
7+
public Binome() {
8+
}
9+
10+
public Binome(final T one, final U two) {
11+
this.one = one;
12+
this.two = two;
13+
}
14+
15+
public T getOne() {
16+
return one;
17+
}
18+
19+
public void setOne(T one) {
20+
this.one = one;
21+
}
22+
23+
public U getTwo() {
24+
return two;
25+
}
26+
27+
public void setTwo(U two) {
28+
this.two = two;
29+
}
30+
31+
@Override
32+
public int hashCode() {
33+
return (one != null ? one.hashCode() : 0) + (two != null ? two.hashCode() : 0);
34+
}
35+
36+
@Override
37+
public boolean equals(final Object obj) {
38+
if (obj instanceof Binome<?, ?>) {
39+
Binome<?, ?> bin = (Binome<?, ?>) obj;
40+
return one != null && one.equals(bin.getOne()) && two != null && two.equals(bin.getTwo());
41+
}
42+
return super.equals(obj);
43+
}
44+
45+
@Override
46+
public String toString() {
47+
return "Binome [one=" + one + ", two=" + two + "]";
48+
}
49+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.apache.ibatis.submitted.nestedresulthandler_multiple_association;
2+
3+
public class ChildBean {
4+
private Integer id;
5+
private String value;
6+
7+
public Integer getId() {
8+
return id;
9+
}
10+
11+
public void setId(Integer id) {
12+
this.id = id;
13+
}
14+
15+
public String getValue() {
16+
return value;
17+
}
18+
19+
public void setValue(String value) {
20+
this.value = value;
21+
}
22+
23+
@Override
24+
public String toString() {
25+
return "ChildBean [id=" + id + ", value=" + value + "]";
26+
}
27+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--
2+
-- Copyright 2009-2014 the original author or authors.
3+
--
4+
-- Licensed under the Apache License, Version 2.0 (the "License");
5+
-- you may not use this file except in compliance with the License.
6+
-- You may obtain a copy of the License at
7+
--
8+
-- http://www.apache.org/licenses/LICENSE-2.0
9+
--
10+
-- Unless required by applicable law or agreed to in writing, software
11+
-- distributed under the License is distributed on an "AS IS" BASIS,
12+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
-- See the License for the specific language governing permissions and
14+
-- limitations under the License.
15+
--
16+
17+
DROP TABLE parent if exists;
18+
DROP TABLE child if exists;
19+
create table parent(
20+
id integer,
21+
value varchar(20)
22+
);
23+
24+
create table child(
25+
id integer,
26+
value varchar(20)
27+
);
28+
29+
create table parent_child(
30+
idparent integer,
31+
idchild_from integer,
32+
idchild_to integer
33+
);
34+
35+
insert into parent (id, value) values (1, 'parent1');
36+
insert into parent (id, value) values (2, 'parent2');
37+
38+
insert into child (id, value) values (1, 'child1');
39+
insert into child (id, value) values (2, 'child2');
40+
insert into child (id, value) values (3, 'child3');
41+
insert into child (id, value) values (4, 'child4');
42+
43+
insert into parent_child (idparent, idchild_from, idchild_to) values (1, 1, 2);
44+
insert into parent_child (idparent, idchild_from, idchild_to) values (2, 2, 3);
45+
insert into parent_child (idparent, idchild_from, idchild_to) values (2, 1, 2);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2009-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.apache.ibatis.submitted.nestedresulthandler_multiple_association;
17+
18+
import java.io.Reader;
19+
import java.sql.Connection;
20+
import java.util.List;
21+
22+
import org.apache.ibatis.io.Resources;
23+
import org.apache.ibatis.jdbc.ScriptRunner;
24+
import org.apache.ibatis.session.SqlSession;
25+
import org.apache.ibatis.session.SqlSessionFactory;
26+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
27+
import org.junit.Assert;
28+
import org.junit.BeforeClass;
29+
import org.junit.Test;
30+
31+
public class NestedResultHandlerMultipleAssociationTest {
32+
33+
private static SqlSessionFactory sqlSessionFactory;
34+
35+
@BeforeClass
36+
public static void setUp() throws Exception {
37+
// create an SqlSessionFactory
38+
Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/nestedresulthandler_multiple_association/mybatis-config.xml");
39+
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
40+
reader.close();
41+
42+
// populate in-memory database
43+
SqlSession session = sqlSessionFactory.openSession();
44+
Connection conn = session.getConnection();
45+
reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/nestedresulthandler_multiple_association/CreateDB.sql");
46+
ScriptRunner runner = new ScriptRunner(conn);
47+
runner.setLogWriter(null);
48+
runner.runScript(reader);
49+
reader.close();
50+
session.close();
51+
}
52+
53+
@Test
54+
public void failure() throws Exception {
55+
SqlSession sqlSession = sqlSessionFactory.openSession();
56+
57+
// Parents have child going from somewhere to somewhere, they are stored in a Binome object
58+
// In this test we have 2 parents:
59+
// Parent1 is going from Child1 to Child2
60+
// Parent2 is going from Child2 to Child3 and from Child1 to Child2
61+
// You'll see a NULL entry in the list instead of the Binome Child1/Child2
62+
List<ParentBean> list = sqlSession.selectList("selectParentBeans");
63+
for (ParentBean pb : list) {
64+
for (Binome<ChildBean, ChildBean> childs : pb.getChilds()) {
65+
Assert.assertNotNull(childs);
66+
Assert.assertNotNull(childs.getOne());
67+
Assert.assertNotNull(childs.getTwo());
68+
}
69+
}
70+
71+
sqlSession.close();
72+
}
73+
74+
@Test
75+
public void success() throws Exception {
76+
SqlSession sqlSession = sqlSessionFactory.openSession();
77+
78+
ParentBean parent = sqlSession.selectOne("selectParentBeanById", 2);
79+
80+
// If you only select the Parent2 it works
81+
for (Binome<ChildBean, ChildBean> childs : parent.getChilds()) {
82+
Assert.assertNotNull(childs);
83+
Assert.assertNotNull(childs.getOne());
84+
Assert.assertNotNull(childs.getTwo());
85+
}
86+
sqlSession.close();
87+
}
88+
89+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.apache.ibatis.submitted.nestedresulthandler_multiple_association;
2+
3+
import java.util.List;
4+
5+
public class ParentBean {
6+
private Integer id;
7+
private String value;
8+
private List<Binome<ChildBean, ChildBean>> childs;
9+
10+
public Integer getId() {
11+
return id;
12+
}
13+
14+
public void setId(Integer id) {
15+
this.id = id;
16+
}
17+
18+
public String getValue() {
19+
return value;
20+
}
21+
22+
public void setValue(String value) {
23+
this.value = value;
24+
}
25+
26+
public List<Binome<ChildBean, ChildBean>> getChilds() {
27+
return childs;
28+
}
29+
30+
public void setChilds(List<Binome<ChildBean, ChildBean>> childs) {
31+
this.childs = childs;
32+
}
33+
34+
@Override
35+
public String toString() {
36+
StringBuilder sb = new StringBuilder("ParentBean [id=" + id + ", value=" + value + "]\nChilds:\n");
37+
for (Binome<ChildBean, ChildBean> binome : childs) {
38+
sb.append("\tChild : ").append(binome).append('\n');
39+
}
40+
return sb.toString();
41+
}
42+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright 2009-2014 the original author or authors.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
18+
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
19+
<mapper namespace="mapper">
20+
<resultMap id="ParentBeanResultMap" type="org.apache.ibatis.submitted.nestedresulthandler_multiple_association.ParentBean">
21+
<id property="id" column="id"/>
22+
<result property="value" column="value"/>
23+
<collection property="childs" select="selectChildsBinomes" column="id" />
24+
</resultMap>
25+
26+
<resultMap id="ChildsBinomesResultMap" type="org.apache.ibatis.submitted.nestedresulthandler_multiple_association.Binome">
27+
<association property="one" select="selectChildBeanById" column="idchild_from" javaType="org.apache.ibatis.submitted.nestedresulthandler_multiple_association.ChildBean" />
28+
<association property="two" select="selectChildBeanById" column="idchild_to" javaType="org.apache.ibatis.submitted.nestedresulthandler_multiple_association.ChildBean" />
29+
</resultMap>
30+
31+
<resultMap id="ChildBeanResultMap" type="org.apache.ibatis.submitted.nestedresulthandler_multiple_association.ChildBean">
32+
<id property="id" column="id"/>
33+
<result property="value" column="value"/>
34+
</resultMap>
35+
36+
<select id="selectParentBeans" resultMap="ParentBeanResultMap" useCache="false">
37+
select * from parent
38+
</select>
39+
40+
<select id="selectParentBeanById" resultMap="ParentBeanResultMap" useCache="false">
41+
select * from parent where id = #{value}
42+
</select>
43+
44+
<select id="selectChildBeanById" parameterType="java.lang.Integer" resultMap="ChildBeanResultMap" useCache="false">
45+
select * from child where id = #{value}
46+
</select>
47+
48+
<select id="selectChildsBinomes" parameterType="java.lang.Integer" resultMap="ChildsBinomesResultMap" useCache="false">
49+
select * from parent_child where idparent = #{value}
50+
</select>
51+
52+
</mapper>

0 commit comments

Comments
 (0)