You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ibatis.apache.org by "Mark Nabours (JIRA)" <ib...@incubator.apache.org> on 2005/02/09 16:04:13 UTC

[jira] Created: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

(N + 1) solution does not yield same results as lazy loading solution
---------------------------------------------------------------------

         Key: IBATIS-69
         URL: http://issues.apache.org/jira/browse/IBATIS-69
     Project: iBatis for Java
        Type: Bug
  Components: SQL Maps  
    Versions: 2.0.9    
 Environment: XP Pro, WSAD 5.1.2
    Reporter: Mark Nabours
 Attachments: Category.java, Item.java, Item.xml

Hello,

We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.

To illustrate the problem I've put together a very simple example.

Please consider the following simple tables of data, categories and items:

+---------------+---------------+
| category_code | category_name |
+---------------+---------------+
| cat1          | Category 1    |
| cat2          | Category 2    |
| cat3          | Category 3    |
+---------------+---------------+

+---------+-----------+---------------+
| item_id | item_name | category_code |
+---------+-----------+---------------+
| item1   | Item 1    | cat1          |
| item2   | Item 2    | cat1          |
| item3   | Item 3    | cat1          |
| item4   | Item 4    | cat1          |
| item5   | Item 5    | cat2          |
| item6   | Item 6    | cat2          |
+---------+-----------+---------------+

The challenge is to load a List of items for each category.

Here are the two extremely simple data objects used to illustrate the issue:

//==============================================================
// Category.java
//==============================================================
package com.alliancesys.ibatisdemo.domain;

import java.util.Iterator;
import java.util.List;

public class Category {
	
	private String code;
	private String name;
	private List items;

	public Category() {
		super();
	}

	public String getCode() {
		return code;
	}

	public List getItems() {
		return items;
	}

	public String getName() {
		return name;
	}

	public void setCode(String string) {
		code = string;
	}

	public void setItems(List list) {
		items = list;
	}

	public void setName(String string) {
		name = string;
	}
	
	/**
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(this.getClass().getName());
		buffer.append("-");
		buffer.append("code:");
		buffer.append(code);
		buffer.append(",name:");
		buffer.append(name);	
		buffer.append(",items:[");
		List items = getItems();
		if (items != null) {
			for (Iterator iter = items.iterator(); iter.hasNext();) {
				buffer.append(iter.next());
				buffer.append(";");			
			}
		}
		buffer.append("]");	
		return buffer.toString();
	}

}


//==============================================================
// Item.java
//==============================================================
package com.alliancesys.ibatisdemo.domain;

public class Item {
	
	private String id;
	private String name;
	private String categoryCode;

	public Item() {
		super();
	}

	public String getCategoryCode() {
		return categoryCode;
	}

	public String getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public void setCategoryCode(String categoryCode) {
		this.categoryCode = categoryCode;
	}

	public void setId(String id) {
		this.id = id;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(this.getClass().getName());
		buffer.append("-");
		buffer.append("id:");
		buffer.append(id); 
		buffer.append(",name:");
		buffer.append(name);
		buffer.append(",categoryCode:");
		buffer.append(categoryCode);
		return buffer.toString();		
	}

}

Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap namespace="Item">

	<typeAlias 
		alias="item" 
		type="com.alliancesys.ibatisdemo.domain.Item" />
		
	<typeAlias 
		alias="category" 
		type="com.alliancesys.ibatisdemo.domain.Category" />
		
	<resultMap 
		id="itemMap"
		class="item">
		<result 
			property="id"
			column="item_id" />
		<result 
			property="name"
			column="item_name" />
		<result 
			property="categoryCode"
			column="category_code" />							
	</resultMap>
	
	<resultMap
		id="categoryWithLazyLoad"
		class="category">		
		<result 
			property="code"
			column="category_code"/>
		<result 
			property="name"
			column="category_name"/>
		<result 
			property="items"
			column="category_code"
			select="getItemsByCategoryCode" />
	</resultMap>

	<resultMap
		id="categoryWithNPlusOne"
		class="category"
		groupBy="code">		
		<result 
			property="code"
			column="category_code"/>
		<result 
			property="name"
			column="category_name" />
		<result 
			property="items"
			resultMap="Item.itemMap" />
	</resultMap>
	
	<select
		id="getItemsByCategoryCode"
		parameterClass="java.lang.String"
		resultMap="itemMap">		
		select
			item_id,
			item_name,
			category_code
		from
			items
		where
			category_code = #value#;		
	</select>

	<select
		id="getAllCategoriesWithLazyLoad"
		resultMap="categoryWithLazyLoad">		
		select
			category_code,
			category_name
		from
			categories;
	</select>
	
	<select
		id="getAllCategoriesWithNPlusOne"
		resultMap="categoryWithNPlusOne">		
		select
			c.category_code,
			c.category_name,
			i.item_id,
			i.item_name
		from
			categories c 
			left join items i on c.category_code = i.category_code
		order by
			c.category_code;		
	</select>	

</sqlMap>

The following test case exercises the two approaches:

package com.alliancesys.ibatis.testing;

import java.io.Reader;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;

import com.alliancesys.ibatisdemo.domain.Category;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;

import junit.framework.TestCase;

public class IBATISAggregateObjectTest extends TestCase {

	/**
	 * Constructor for IBATISAggregateObjectTest.
	 * @param arg0
	 */
	public IBATISAggregateObjectTest(String arg0) {
		super(arg0);
	}

	public static void main(String[] args) {
		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
	}

	public void testGetCategories() throws Exception {
		Reader reader =
			Resources.getResourceAsReader(
				getClass().getClassLoader(),
				"sql-map-config.xml");
		SqlMapClient sqlMapClient =
			SqlMapClientBuilder.buildSqlMapClient(reader);

		try {
			
			sqlMapClient.startTransaction();
			
			//Fetch a list of categories with lazy load of items.
			List categoriesWithLazyLoad =
				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
			
			for (Iterator iter = categoriesWithLazyLoad.iterator();
				iter.hasNext();
				) {
				//force lazy load
				((Category)iter.next()).getItems().iterator();				
			}
			//print each category
			for (Iterator iter = categoriesWithLazyLoad.iterator();
				iter.hasNext();
				) {
				System.out.println(iter.next());
			}
			
			// Fetch a list of categories with N+1 solution
			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
			// print categories expect same output as before
			for (Iterator iter = categoriesWithNPlusOne.iterator();
				iter.hasNext();
				) {
				System.out.println(iter.next());		
			}
		} catch (SQLException e) {
			throw e;
		} finally {
			sqlMapClient.endTransaction();
		}

	}

}

We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.

Looking at cat3 for the N + 1 solution approach, the following is outputted:
com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
Indicating that a nonexistent item was loaded into the list for the items property.

I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:

+---------------+---------------+---------+-----------+
| category_code | category_name | item_id | item_name |
+---------------+---------------+---------+-----------+
| cat1          | Category 1    | item3   | Item 3    |
| cat1          | Category 1    | item4   | Item 4    |
| cat1          | Category 1    | item1   | Item 1    |
| cat1          | Category 1    | item2   | Item 2    |
| cat2          | Category 2    | item5   | Item 5    |
| cat2          | Category 2    | item6   | Item 6    |
| cat3          | Category 3    | NULL    | NULL      |
+---------------+---------------+---------+-----------+

The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.

So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?

We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.

Thanks,
Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Commented: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Mark Nabours (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=comments#action_58828 ]
     
Mark Nabours commented on IBATIS-69:
------------------------------------

Clinton,

I have removed the category_code mapping from the itemMap.  Futhermore, I also removed the categoryCode  property from the Item class.

Now when I run the code, the two approaches yield the EXACT SAME results.  Excellent!  So I certainly now understand when you state that the N + 1 solution looks at ALL of the columns mapped.

Essentially, believe that I could avoid the problem if I would have originally used the following SQL statement:

select
   c.category_code,
   c.category_name,
   i.item_id,
   i.item_name,
   i.category_code as item_category_code
from
   categories c 
   left join items i on c.category_code = i.category_code
order by
   c.category_code;

And mapped item_category_code instead (-- item_category_code would be null for cat3).

This is an acceptable workaround if it works.  I'll test it and post my results.

Thanks,
Mark


> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>  Attachments: Category.java, Item.java, Item.xml, category_item.sql
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Updated: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Mark Nabours (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=history ]

Mark Nabours updated IBATIS-69:
-------------------------------

    Attachment: Category.java
                Item.java
                Item.xml

> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>  Attachments: Category.java, Item.java, Item.xml
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Updated: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Mark Nabours (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=history ]

Mark Nabours updated IBATIS-69:
-------------------------------

    Attachment: category_item.sql

> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>  Attachments: Category.java, Item.java, Item.xml, category_item.sql
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Commented: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Mark Nabours (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=comments#action_58835 ]
     
Mark Nabours commented on IBATIS-69:
------------------------------------

Clinton,

Yes, I'm happy to say that this will satisfy our current needs.

Keep up the great work.

Thanks,
Mark

> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>  Attachments: Category.java, Item.java, Item.xml, category_item.sql
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Commented: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Clinton Begin (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=comments#action_58832 ]
     
Clinton Begin commented on IBATIS-69:
-------------------------------------


Ahhh...fantastic..

That actually looks like it makes a lot more sense too.

Is this a solution for you?  I only ask because our SVN repos is frozen right now, so I won't be able to get the fix in any time soon.

That said, this looks like it should work quite well for most situations.  The only one I can see that it wouldn't work for is if you joined by key fields, but wanted to include a non-key field as the additional property.  For example, if your Item class had category_name on it, this wouldn't work because there's no category_name column on the item table --therefore forcing you back into the previous situation.

Understanding that, will this solution work well for you in the mean time?

Cheers,
Clinton

> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>  Attachments: Category.java, Item.java, Item.xml, category_item.sql
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Commented: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Mark Nabours (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=comments#action_58831 ]
     
Mark Nabours commented on IBATIS-69:
------------------------------------

Alright -- it's verified.

The following SQL Map file yields the same results under lazy loading and the (N + 1) solution:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap namespace="Item">

   <typeAlias 
      alias="item" 
      type="com.alliancesys.ibatisdemo.domain.Item" />
      
   <typeAlias 
      alias="category" 
      type="com.alliancesys.ibatisdemo.domain.Category" />
      
   <resultMap 
      id="itemMap"
      class="item">
      <result 
         property="id"
         column="item_id" />
      <result 
         property="name"
         column="item_name" />
      <result 
         property="categoryCode"
         column="item_category_code" />               
   </resultMap>
   
   <resultMap
      id="categoryWithLazyLoad"
      class="category">    
      <result 
         property="code"
         column="category_code"/>
      <result 
         property="name"
         column="category_name"/>
      <result 
         property="items"
         column="category_code"
         select="getItemsByCategoryCode" />
   </resultMap>

   <resultMap
      id="categoryWithNPlusOne"
      class="category"
      groupBy="code">      
      <result 
         property="code"
         column="category_code"/>
      <result 
         property="name"
         column="category_name" />
      <result 
         property="items"
         resultMap="Item.itemMap" />
   </resultMap>
   
   <select
      id="getItemsByCategoryCode"
      parameterClass="java.lang.String"
      resultMap="itemMap">    
      select
         item_id,
         item_name,
         category_code as item_category_code
      from
         items
      where
         category_code = #value#;      
   </select>

   <select
      id="getAllCategoriesWithLazyLoad"
      resultMap="categoryWithLazyLoad">      
      select
         category_code,
         category_name
      from
         categories;
   </select>
   
   <select
      id="getAllCategoriesWithNPlusOne"
      resultMap="categoryWithNPlusOne">      
      select
         c.category_code,
         c.category_name,
         i.item_id,
         i.item_name,
         i.category_code as item_category_code
      from
         categories c 
         left join items i on c.category_code = i.category_code
      order by
         c.category_code;     
   </select>   

</sqlMap>

The only changes are that the alias item_category_code is used in the two selects and that alias is mapped to the categoryCode property on Item via :

     <result 
         property="categoryCode"
         column="item_category_code" />  

> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>  Attachments: Category.java, Item.java, Item.xml, category_item.sql
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Commented: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Mark Nabours (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=comments#action_58826 ]
     
Mark Nabours commented on IBATIS-69:
------------------------------------

*** This is just a reposting of the original, but with spaces used instead of tabs in hope of preserving formatting ***

Hello,

We have a situation where it would be advantageous for performance reasons
to avoid lazy loading and utilize the recently implemented (N + 1)
solution.  Unfortunately, when utilizing a left join the results are not
the same as the lazy loading solution.  

To illustrate the problem I've put together a very simple example.

Please consider the following simple tables of data, categories and items:

+---------------+---------------+
| category_code | category_name |
+---------------+---------------+
| cat1          | Category 1    |
| cat2          | Category 2    |
| cat3          | Category 3    |
+---------------+---------------+

+---------+-----------+---------------+
| item_id | item_name | category_code |
+---------+-----------+---------------+
| item1   | Item 1    | cat1          |
| item2   | Item 2    | cat1          |
| item3   | Item 3    | cat1          |
| item4   | Item 4    | cat1          |
| item5   | Item 5    | cat2          |
| item6   | Item 6    | cat2          |
+---------+-----------+---------------+

The challenge is to load a List of items for each category.

Here are the two extremely simple data objects used to illustrate the
issue:

//==============================================================
// Category.java
//==============================================================
package com.alliancesys.ibatisdemo.domain;

import java.util.Iterator;
import java.util.List;

public class Category {

      private String code;
      private String name;
      private List items;

      public Category() {
            super();
      }

      public String getCode() {
            return code;
      }

      public List getItems() {
            return items;
      }

      public String getName() {
            return name;
      }

      public void setCode(String string) {
            code = string;
      }

      public void setItems(List list) {
            items = list;
      }

      public void setName(String string) {
            name = string;
      }

      /**
       * @see java.lang.Object#toString()
       */
      public String toString() {
            StringBuffer buffer = new StringBuffer();
            buffer.append(this.getClass().getName());
            buffer.append("-");
            buffer.append("code:");
            buffer.append(code);
            buffer.append(",name:");
            buffer.append(name);
            buffer.append(",items:[");
            List items = getItems();
            if (items != null) {
                  for (Iterator iter = items.iterator(); iter.hasNext();) {
                        buffer.append(iter.next());
                        buffer.append(";");
                  }
            }
            buffer.append("]");
            return buffer.toString();
      }

}


//==============================================================
// Item.java
//==============================================================
package com.alliancesys.ibatisdemo.domain;

public class Item {

      private String id;
      private String name;
      private String categoryCode;

      public Item() {
            super();
      }

      public String getCategoryCode() {
            return categoryCode;
      }

      public String getId() {
            return id;
      }

      public String getName() {
            return name;
      }

      public void setCategoryCode(String categoryCode) {
            this.categoryCode = categoryCode;
      }

      public void setId(String id) {
            this.id = id;
      }

      public void setName(String name) {
            this.name = name;
      }

      /**
       * @see java.lang.Object#toString()
       */
      public String toString() {
            StringBuffer buffer = new StringBuffer();
            buffer.append(this.getClass().getName());
            buffer.append("-");
            buffer.append("id:");
            buffer.append(id);
            buffer.append(",name:");
            buffer.append(name);
            buffer.append(",categoryCode:");
            buffer.append(categoryCode);
            return buffer.toString();
      }

}

Here is the sqlMap file that contains both the lazy loading solution (
getAllCategoriesWithLazyLoad) and the N + 1 solution (
getAllCategoriesWithNPlusOne):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap namespace="Item">

      <typeAlias
            alias="item"
            type="com.alliancesys.ibatisdemo.domain.Item" />

      <typeAlias
            alias="category"
            type="com.alliancesys.ibatisdemo.domain.Category" />

      <resultMap
            id="itemMap"
            class="item">
            <result
                  property="id"
                  column="item_id" />
            <result
                  property="name"
                  column="item_name" />
            <result
                  property="categoryCode"
                  column="category_code" />

      </resultMap>

      <resultMap
            id="categoryWithLazyLoad"
            class="category">
            <result
                  property="code"
                  column="category_code"/>
            <result
                  property="name"
                  column="category_name"/>
            <result
                  property="items"
                  column="category_code"
                  select="getItemsByCategoryCode" />
      </resultMap>

      <resultMap
            id="categoryWithNPlusOne"
            class="category"
            groupBy="code">
            <result
                  property="code"
                  column="category_code"/>
            <result
                  property="name"
                  column="category_name" />
            <result
                  property="items"
                  resultMap="Item.itemMap" />
      </resultMap>

      <select
            id="getItemsByCategoryCode"
            parameterClass="java.lang.String"
            resultMap="itemMap">
            select
                  item_id,
                  item_name,
                  category_code
            from
                  items
            where
                  category_code = #value#;
      </select>

      <select
            id="getAllCategoriesWithLazyLoad"
            resultMap="categoryWithLazyLoad">
            select
                  category_code,
                  category_name
            from
                  categories;
      </select>

      <select
            id="getAllCategoriesWithNPlusOne"
            resultMap="categoryWithNPlusOne">
            select
                  c.category_code,
                  c.category_name,
                  i.item_id,
                  i.item_name
            from
                  categories c
                  left join items i on c.category_code = i.category_code
            order by
                  c.category_code;
      </select>

</sqlMap>

The following test case exercises the two approaches:

package com.alliancesys.ibatis.testing;

import java.io.Reader;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;

import com.alliancesys.ibatisdemo.domain.Category;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;

import junit.framework.TestCase;

public class IBATISAggregateObjectTest extends TestCase {

      /**
       * Constructor for IBATISAggregateObjectTest.
       * @param arg0
       */
      public IBATISAggregateObjectTest(String arg0) {
            super(arg0);
      }

      public static void main(String[] args) {
            junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
      }

      public void testGetCategories() throws Exception {
            Reader reader =
                  Resources.getResourceAsReader(
                        getClass().getClassLoader(),
                        "sql-map-config.xml");
            SqlMapClient sqlMapClient =
                  SqlMapClientBuilder.buildSqlMapClient(reader);

            try {

                  sqlMapClient.startTransaction();

                  //Fetch a list of categories with lazy load of items.
                  List categoriesWithLazyLoad =
                        sqlMapClient.queryForList(
"getAllCategoriesWithLazyLoad", null);

                  for (Iterator iter = categoriesWithLazyLoad.iterator();
                        iter.hasNext();
                        ) {
                        //force lazy load
                        ((Category)iter.next()).getItems().iterator();

                  }
                  //print each category
                  for (Iterator iter = categoriesWithLazyLoad.iterator();
                        iter.hasNext();
                        ) {
                        System.out.println(iter.next());
                  }

                  // Fetch a list of categories with N+1 solution
                  List categoriesWithNPlusOne = sqlMapClient.queryForList(
"getAllCategoriesWithNPlusOne", null);
                  // print categories expect same output as before
                  for (Iterator iter = categoriesWithNPlusOne.iterator();
                        iter.hasNext();
                        ) {
                        System.out.println(iter.next());
                  }
            } catch (SQLException e) {
                  throw e;
            } finally {
                  sqlMapClient.endTransaction();
            }

      }

}

We expect both approaches to yield the same output.  But for the cat3
category under the lazy loading approach the following is outputted:
com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category
3,items:[]
Indicating that the items property was loaded with an empty List.  This is
exactly  what I expect would happen since there are no items for that
category.  All is well except that performance stinks for our real world
needs because of the N + 1 issue.

Looking at cat3 for the N + 1 solution approach, the following is
outputted:
com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category
3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
Indicating that a nonexistent item was loaded into the list for the items
property.

I believe the issue is that the (N + 1) solution approach does not handle
left joins.  (It's not acceptable for me to switch the join to an inner
join or I would basically lose cat3 from the collection.)  The results from
the left join are as follows:

+---------------+---------------+---------+-----------+
| category_code | category_name | item_id | item_name |
+---------------+---------------+---------+-----------+
| cat1          | Category 1    | item3   | Item 3    |
| cat1          | Category 1    | item4   | Item 4    |
| cat1          | Category 1    | item1   | Item 1    |
| cat1          | Category 1    | item2   | Item 2    |
| cat2          | Category 2    | item5   | Item 5    |
| cat2          | Category 2    | item6   | Item 6    |
| cat3          | Category 3    | NULL    | NULL      |
+---------------+---------------+---------+-----------+

The cat3 row seems to be problematic unless there are additional
configuration settings in the SQLMap file that address this.

So in summary, the lazy loading approach yields the correct results, but we
need to utilize the (N + 1) solution for performance but it yields bogus
results.  Is there already a solution for this?  Or should this be logged
in JIRA?

We would appreciate a quick turnaround on this because the (N + 1) support
is such a great feature that we want to be able to utilize to simplify our
code.

Thanks,
Mark

> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>  Attachments: Category.java, Item.java, Item.xml, category_item.sql
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Closed: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Clinton Begin (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=history ]
     
Clinton Begin closed IBATIS-69:
-------------------------------

     Assign To: Clinton Begin
    Resolution: Invalid


I'll close this issue, as the new solution makes a lot of sense.  We can always reopen it as a feature request if we see a need for it.

> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>     Assignee: Clinton Begin
>  Attachments: Category.java, Item.java, Item.xml, category_item.sql
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira


[jira] Commented: (IBATIS-69) (N + 1) solution does not yield same results as lazy loading solution

Posted by "Clinton Begin (JIRA)" <ib...@incubator.apache.org>.
     [ http://issues.apache.org/jira/browse/IBATIS-69?page=comments#action_58825 ]
     
Clinton Begin commented on IBATIS-69:
-------------------------------------

Ahhh...okay.  Even without the exception, I can see the problem here.

First, please understand that iBATIS is NOT an O/R Mapper, so it
differs greatly in its behavior.  Your assumption that N+1 and Lazy
Load will work the same way is somewhat more applicable to an ORM than
it is to iBATIS.  iBATIS doesn't know the difference between the types
in the table, so it has no idea what you're definition of the data is.
iBATIS just knows rows and results, it does not know types.  That is,
Category columns are no different from Item columns.

Now, here's the deal.  Your result data is as follows:

+---------------+---------------+---------+-----------+
| category_code | category_name | item_id | item_name |
+---------------+---------------+---------+-----------+
| cat1          | Category 1    | item3   | Item 3    |
| cat1          | Category 1    | item4   | Item 4    |
| cat1          | Category 1    | item1   | Item 1    |
| cat1          | Category 1    | item2   | Item 2    |
| cat2          | Category 2    | item5   | Item 5    |
| cat2          | Category 2    | item6   | Item 6    |
| cat3          | Category 3    | NULL    | NULL      |
+---------------+---------------+---------+-----------+

In the last row, the item columns are NULL.  But, category_code does
have a value, and you've mapped it in "itemMap".  Because there's
mapped data in that row, iBATIS will create a row for it.  This
differs from the lazy loader in that it only cares about the column
you use as the key (or parameter).  The N+1 solution cares about ALL
mapped columns --if there's any data to report based on the resultMap,
it will be mapped.

<resultMap id="itemMap" class="item">
 <result property="id" column="item_id" />
 <result property="name" column="item_name" />
 <result property="categoryCode" column="category_code" />
</resultMap>

Now, let's talk about a solution.

As a quick sanity check, could you please remove the "category_code"
mapping from "itemMap"?  This will prove my conclusion and allow us to
procede with a solution.

What are the potential solutions?  It will no doubt require an
additional attribute.  The new attribute should indicate which columns
are significant to identity in the resultMap.  It's starting to sound
like a key indicator.

To date, we've shunned key indicators for various reasons.  Object
identity is not something we support.  However, this is probably the
first indication of a possible "requirement" for key support.
Luckily, there are other reasons why it will help (could help improve
caching).

Anyway...

Pascal, could you please copy your full request into a JIRA ticket,
and I'll post this response there?  I really would rather use JIRA to
track large requests like this.  The good news is, the fix probably
won't be terribly hard to implement and could make it into the next
release (once our SVN repos gets set up).

Clinton

> (N + 1) solution does not yield same results as lazy loading solution
> ---------------------------------------------------------------------
>
>          Key: IBATIS-69
>          URL: http://issues.apache.org/jira/browse/IBATIS-69
>      Project: iBatis for Java
>         Type: Bug
>   Components: SQL Maps
>     Versions: 2.0.9
>  Environment: XP Pro, WSAD 5.1.2
>     Reporter: Mark Nabours
>  Attachments: Category.java, Item.java, Item.xml, category_item.sql
>
> Hello,
> We have a situation where it would be advantageous for performance reasons to avoid lazy loading and utilize the recently implemented (N + 1) solution.  Unfortunately, when utilizing a left join the results are not  the same as the lazy loading solution.
> To illustrate the problem I've put together a very simple example.
> Please consider the following simple tables of data, categories and items:
> +---------------+---------------+
> | category_code | category_name |
> +---------------+---------------+
> | cat1          | Category 1    |
> | cat2          | Category 2    |
> | cat3          | Category 3    |
> +---------------+---------------+
> +---------+-----------+---------------+
> | item_id | item_name | category_code |
> +---------+-----------+---------------+
> | item1   | Item 1    | cat1          |
> | item2   | Item 2    | cat1          |
> | item3   | Item 3    | cat1          |
> | item4   | Item 4    | cat1          |
> | item5   | Item 5    | cat2          |
> | item6   | Item 6    | cat2          |
> +---------+-----------+---------------+
> The challenge is to load a List of items for each category.
> Here are the two extremely simple data objects used to illustrate the issue:
> //==============================================================
> // Category.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> import java.util.Iterator;
> import java.util.List;
> public class Category {
> 	
> 	private String code;
> 	private String name;
> 	private List items;
> 	public Category() {
> 		super();
> 	}
> 	public String getCode() {
> 		return code;
> 	}
> 	public List getItems() {
> 		return items;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCode(String string) {
> 		code = string;
> 	}
> 	public void setItems(List list) {
> 		items = list;
> 	}
> 	public void setName(String string) {
> 		name = string;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("code:");
> 		buffer.append(code);
> 		buffer.append(",name:");
> 		buffer.append(name);	
> 		buffer.append(",items:[");
> 		List items = getItems();
> 		if (items != null) {
> 			for (Iterator iter = items.iterator(); iter.hasNext();) {
> 				buffer.append(iter.next());
> 				buffer.append(";");			
> 			}
> 		}
> 		buffer.append("]");	
> 		return buffer.toString();
> 	}
> }
> //==============================================================
> // Item.java
> //==============================================================
> package com.alliancesys.ibatisdemo.domain;
> public class Item {
> 	
> 	private String id;
> 	private String name;
> 	private String categoryCode;
> 	public Item() {
> 		super();
> 	}
> 	public String getCategoryCode() {
> 		return categoryCode;
> 	}
> 	public String getId() {
> 		return id;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setCategoryCode(String categoryCode) {
> 		this.categoryCode = categoryCode;
> 	}
> 	public void setId(String id) {
> 		this.id = id;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> 	/**
> 	 * @see java.lang.Object#toString()
> 	 */
> 	public String toString() {
> 		StringBuffer buffer = new StringBuffer();
> 		buffer.append(this.getClass().getName());
> 		buffer.append("-");
> 		buffer.append("id:");
> 		buffer.append(id); 
> 		buffer.append(",name:");
> 		buffer.append(name);
> 		buffer.append(",categoryCode:");
> 		buffer.append(categoryCode);
> 		return buffer.toString();		
> 	}
> }
> Here is the sqlMap file that contains both the lazy loading solution (getAllCategoriesWithLazyLoad) and the N + 1 solution (getAllCategoriesWithNPlusOne):
> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
> <sqlMap namespace="Item">
> 	<typeAlias 
> 		alias="item" 
> 		type="com.alliancesys.ibatisdemo.domain.Item" />
> 		
> 	<typeAlias 
> 		alias="category" 
> 		type="com.alliancesys.ibatisdemo.domain.Category" />
> 		
> 	<resultMap 
> 		id="itemMap"
> 		class="item">
> 		<result 
> 			property="id"
> 			column="item_id" />
> 		<result 
> 			property="name"
> 			column="item_name" />
> 		<result 
> 			property="categoryCode"
> 			column="category_code" />							
> 	</resultMap>
> 	
> 	<resultMap
> 		id="categoryWithLazyLoad"
> 		class="category">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name"/>
> 		<result 
> 			property="items"
> 			column="category_code"
> 			select="getItemsByCategoryCode" />
> 	</resultMap>
> 	<resultMap
> 		id="categoryWithNPlusOne"
> 		class="category"
> 		groupBy="code">		
> 		<result 
> 			property="code"
> 			column="category_code"/>
> 		<result 
> 			property="name"
> 			column="category_name" />
> 		<result 
> 			property="items"
> 			resultMap="Item.itemMap" />
> 	</resultMap>
> 	
> 	<select
> 		id="getItemsByCategoryCode"
> 		parameterClass="java.lang.String"
> 		resultMap="itemMap">		
> 		select
> 			item_id,
> 			item_name,
> 			category_code
> 		from
> 			items
> 		where
> 			category_code = #value#;		
> 	</select>
> 	<select
> 		id="getAllCategoriesWithLazyLoad"
> 		resultMap="categoryWithLazyLoad">		
> 		select
> 			category_code,
> 			category_name
> 		from
> 			categories;
> 	</select>
> 	
> 	<select
> 		id="getAllCategoriesWithNPlusOne"
> 		resultMap="categoryWithNPlusOne">		
> 		select
> 			c.category_code,
> 			c.category_name,
> 			i.item_id,
> 			i.item_name
> 		from
> 			categories c 
> 			left join items i on c.category_code = i.category_code
> 		order by
> 			c.category_code;		
> 	</select>	
> </sqlMap>
> The following test case exercises the two approaches:
> package com.alliancesys.ibatis.testing;
> import java.io.Reader;
> import java.sql.SQLException;
> import java.util.Iterator;
> import java.util.List;
> import com.alliancesys.ibatisdemo.domain.Category;
> import com.ibatis.common.resources.Resources;
> import com.ibatis.sqlmap.client.SqlMapClient;
> import com.ibatis.sqlmap.client.SqlMapClientBuilder;
> import junit.framework.TestCase;
> public class IBATISAggregateObjectTest extends TestCase {
> 	/**
> 	 * Constructor for IBATISAggregateObjectTest.
> 	 * @param arg0
> 	 */
> 	public IBATISAggregateObjectTest(String arg0) {
> 		super(arg0);
> 	}
> 	public static void main(String[] args) {
> 		junit.textui.TestRunner.run(IBATISAggregateObjectTest.class);
> 	}
> 	public void testGetCategories() throws Exception {
> 		Reader reader =
> 			Resources.getResourceAsReader(
> 				getClass().getClassLoader(),
> 				"sql-map-config.xml");
> 		SqlMapClient sqlMapClient =
> 			SqlMapClientBuilder.buildSqlMapClient(reader);
> 		try {
> 			
> 			sqlMapClient.startTransaction();
> 			
> 			//Fetch a list of categories with lazy load of items.
> 			List categoriesWithLazyLoad =
> 				sqlMapClient.queryForList("getAllCategoriesWithLazyLoad", null);
> 			
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				//force lazy load
> 				((Category)iter.next()).getItems().iterator();				
> 			}
> 			//print each category
> 			for (Iterator iter = categoriesWithLazyLoad.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());
> 			}
> 			
> 			// Fetch a list of categories with N+1 solution
> 			List categoriesWithNPlusOne = sqlMapClient.queryForList("getAllCategoriesWithNPlusOne", null);
> 			// print categories expect same output as before
> 			for (Iterator iter = categoriesWithNPlusOne.iterator();
> 				iter.hasNext();
> 				) {
> 				System.out.println(iter.next());		
> 			}
> 		} catch (SQLException e) {
> 			throw e;
> 		} finally {
> 			sqlMapClient.endTransaction();
> 		}
> 	}
> }
> We expect both approaches to yield the same output.  But for the cat3 category under the lazy loading approach the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[]
> Indicating that the items property was loaded with an empty List.  This is exactly  what I expect would happen since there are no items for that category.  All is well except that performance stinks for our real world needs because of the N + 1 issue.
> Looking at cat3 for the N + 1 solution approach, the following is outputted:
> com.alliancesys.ibatisdemo.domain.Category-code:cat3,name:Category 3,items:[com.alliancesys.ibatisdemo.domain.Item-id:null,name:null,categoryCode:cat3;]
> Indicating that a nonexistent item was loaded into the list for the items property.
> I believe the issue is that the (N + 1) solution approach does not handle left joins.  (It's not acceptable for me to switch the join to an inner join or I would basically lose cat3 from the collection.)  The results from the left join are as follows:
> +---------------+---------------+---------+-----------+
> | category_code | category_name | item_id | item_name |
> +---------------+---------------+---------+-----------+
> | cat1          | Category 1    | item3   | Item 3    |
> | cat1          | Category 1    | item4   | Item 4    |
> | cat1          | Category 1    | item1   | Item 1    |
> | cat1          | Category 1    | item2   | Item 2    |
> | cat2          | Category 2    | item5   | Item 5    |
> | cat2          | Category 2    | item6   | Item 6    |
> | cat3          | Category 3    | NULL    | NULL      |
> +---------------+---------------+---------+-----------+
> The cat3 row seems to be problematic unless there are additional configuration settings in the SQLMap file that address this.
> So in summary, the lazy loading approach yields the correct results, but we need to utilize the (N + 1) solution for performance but it yields bogus results.  Is there already a solution for this?  Or should this be logged in JIRA?
> We would appreciate a quick turnaround on this because the (N + 1) support is such a great feature that we want to be able to utilize to simplify our code.
> Thanks,
> Mark

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira