Skip to content

Commit e163a91

Browse files
committed
mybatis#101: Keep track of pending creations that were set as property mappings
- We check for the existence of these (and build them if needed) before returning the final object - Fixes `testImmutableNestedObjects()`
1 parent a01b27e commit e163a91

File tree

8 files changed

+102
-16
lines changed

8 files changed

+102
-16
lines changed

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

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ public class DefaultResultSetHandler implements ResultSetHandler {
9191
private final ObjectFactory objectFactory;
9292
private final ReflectorFactory reflectorFactory;
9393

94+
// pending creations property tracker
95+
private final Map<Object, PendingRelation> pendingPccRelations = new HashMap<>();
96+
9497
// nested resultmaps
9598
private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
9699
private final Map<String, Object> ancestorObjects = new HashMap<>();
@@ -387,9 +390,14 @@ private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Ob
387390
ResultMapping parentMapping, ResultSet rs) throws SQLException {
388391
if (parentMapping != null) {
389392
linkToParents(rs, parentMapping, rowValue);
390-
} else {
391-
callResultHandler(resultHandler, resultContext, rowValue);
393+
return;
394+
}
395+
396+
if (pendingPccRelations.containsKey(rowValue)) {
397+
createPendingConstructorCreations(rowValue);
392398
}
399+
400+
callResultHandler(resultHandler, resultContext, rowValue);
393401
}
394402

395403
@SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object> */)
@@ -1265,6 +1273,36 @@ private boolean applyNestedPendingConstructorCreations(ResultSetWrapper rsw, Res
12651273
return foundValues;
12661274
}
12671275

1276+
private void createPendingConstructorCreations(Object rowValue) {
1277+
// handle possible pending creations within this object
1278+
// by now, the property mapping has been completely built, we can reconstruct it
1279+
final PendingRelation pendingRelation = pendingPccRelations.remove(rowValue);
1280+
final MetaObject metaObject = pendingRelation.metaObject;
1281+
final ResultMapping resultMapping = pendingRelation.propertyMapping;
1282+
1283+
// get the list to be built
1284+
Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1285+
if (collectionProperty != null) {
1286+
// we expect pending creations now
1287+
final Collection<Object> pendingCreations = (Collection<Object>) collectionProperty;
1288+
1289+
// remove the link to the old collection
1290+
metaObject.setValue(resultMapping.getProperty(), null);
1291+
1292+
// create new collection property
1293+
collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1294+
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1295+
1296+
// create the pending objects
1297+
for (Object pendingCreation : pendingCreations) {
1298+
if (pendingCreation instanceof PendingConstructorCreation) {
1299+
final PendingConstructorCreation pendingConstructorCreation = (PendingConstructorCreation) pendingCreation;
1300+
targetMetaObject.add(pendingConstructorCreation.create(objectFactory, false));
1301+
}
1302+
}
1303+
}
1304+
}
1305+
12681306
private void verifyPendingCreationPreconditions(ResultMapping parentMapping) {
12691307
if (parentMapping != null) {
12701308
throw new ExecutorException(
@@ -1481,6 +1519,17 @@ private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Obj
14811519
if (collectionProperty != null) {
14821520
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
14831521
targetMetaObject.add(rowValue);
1522+
1523+
// it is possible for pending creations to get set via property mappings,
1524+
// keep track of these, so we can rebuild them.
1525+
final Object originalObject = metaObject.getOriginalObject();
1526+
if (rowValue instanceof PendingConstructorCreation && !pendingPccRelations.containsKey(originalObject)) {
1527+
PendingRelation pendingRelation = new PendingRelation();
1528+
pendingRelation.propertyMapping = resultMapping;
1529+
pendingRelation.metaObject = metaObject;
1530+
1531+
pendingPccRelations.put(originalObject, pendingRelation);
1532+
}
14841533
} else {
14851534
metaObject.setValue(resultMapping.getProperty(), rowValue);
14861535
}

src/site/markdown/sqlmap-xml.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ User{username=Peter, roles=[Users, Maintainers, Approvers]}
759759
This functionality is still experimental, please report any issues you may find on the issue tracker.
760760

761761
It is important to note that mixed mappings have limited support, i.e. property mappings combined with nested constructor mappings are likely to fail.
762-
When using this functionality, it is preferable for the entire mapping hieracrhy to use immutable constructor mappings.
762+
When using this functionality, it is preferable for the entire mapping hierarchy to use immutable constructor mappings.
763763

764764
#### association
765765

src/test/java/org/apache/ibatis/submitted/collection_in_constructor/CollectionInConstructorTest.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,7 @@ void testCollectionArgWithTypeHandler() {
165165
}
166166

167167
@Test
168-
@Disabled
169168
void testImmutableNestedObjects() {
170-
/*
171-
* This resultmap contains mixed property and constructor mappings, the logic assumes the entire chain will be
172-
* immutable when we have mixed mappings, we don't know when to create the final object, as property mappings could
173-
* still be modified at any point in time This brings us to a design question, is this really what we want from this
174-
* functionality, as the point was to create immutable objects in my opinion, supporting this defeats the purpose;
175-
* for example propery mapping -> immutable collection -> immutable object -> mapped by property mapping. we cannot
176-
* build the final object if it can still be modified; i.e, the signal to build the immutable object is lost. the
177-
* code in this pr assumes and relies on the base object also being immutable, i.e: constructor mapping -> immutable
178-
* collection -> immutable object -> mapped by constructor mapping. Imo, there is only one option here, it should be
179-
* added in the documentation; as doing (and supporting this, will be extremely complex)
180-
*/
181169
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
182170
Mapper mapper = sqlSession.getMapper(Mapper.class);
183171
Container container = mapper.getAContainer();

src/test/java/org/apache/ibatis/submitted/collection_injection/CollectionInjectionTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.ibatis.session.SqlSession;
2525
import org.apache.ibatis.session.SqlSessionFactory;
2626
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
27+
import org.apache.ibatis.submitted.collection_injection.immutable.HousePortfolio;
2728
import org.apache.ibatis.submitted.collection_injection.immutable.ImmutableDefect;
2829
import org.apache.ibatis.submitted.collection_injection.immutable.ImmutableFurniture;
2930
import org.apache.ibatis.submitted.collection_injection.immutable.ImmutableHouse;
@@ -117,4 +118,13 @@ private static void assertResult(StringBuilder builder) {
117118

118119
assertThat(builder.toString()).isNotEmpty().isEqualTo(expected);
119120
}
121+
122+
@Test
123+
void getHousePortfolio() {
124+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
125+
final ImmutableHouseMapper mapper = sqlSession.getMapper(ImmutableHouseMapper.class);
126+
final HousePortfolio portfolio = mapper.getHousePortfolio(1);
127+
Assertions.assertNotNull(portfolio);
128+
}
129+
}
120130
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.apache.ibatis.submitted.collection_injection.immutable;
2+
3+
import java.util.List;
4+
5+
public class HousePortfolio {
6+
7+
private int portfolioId;
8+
private List<ImmutableHouse> houses;
9+
10+
public int getPortfolioId() {
11+
return portfolioId;
12+
}
13+
14+
public void setPortfolioId(int portfolioId) {
15+
this.portfolioId = portfolioId;
16+
}
17+
18+
public List<ImmutableHouse> getHouses() {
19+
return houses;
20+
}
21+
22+
public void setHouses(List<ImmutableHouse> houses) {
23+
this.houses = houses;
24+
}
25+
}

src/test/java/org/apache/ibatis/submitted/collection_injection/immutable/ImmutableHouseMapper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ public interface ImmutableHouseMapper {
2323

2424
ImmutableHouse getHouse(int it);
2525

26+
HousePortfolio getHousePortfolio(int id);
2627
}

src/test/resources/org/apache/ibatis/submitted/collection_injection/immutable/ImmutableHouseMapper.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@
6363
</resultMap>
6464

6565
<sql id="housesSelect">
66-
select h.*
66+
select
67+
68+
1 as portfolioId
69+
70+
, h.*
6771

6872
, r.id as room_id
6973
, r.name as room_name
@@ -92,4 +96,12 @@
9296
where h.id = #{id}
9397
</select>
9498

99+
<resultMap id="housePortfolioMap" type="HousePortfolio">
100+
<id property="portfolioId" javaType="_int" column="portfolioId"/>
101+
<collection property="houses" resultMap="houseMap"/>
102+
</resultMap>
103+
104+
<select id="getHousePortfolio" resultMap="housePortfolioMap" resultOrdered="true">
105+
<include refid="housesSelect"/>
106+
</select>
95107
</mapper>

src/test/resources/org/apache/ibatis/submitted/collection_injection/mybatis_config.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<typeAlias type="org.apache.ibatis.submitted.collection_injection.immutable.ImmutableRoomDetail" alias="ImmutableRoomDetail"/>
3535
<typeAlias type="org.apache.ibatis.submitted.collection_injection.immutable.ImmutableFurniture" alias="ImmutableFurniture"/>
3636
<typeAlias type="org.apache.ibatis.submitted.collection_injection.immutable.ImmutableDefect" alias="ImmutableDefect"/>
37+
<typeAlias type="org.apache.ibatis.submitted.collection_injection.immutable.HousePortfolio" alias="HousePortfolio"/>
3738
</typeAliases>
3839

3940
<environments default="development">

0 commit comments

Comments
 (0)