Hallo,
I'am using Ibatis 2.3.0.677. Under certain circumstances properties of
the result class are not set, although contained in the result map. This
happens, when the only property of a result map is mapped to a result
map. The essence is, that
<resultMap id="resultIdOnly" class="Type2" >
<result property="type1" resultMap="type1.result"/>
</resultMap>
does not work, because the property type1 of the instance of Type2 was
not set and remains null. I will illustrate this with the following
example. I'am using 3 classes instead of 2 for motivation only.
Example:
Database
CREATE TABLE type1
(
id int NOT NULL,
attribute1 text NOT NULL,
CONSTRAINT pk_type1 PRIMARY KEY (id)
);
CREATE TABLE type2
(
type1id int NOT NULL,
attribute2 text NOT NULL,
CONSTRAINT pk_type2 PRIMARY KEY (type1id),
CONSTRAINT fk_type2_type1 FOREIGN KEY (type1id)
REFERENCES type1(id)
);
(1 to 1 relationship between type1 and type2)
CREATE TABLE type3
(
id int NOT NULL,
type2id int NULL,
CONSTRAINT pk_type3 PRIMARY KEY (id),
CONSTRAINT fk_type3_type2 FOREIGN KEY (type2id)
REFERENCES type2(type1id)
);
Java
class Type1
{
private int id;
private String attribute1;
public void setId(int id)
{
this.id = id;
}
public void getId()
{
return this.id;
}
public void setAttribute1(String attribute1)
{
this.attribute1 = attribute1;
}
public String getAttribute1()
{
return this.attribute1;
}
}
class Type2
{
private Type1 type1;
private String attribute2;
public void setType1(Type1 type1)
{
this.type1 = type1;
}
public void getType1()
{
return this.type1;
}
public void setAttribute2(String attribute2)
{
this.attribute2 = attribute2;
}
public String getAttribute2()
{
return this.attribute2;
}
}
class Type3
{
private int id;
private Type2 type2;
public void setId(int id)
{
this.id = id;
}
public void getId()
{
return this.id;
}
public void setType2(Type2 type2)
{
this.type2 = type2;
}
public void getType2()
{
return this.type2;
}
}
Imagine that an instance of Type2 is a complex object, that I don't want
to load twice. That's why I intend to let Ibatis create an instance of
Type3, containing an instance of Type2 containing only an instance of
Type1 which contains the property id only. This way I can identify the
already loaded instance of Type2.
XML
type3.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd" >
<sqlMap namespace="type3" >
<resultMap id="result" class="Type3" >
<result column="t3_id" jdbcType="INTEGER" property="id" />
<result property="type2" resultMap="type2.resultIdOnly" />
</resultMap>
<select id="select" resultMap="result" parameterClass="int">
SELECT
t3.id AS t3_id,
t3.type2id AS t1_id
WHERE t3.id = #value#
</select>
</sqlMap>
type2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd" >
<sqlMap namespace="type2" >
<!-- Not used in this example -->
<resultMap id="result" class="Type2" >
<result property="type1" resultMap="type1.result"/>
<result column="t2_attribute2" jdbcType="VARCHAR" property="attribute2"/>
</resultMap>
<!-- Does not work -->
<resultMap id="resultIdOnly" class="Type2" >
<result property="type1" resultMap="type1.result"/>
</resultMap>
</sqlMap>
type1.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd" >
<sqlMap namespace="type1" >
<!-- Not used in this example -->
<resultMap id="result" class="Type1" >
<result column="t1_id" jdbcType="INTEGER" property="id"/>
<result column="t1_attribute1" jdbcType="VARCHAR" property="attribute2"/>
</resultMap>
<resultMap id="resultIdOnly" class="Type1" >
<result column="t1_id" jdbcType="INTEGER" property="id" />
</resultMap>
</sqlMap>
DAO
class Type3DAOImpl
{
public Type3 select(int id)
{
return (Type3)
getSqlMapClientTemplate().queryForObject("type3.select", id);
}
}
The returned instance of Type3 does not contain the expected instance of
Type2. Feature or bug?
Workaround 1
If I don't want to change the data model or the application logic, I
have to let Ibatis set a second property on the instance of Type2, let's
say property attribute2:
type2.xml
<resultMap id="resultIdOnly" class="Type2" >
<result property="type1" resultMap="type1.result"/>
<result column="t2_attribute2" jdbcType="VARCHAR"
</resultMap>
type3.xml
<select id="select" resultMap="result" parameterClass="int">
SELECT
t3.id AS t3_id,
t3.type2id AS t1_id,
'' AS t2_attribute2
FROM type3 AS t3
WHERE t3.id = #value#
</select>
The last leads to false result, if the t3.type2id is NULL, because an
instance of Type2 is created in every case due to the empty string value
of attribut2. I can use proprietary SQL to avoid this.
Workaround 2
<select id="select" resultMap="result" parameterClass="int">
SELECT
t3.id AS t3_id,
t3.type2id AS t1_id,
CASE WHEN t3.type2id IS NULL THEN NULL ELSE '' END AS t2_attribute2
FROM type3 AS t3
WHERE t3.id = #value#
</select>
Another idea is to use a JOIN.
Workaround 3
<select id="select" resultMap="result" parameterClass="int">
SELECT
t3.id AS t3_id,
t3.type2id AS t1_id,
t2.attribute2 AS t2_attribute2
FROM type3 AS t3
LEFT JOIN type2 AS t2 ON t2.type1id = t3.type2id
WHERE t3.id = #value#
</select>
But what happens, when column type2.attribute2 contains NULL values?
Ibatis cannot distinct the case, where a matching record in type2 with
attribut2 is NULL exists respectively no matching record in type2 exists.
Is this behaviour known?
Does anybody know a solution?