我們先來看一個問題:一個User用戶對應多張信用卡Card
類User:
package com.demo.beans;
import java.util.List;
public class User {
private int id;
private String name;
private List<Card> cards;
set get省略...
}
類Card:
package com.demo.beans;
public class Card {
private int id;
private String cardName;
private double balance;
private int userId;
set get省略...
}
對應數據庫中的表結構:
表user:
CREATE TABLE `user` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;
表card:
CREATE TABLE `card` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`cardName` varchar(255) DEFAULT NULL,
`balance` double DEFAULT NULL,
`userId` int(11) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=2006 DEFAULT CHARSET=utf8;
我們考慮這樣一個需求:從數據庫中獲得所有用戶的信息(包括他所持有的信用卡信息)。
對應的UserCards.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="UserCards">
<typeAlias alias="Card" type="com.demo.beans.Card" />
<typeAlias alias="User" type="com.demo.beans.User"/>
<resultMap class="User" id="UserResult" >
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="cards" select="UserCards.getCardsByUserId" column="id"/>
</resultMap>
<resultMap id="CardResult" class="Card">
<result property="id" column="id" />
<result property="cardName" column="cardName" />
<result property="balance" column="balance" />
<result property="userId" column="userId" />
</resultMap>
<select id="getUserById" resultMap="UserResult" parameterClass="int">
select * from user where id = #value#
</select>
<select id="getCardsByUserId" resultMap="CardResult" parameterClass="int">
select * from card where userId = #value#
</select>
<select id="getAllUsers" resultMap="UserResult">
select * from user
</select>
<insert id="addCard" parameterClass="Card" >
<selectKey resultClass="int" type="post" keyProperty="id" >
select LAST_INSERT_ID() as value
</selectKey>
insert into card values(#id#,#cardName#,#balance#,#userId#)
</insert>
<insert id="addUser" parameterClass="User" >
<selectKey resultClass="int" type="post" keyProperty="id" >
select LAST_INSERT_ID() as value
</selectKey>
insert into user values(#id#,#name#)
</insert>
</sqlMap>
注意上面xml關鍵部分:
<result property="cards" select="UserCards.getCardsByUserId" column="id"/>
每查一個user,根據他的id來getCardsByUserId查卡信息
我們的單元測試部分:
public void testGetAll(){
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) factory.getBean("userDao");
long start = System.currentTimeMillis();
List<User> userList = userDao.getAllUser();
for(User user : userList){
System.out.println(user.getName());
for(Card card : user.getCards()){
System.out.println(card.getCardName()+"|"+card.getBalance());
}
System.out.println("------------------------");
}
long end = System.currentTimeMillis();
System.out.println(end-start);
}
在數據庫中我們添加了1000個左右的用戶和1000張卡信息,相互對應
控制檯輸出如下:
xiaoye994
2011-01-14 10:38:52,218 DEBUG - {conn-102994} Connection
2011-01-14 10:38:52,218 DEBUG - {conn-102994} Preparing Statement: select * from card where userId = ?
2011-01-14 10:38:52,218 DEBUG - {pstm-102995} Executing Statement: select * from card where userId = ?
2011-01-14 10:38:52,218 DEBUG - {pstm-102995} Parameters: [998]
2011-01-14 10:38:52,218 DEBUG - {pstm-102995} Types: [java.lang.Integer]
中信A00998|999.0
------------------------
xiaoye995
2011-01-14 10:38:52,218 DEBUG - {conn-102997} Connection
2011-01-14 10:38:52,218 DEBUG - {conn-102997} Preparing Statement: select * from card where userId = ?
2011-01-14 10:38:52,218 DEBUG - {pstm-102998} Executing Statement: select * from card where userId = ?
2011-01-14 10:38:52,218 DEBUG - {pstm-102998} Parameters: [999]
2011-01-14 10:38:52,218 DEBUG - {pstm-102998} Types: [java.lang.Integer]
中信A00999|1000.0
------------------------
2078
可以看到每次輸出一個用戶名如"xiaoye995",然後發出一條select * from card where userId = ?語句。
這就是N+1問題:我們有995個用戶 那麼就會發出995條上面的語句 另外一條則是查詢用戶的SQL語句select * from user,
這裏的N即爲995。可以看到,如果要查詢所有用戶及其卡信息,這樣的效率是很差的,總共花了大約2000多毫秒的時間。
解決辦法之一,使用懶加載 就是在需要用戶卡信息的時候才觸發sql調用
<settings lazyLoadingEnabled="true" useStatementNamespaces="true"/>
解決辦法之二,ibatis中已經解決了N+1問題,具體看下面:
修改UserCards.xml文件,關鍵部分如下:
<resultMap class="User" id="UserResult" groupBy="id">
<result property="id" column="id" nullValue="0"/>
<result property="name" column="name"/>
<result property="cards" resultMap="UserCardsN1.CardResult" />
</resultMap>
<select id="getAllUsers" resultMap="UserResult">
select * from user a left join card b on a.id = b.userId
</select>
區別主要體現在groupBy="id"和<result property="cards" resultMap="UserCardsN1.CardResult" />及select用了表連接
控制檯輸出信息:
2011-01-14 11:10:55,156 DEBUG - {conn-100000} Connection
2011-01-14 11:10:55,171 DEBUG - {conn-100000} Preparing Statement: select * from user a left join card b on a.id = b.userId
2011-01-14 11:10:55,703 DEBUG - {pstm-100001} Executing Statement: select * from user a left join card b on a.id = b.userId
2011-01-14 11:10:55,703 DEBUG - {pstm-100001} Parameters: []
2011-01-14 11:10:55,703 DEBUG - {pstm-100001} Types: []
xiaoye
建行卡|4353.0
招行卡|23112.0
中信A001|2.0
------------------------
king
工行卡|1000.0
中信A002|3.0
------------------------
..........
.........
------------------------
766
可以看到只發出了一條sql語句 輸出了所有的信息 大概用時766毫秒
結論:
1)懶加載適合不需要一次性取出用戶信息及其關聯信息的場景
2)N+1的解決方法是一次性取出的,適合數據量較少的情況