在mybatis中使用一对多时,在collection中使用select和不使用select两次返回的结果集不同。
IUserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.my.mapper.IUserMapper">
<resultMap id="userMap" type="user">
<id property="user_id" column="user_id"></id>
<result property="user_name" column="user_name"></result>
<!--从这里开始的两种方式-->
<collection property="books" column="user_id" ofType="book" select="com.my.mapper.IBookMapper.queryByUserId"></collection>
<!--<collection property="books" column="user_id" ofType="book" >
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="author" column="author"></result>
<result property="price" column="price"></result>
<result property="user_id" column="user_id"></result>
</collection>-->
<!--结束-->
</resultMap>
<select id="queryAll" resultMap="userMap" >
select a.user_name,b.* from user a left join book b on a.user_id = b.user_id
</select>
</mapper>
IBookMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.my.mapper.IBookMapper">
<select id="queryByUserId" parameterType="Integer" resultType="book">
select * from book where user_id = #{userid}
</select>
</mapper>
TestUser.class
package com.my.test;
import com.my.bean.User;
import com.my.mapper.IUserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class TestUser {
private InputStream in;
private SqlSession sqlSession;
private IUserMapper userMapper;
@Before
public void init(){
try {
in = Resources.getResourceAsStream("Mybatis_config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
sqlSession = factory.openSession();
userMapper = sqlSession.getMapper(IUserMapper.class);
} catch (IOException e) {
e.printStackTrace();
}
}
@After
public void destroy(){
try {
sqlSession.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testQueryAll(){
List<User> users = userMapper.queryAll();
System.out.println(users.size());
for (User user :
users) {
System.out.println(user);//这里两个表虽然是关联的,但是打印对象是分开的
System.out.println(user.getBooks());
}
}
}
先用collection中的select属性,输出:
现在用另一种方式运行,就是不用select的方式,输出:
结果发现两次运行的结果不一样,但是在一对一的关联关系中,这两种方式的运行结果一样。
这到底是怎么回事呢?
原来是MyBatis为了降低内存开销,采用ResultHandler逐行读取的JDBC ResultSet结果集的,这就会造成MyBatis在结果行返回的时候无法判断以后的是否还会有这个id的行返回,所以它采用了一个方法来判断当前id的结果行是否已经读取完成,从而将其加入结果集List,这个方法是:
1. 读取当前行记录A,将A加入自定义Cache类,同时读取下一行记录B。
2. 使用下一行记录B的id列和值为key(这个key由resultMap的标签列定义)去Cache类里获取记录。
3.假如使用B的key不能够获取到记录,则说明B的id与A不同,那么A将被加入到List。
4.假如使用B的key可以获取到记录,说明A与B的id相同,则会将A与B合并(相当于将两个goodsImg合并到一个List中,而goods本身并不会增加)。
5. 将B定为当前行,同时读取下一行C,重复1-5,直到没有下一行记录。
6. 当没有下一行记录的时候,将最后一个合并的resultMap对应的java对象加入到List(最后一个被合并goodsImg的Goods)。
所以,
a. 当结果行是乱序的,例如BBAB这样的顺序,在记录行A遇到一个id不同的曾经出现过的记录行B时, A将不会被加入到List里(因为Cache里已经存在B的id为key的cahce了)。
b. 当结果是顺序时,则结果集不会有任何问题,因为 记录行 A 不可能 遇到一个曾经出现过的 记录行B, 所以记录行A不会被忽略,每次遇到新行B时,都不可能使用B的key去Cache里取到值,所以A必然可以被加入到List。
所以,使用一对多或者是多对多的时候,最好是手动配置collection,而不要去使用select。
参考自:http://www.360doc.com/content/14/1205/16/15272201_430624495.shtml