ibatis N+1问题

来源:互联网 发布:知之愈明 则行之愈笃 编辑:程序博客网 时间:2024/05/18 20:51

我们先来看一个问题:一个User用户对应多张信用卡Card

类User:

[java] view plaincopy
  1. package com.demo.beans;  
  2.   
  3. import java.util.List;  
  4.   
  5. public class User {  
  6.     private int id;  
  7.     private String name;  
  8.     private List<Card> cards;  
  9.   
  10.         set get省略...  
  11. }  


类Card:

[java] view plaincopy
  1. package com.demo.beans;  
  2.   
  3. public class Card {  
  4.     private int id;  
  5.     private String cardName;  
  6.     private double balance;  
  7.     private int userId;  
  8.   
  9.         set get省略...  
  10. }  


 

对应数据库中的表结构:

表user:

[sql] view plaincopy
  1. CREATE TABLE `user` (  
  2.   `Id` int(11) NOT NULL AUTO_INCREMENT,  
  3.   `namevarchar(255) DEFAULT NULL,  
  4.   PRIMARY KEY (`Id`)  
  5. ) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;  


表card:

[sql] view plaincopy
  1. CREATE TABLE `card` (  
  2.   `Id` int(11) NOT NULL AUTO_INCREMENT,  
  3.   `cardName` varchar(255) DEFAULT NULL,  
  4.   `balance` double DEFAULT NULL,  
  5.   `userId` int(11) DEFAULT NULL,  
  6.   PRIMARY KEY (`Id`)  
  7. ) ENGINE=InnoDB AUTO_INCREMENT=2006 DEFAULT CHARSET=utf8;  


 

我们考虑这样一个需求:从数据库中获得所有用户的信息(包括他所持有的信用卡信息)。

对应的UserCards.xml:

[java] view plaincopy
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2.   
  3. <!DOCTYPE sqlMap        
  4.     PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"        
  5.     "http://ibatis.apache.org/dtd/sql-map-2.dtd">  
  6.   
  7. <sqlMap namespace="UserCards">  
  8.   
  9.     <typeAlias alias="Card" type="com.demo.beans.Card" />  
  10.     <typeAlias alias="User" type="com.demo.beans.User"/>  
  11.       
  12.     <resultMap class="User" id="UserResult" >  
  13.         <result property="id" column="id"/>  
  14.         <result property="name" column="name"/>  
  15.         <result property="cards" select="UserCards.getCardsByUserId" column="id"/>  
  16.     </resultMap>  
  17.       
  18.     <resultMap id="CardResult" class="Card">  
  19.         <result property="id" column="id" />  
  20.         <result property="cardName" column="cardName" />  
  21.         <result property="balance" column="balance" />  
  22.         <result property="userId" column="userId" />  
  23.     </resultMap>  
  24.       
  25.     <select id="getUserById" resultMap="UserResult" parameterClass="int">  
  26.         select * from user where id = #value#  
  27.     </select>  
  28.       
  29.     <select id="getCardsByUserId" resultMap="CardResult" parameterClass="int">  
  30.         select * from card where userId = #value#  
  31.     </select>  
  32.       
  33.     <select id="getAllUsers" resultMap="UserResult">  
  34.         select * from user  
  35.     </select>  
  36.       
  37.     <insert id="addCard" parameterClass="Card" >  
  38.         <selectKey resultClass="int" type="post"  keyProperty="id" >  
  39.             select LAST_INSERT_ID() as value  
  40.         </selectKey>  
  41.         insert into card values(#id#,#cardName#,#balance#,#userId#)  
  42.     </insert>  
  43.       
  44.     <insert id="addUser" parameterClass="User" >  
  45.         <selectKey resultClass="int" type="post"  keyProperty="id" >  
  46.             select LAST_INSERT_ID() as value  
  47.         </selectKey>  
  48.         insert into user values(#id#,#name#)  
  49.     </insert>  
  50.       
  51. </sqlMap>  


 

注意上面xml关键部分:

[java] view plaincopy
  1. <result property="cards" select="UserCards.getCardsByUserId" column="id"/>  

每查一个user,根据他的id来getCardsByUserId查卡信息

 

我们的单元测试部分:

[java] view plaincopy
  1. public void testGetAll(){  
  2.         BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");  
  3.         UserDao userDao = (UserDao) factory.getBean("userDao");  
  4.           
  5.         long start = System.currentTimeMillis();  
  6.           
  7.         List<User> userList = userDao.getAllUser();  
  8.         for(User user : userList){  
  9.             System.out.println(user.getName());  
  10.             for(Card card : user.getCards()){  
  11.                 System.out.println(card.getCardName()+"|"+card.getBalance());  
  12.             }  
  13.             System.out.println("------------------------");  
  14.         }  
  15.           
  16.         long end = System.currentTimeMillis();  
  17.         System.out.println(end-start);  
  18.     }  


 

在数据库中我们添加了1000个左右的用户和1000张卡信息,相互对应

控制台输出如下:

[java] view plaincopy
  1. xiaoye994  
  2. 2011-01-14 10:38:52,218 DEBUG - {conn-102994} Connection  
  3. 2011-01-14 10:38:52,218 DEBUG - {conn-102994} Preparing Statement:    select * from card where userId = ?    
  4. 2011-01-14 10:38:52,218 DEBUG - {pstm-102995} Executing Statement:    select * from card where userId = ?    
  5. 2011-01-14 10:38:52,218 DEBUG - {pstm-102995} Parameters: [998]  
  6. 2011-01-14 10:38:52,218 DEBUG - {pstm-102995} Types: [java.lang.Integer]  
  7. 中信A00998|999.0  
  8. ------------------------  
  9. xiaoye995  
  10. 2011-01-14 10:38:52,218 DEBUG - {conn-102997} Connection  
  11. 2011-01-14 10:38:52,218 DEBUG - {conn-102997} Preparing Statement:    select * from card where userId = ?    
  12. 2011-01-14 10:38:52,218 DEBUG - {pstm-102998} Executing Statement:    select * from card where userId = ?    
  13. 2011-01-14 10:38:52,218 DEBUG - {pstm-102998} Parameters: [999]  
  14. 2011-01-14 10:38:52,218 DEBUG - {pstm-102998} Types: [java.lang.Integer]  
  15. 中信A00999|1000.0  
  16. ------------------------  
  17. 2078  

 

可以看到每次输出一个用户名如"xiaoye995",然后发出一条select * from card where userId = ?语句。

这就是N+1问题:我们有995个用户 那么就会发出995条上面的语句 另外一条则是查询用户的SQL语句select * from user,

这里的N即为995。可以看到,如果要查询所有用户及其卡信息,这样的效率是很差的,总共花了大约2000多毫秒的时间。

 

解决办法之一,使用懒加载 就是在需要用户卡信息的时候才触发sql调用

[java] view plaincopy
  1. <settings lazyLoadingEnabled="true" useStatementNamespaces="true"/>  


 

解决办法之二,ibatis中已经解决了N+1问题,具体看下面:

修改UserCards.xml文件,关键部分如下:

[java] view plaincopy
  1. <resultMap class="User" id="UserResult" groupBy="id">  
  2.         <result property="id" column="id" nullValue="0"/>  
  3.         <result property="name" column="name"/>  
  4.         <result property="cards" resultMap="UserCardsN1.CardResult" />  
  5. </resultMap>  
  6.   
  7. <select id="getAllUsers" resultMap="UserResult">  
  8.         select * from user a left join card b on a.id = b.userId  
  9. </select>  


 

区别主要体现在groupBy="id"和<result property="cards" resultMap="UserCardsN1.CardResult" />及select用了表连接

控制台输出信息:

[java] view plaincopy
  1. 2011-01-14 11:10:55,156 DEBUG - {conn-100000} Connection  
  2. 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    
  3. 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    
  4. 2011-01-14 11:10:55,703 DEBUG - {pstm-100001} Parameters: []  
  5. 2011-01-14 11:10:55,703 DEBUG - {pstm-100001} Types: []  
  6. xiaoye  
  7. 建行卡|4353.0  
  8. 招行卡|23112.0  
  9. 中信A001|2.0  
  10. ------------------------  
  11. king  
  12. 工行卡|1000.0  
  13. 中信A002|3.0  
  14. ------------------------  
  15. ..........  
  16. .........  
  17. ------------------------  
  18. 766  


可以看到只发出了一条sql语句 输出了所有的信息 大概用时766毫秒

 

结论:

1)懒加载适合不需要一次性取出用户信息及其关联信息的场景

 

2)N+1的解决方法是一次性取出的,适合数据量较少的情况

 

http://xiaoye4188.iteye.com/blog/871050

0 0
原创粉丝点击