You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user-java@ibatis.apache.org by Ingmar Lötzsch <il...@asci-systemhaus.de> on 2008/10/23 11:10:07 UTC

execution of nested result maps depends on number of mapped properties?

Hello,

I found a behaviour in iBATIS, what I don't unterstand. Dependend on the
list of properties selected in a statement joining 3 tables the
execution of a nested result map results in an instantiation or not.

iBATIS: 2.3.4.726
Spring: 2.5.2

I have 3 tables (I omit the unnecessary properties):

--- tables ---

CREATE TABLE vertragszeitart
(
	id int NOT NULL,
	PRIMARY KEY (id)
);

CREATE TABLE dienstart
(
	id int NOT NULL,
	vertragszeitartid int NOT NULL,
	PRIMARY KEY (id),
	FOREIGN KEY (vertragszeitartid) REFERENCES vertragszeitart (id)
);

CREATE TABLE artikel
(
	id int NOT NULL,
	nummer text NOT NULL,
	dienstartid int,
	PRIMARY KEY (id),
	FOREIGN KEY (dienstartid) REFERENCES dienstart (id)
);

INSERT INTO vertragszeitart (id) VALUES (1);
INSERT INTO dienstart(id, vertragszeitartid) VALUES (1, 1);
INSERT INTO artikel (id, nummer, dienstartid) VALUES (1, 'Art. 1', 1);

--- domain classes ---

class Artikel:

package test;

public class Artikel
{
	private int id;
	private String nummer;
	private Dienstart dienstart;
	public int getId()
	{
		return this.id;
	}
	public void setId(int id)
	{
		this.id = id;
	}
	public String getNummer()
	{
		return this.nummer;
	}
	public void setNummer(String nummer)
	{
		this.nummer = nummer;
	}
	public Dienstart getDienstart()
	{
		return this.dienstart;
	}
	public void setDienstart(Dienstart dienstart)
	{
		this.dienstart = dienstart;
	}
}

class Dienstart:

package test;

public class Dienstart
{
	private int id;
	private Vertragszeitart vertragszeitart;
	public int getId()
	{
		return this.id;
	}
	public void setId(int id)
	{
		this.id = id;
	}
	public Vertragszeitart getVertragszeitart()
	{
		return this.vertragszeitart;
	}
	public void setVertragszeitart(Vertragszeitart vertragszeitart)
	{
		this.vertragszeitart = vertragszeitart;
	}
}
	
class Vertragszeitart:

package test;

public class Vertragszeitart
{
	private int id;
	public int getId()
	{
		return this.id;
	}
	public void setId(int id)
	{
		this.id = id;
	}
}

Now I want to load the artikel with ID 1. 1st approach is using the
select-Element with id "selectTest1":

--- test.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="test" >

<resultMap id="resultVertragszeitart" class="test.Vertragszeitart" >
  <result column="da_vertragszeitartid" property="id" jdbcType="INTEGER" />
</resultMap>
	
<resultMap id="resultDienstart1" class="test.Dienstart" >
  <result column="da_id" jdbcType="INTEGER" property="id" />
  <result property="vertragszeitart"
resultMap="test.resultVertragszeitart"/>
</resultMap>
	
<resultMap id="resultArtikel1" class="test.Artikel" >
  <result column="ar_id" property="id" jdbcType="INTEGER" />
  <result property="dienstart" resultMap="test.resultDienstart1"/>
</resultMap>

<select id="selectTest1" parameterClass="int" resultMap="resultArtikel1" >
	SELECT
		ar.id AS ar_id,
		da.id AS da_id,
		da.vertragszeitartid AS da_vertragszeitartid
	FROM artikel AS ar
	INNER JOIN dienstart AS da ON da.id = ar.dienstartid
	WHERE ar.id = #value#
</select>
</sqlMap>

--- DAO ---

class IbatisTestDAO:

package test;

import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

public class IbatisTestDAO
extends SqlMapClientDaoSupport
{
	public IbatisTestDAO()
	{
		super();
	}

	public Artikel selectTest1(Integer id)
	{
		Artikel artikel = (Artikel)
getSqlMapClientTemplate().queryForObject("test.selectTest1", id);
		return artikel;
	}
}

-----------------------------

OK, this works. Calling the selectTest1() method returns an instance of
Artikel which contains the ID 1 and an instance of Dienstart, and the
latter contains an instance of Vertragszeitart.

Now I omit one property: the dienstart.id.

Add some elements:

--- test.xml ---

<resultMap id="resultDienstart2" class="test.Dienstart" >
  <result property="vertragszeitart"
resultMap="test.resultVertragszeitart"/>
</resultMap>
	
<resultMap id="resultArtikel2" class="test.Artikel" >
  <result column="ar_id" property="id" jdbcType="INTEGER" />
  <result property="dienstart" resultMap="test.resultDienstart2"/>
</resultMap>
	
<select id="selectTest2" parameterClass="int" resultMap="resultArtikel2" >
	SELECT
		ar.id AS ar_id,
		da.vertragszeitartid AS da_vertragszeitartid
	FROM artikel AS ar
	INNER JOIN dienstart AS da ON da.id = ar.dienstartid
	WHERE ar.id = #value#
</select>
</sqlMap>

--- IbatisTestDAO ---

	public Artikel selectTest2(Integer id)
	{
		Artikel artikel = (Artikel)
getSqlMapClientTemplate().queryForObject("test.selectTest2", id);
		return artikel;
	}

---------------------------

What happens now? Calling IbatisTestDAO.selectTest2() returns an
instance of Artikel, containing the ID 1, but not containing an instance
of Dienstart, although the selected record of artikel contains the value
1 for the column dienstartid.

Of course I searched the sources of iBATIS classes and followed the
execution of code in ResultMap with the debugger. Somewhere during the
call of applyNestedResultMap() in line 380 is decided to not instantiate
the nested object.

Maybe this is related to the comment in line 446:
"//JIRA 375 "Provide a way for not creating items from nested ResultMaps
when the items contain only null values"

Can anybody explain this behaviour?

Thanks a lot!

Ingmar

Re: execution of nested result maps depends on number of mapped properties?

Posted by Ingmar Lötzsch <il...@asci-systemhaus.de>.
Thank you Jeff, for the information.

Jeff Butler schrieb:
> This is a known bug:
> 
> http://issues.apache.org/jira/browse/IBATIS-357
> http://issues.apache.org/jira/browse/IBATIS-450
> 
> Feel free to attach a patch if you've got one.

I think, my first idea, simply call ResultSet.wasNull() does not work. 
The column name attribute of the property element must be ignored in 
case of the presence of the resultMap attribute. Maybe it's correct to 
first apply the children and if any returns not null, then set foundData 
to true. But this is another algorithm.

> Jeff Butler
> 
> 
> On Thu, Oct 23, 2008 at 6:37 AM, Ingmar Lötzsch
> <il...@asci-systemhaus.de> wrote:
>> Hello,
>>
>> I found the reason for my problem is the following method in class
>> ResultMap, line 310:
>>
>>  public Object[] getResults(StatementScope statementScope, ResultSet rs)
>>      throws SQLException {
>> ...
>>    boolean foundData = false;
>>    Object[] columnValues = new Object[getResultMappings().length];
>>    for (int i = 0; i < getResultMappings().length; i++) {
>>      ResultMapping mapping = (ResultMapping) getResultMappings()[i];
>>      errorContext.setMoreInfo(mapping.getErrorString());
>>      if (mapping.getStatementName() != null) {
>> ...
>>        foundData = foundData || columnValues[i] != null;
>>      } else if (mapping.getNestedResultMapName() == null) {
>>        columnValues[i] = getPrimitiveResultMappingValue(rs, mapping);
>>        if (columnValues[i] == null) {
>>          columnValues[i] = doNullMapping(columnValues[i], mapping);
>>        } else {
>>          foundData = true;
>>        }
>>      }
>>    // TODO: add a check for the case
>>    // mapping.getNestedResultMapName() != null
>>    }
>>
>>    statementScope.setRowDataFound(foundData);
>>
>>    return columnValues;
>>  }
>>
>> The value of foundData remains false, if there are only mappings with nested
>> result maps. This proceeding assumes that there is no data in this case. But
>> this assumption seems to be wrong. For the nested result maps one have to
>> check, if ResultSet.wasNull() returns true, I think.
>>
>> else {
>>    // check the return value of ResultSet.wasNull()
>>    if (ResultSet.wasNull() has returned false) {
>>        foundDate = true;
>>    }
>> }

Re: execution of nested result maps depends on number of mapped properties?

Posted by Jeff Butler <je...@gmail.com>.
This is a known bug:

http://issues.apache.org/jira/browse/IBATIS-357
http://issues.apache.org/jira/browse/IBATIS-450

Feel free to attach a patch if you've got one.

Jeff Butler


On Thu, Oct 23, 2008 at 6:37 AM, Ingmar Lötzsch
<il...@asci-systemhaus.de> wrote:
> Hello,
>
> I found the reason for my problem is the following method in class
> ResultMap, line 310:
>
>  public Object[] getResults(StatementScope statementScope, ResultSet rs)
>      throws SQLException {
> ...
>    boolean foundData = false;
>    Object[] columnValues = new Object[getResultMappings().length];
>    for (int i = 0; i < getResultMappings().length; i++) {
>      ResultMapping mapping = (ResultMapping) getResultMappings()[i];
>      errorContext.setMoreInfo(mapping.getErrorString());
>      if (mapping.getStatementName() != null) {
> ...
>        foundData = foundData || columnValues[i] != null;
>      } else if (mapping.getNestedResultMapName() == null) {
>        columnValues[i] = getPrimitiveResultMappingValue(rs, mapping);
>        if (columnValues[i] == null) {
>          columnValues[i] = doNullMapping(columnValues[i], mapping);
>        } else {
>          foundData = true;
>        }
>      }
>    // TODO: add a check for the case
>    // mapping.getNestedResultMapName() != null
>    }
>
>    statementScope.setRowDataFound(foundData);
>
>    return columnValues;
>  }
>
> The value of foundData remains false, if there are only mappings with nested
> result maps. This proceeding assumes that there is no data in this case. But
> this assumption seems to be wrong. For the nested result maps one have to
> check, if ResultSet.wasNull() returns true, I think.
>
> else {
>    // check the return value of ResultSet.wasNull()
>    if (ResultSet.wasNull() has returned false) {
>        foundDate = true;
>    }
> }
>
> Ingmar
>

Re: execution of nested result maps depends on number of mapped properties?

Posted by Ingmar Lötzsch <il...@asci-systemhaus.de>.
Hello,

I found the reason for my problem is the following method in class 
ResultMap, line 310:

   public Object[] getResults(StatementScope statementScope, ResultSet rs)
       throws SQLException {
...
     boolean foundData = false;
     Object[] columnValues = new Object[getResultMappings().length];
     for (int i = 0; i < getResultMappings().length; i++) {
       ResultMapping mapping = (ResultMapping) getResultMappings()[i];
       errorContext.setMoreInfo(mapping.getErrorString());
       if (mapping.getStatementName() != null) {
...
         foundData = foundData || columnValues[i] != null;
       } else if (mapping.getNestedResultMapName() == null) {
         columnValues[i] = getPrimitiveResultMappingValue(rs, mapping);
         if (columnValues[i] == null) {
           columnValues[i] = doNullMapping(columnValues[i], mapping);
         } else {
           foundData = true;
         }
       }
     // TODO: add a check for the case
     // mapping.getNestedResultMapName() != null
     }

     statementScope.setRowDataFound(foundData);

     return columnValues;
   }

The value of foundData remains false, if there are only mappings with 
nested result maps. This proceeding assumes that there is no data in 
this case. But this assumption seems to be wrong. For the nested result 
maps one have to check, if ResultSet.wasNull() returns true, I think.

else {
     // check the return value of ResultSet.wasNull()
     if (ResultSet.wasNull() has returned false) {
         foundDate = true;
     }
}

Ingmar