mabatis关联查询分页返回数据不对的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/insis_mo/article/details/85163927


今天开发遇到一个问题, mabatis关联查询一对多进行分页的时候返回数据丢失(不算丢失只是数据库是10条,返回少了,是属于机制问题)研究了一个小时才终于找到了原因,现在记录一下,

案例一:产生问题

刚开始分页的时候我的分页语句是这个样子的:

SELECT 
  kiaInfo.InfoId,
  kiaInfo.DocNo,
  kiaInfo.DPID,
  kiaInfo.DocGuid,
  kiaInfo.AssetName,

 pth.id AS id,

  pth.SavePath AS savePath,

  pth.SysNum AS sysNum


FROM
  (SELECT
    t.InfoId,
    t.DocNo,
    t.DPID,
    t.DocGuid,
    t.AssetName,
    kdp.InfoLevel8
  FROM
    (SELECT
      InfoId,
      DocNo,
      DPID,
      DocGuid,
      AssetName
    FROM
      eip_kia_infoasset(这个是主表)

    WHERE 1 = 1
      AND DocGuid = '00'
      OR AssetName LIKE CONCAT(CONCAT('%', '00'), '%')
      AND Hide = 0
    UNION
    ALL
    SELECT
      InfoId,
      DocNo,
      DPID,
      DocGuid,
      AssetName
    FROM
      `eip_kia_infoasset`
    WHERE 1 = 1
      AND DocGuid = '00'
      OR AssetName = '00'
      AND Hide = 1) AS t,
    eip_kia_deptpbiinfo AS kdp(这个是关联表和主表是一对一关系,带出主表归属于水)
  WHERE t.DPID = kdp.DPID  LIMIT 0, 1 ) AS kiaInfo,
  kia_sys_path AS pth
WHERE kiaInfo.DocGuid = pth.DocGuid

LIMIT 0, 10  (一对多关联数据 pth 是多的那张表)

刚开始把分页放在最后面执行,查出数据是10条但是返回到前端时只有6条或者8条,结果发现一对多查询到的数据如果主表一条数据对应了字表多条数据,那么那一条数据会是多条,下面以一个有多条子数据的实验;看结果:

可以看到由于是一对多的关系,这里面时间只是两条数据,返回的时候就是返回两条数据,每个主表数据里面有一个list存放多条子表数据,但是我们查的是四条,返回只有两条这样就不多了吧:所以经过研究发现;

把语句改为下面的样子:limit分页放在子查询里面:

SELECT 
  kiaInfo.InfoId,
  kiaInfo.DocNo,
  kiaInfo.DPID,
  kiaInfo.DocGuid,
  kiaInfo.AssetName,
  pth.SysNum AS sysNum
FROM
  (SELECT
    t.InfoId,
    t.DocNo,
    t.DPID,
    t.DocGuid,
    t.AssetName,
    kdp.InfoLevel8
  FROM
    (SELECT
      InfoId,
      DocNo,
      DPID,
      DocGuid,
      AssetName
    FROM
      eip_kia_infoasset(这个是主表)

    WHERE 1 = 1
      AND DocGuid = '00'
      OR AssetName LIKE CONCAT(CONCAT('%', '00'), '%')
      AND Hide = 0
    UNION
    ALL
    SELECT
      InfoId,
      DocNo,
      DPID,
      DocGuid,
      AssetName
    FROM
      `eip_kia_infoasset`
    WHERE 1 = 1
      AND DocGuid = '00'
      OR AssetName = '00'
      AND Hide = 1) AS t,
    eip_kia_deptpbiinfo AS kdp(这个是关联表和主表是一对一关系,带出主表归属于水)
  WHERE t.DPID = kdp.DPID  LIMIT 0, 1 ) AS kiaInfo,
  kia_sys_path AS pth
WHERE kiaInfo.DocGuid = pth.DocGuid AND (InfoId = '20100815-0605-3596-BE07-304562ADEFD9' OR infoId = '20100815-0605-3596-BE07-304562ADEFD9' 
OR infoId = '20100815-0605-3525-8187-484F908A7CD6')  (一对多关联数据 pth 是多的那张表)

 

可以发现返回的数据: 我们查的只要一条数据,但是他返回的数据是两条,但是主表数据是重复的,对应子表数据是不同的,说明它会把这样的主表相同数据当做是一条数据返回出来 。这样就不会出现返回数据不够条数的问题了:

研究了一下别人的博客是这个样子的;

 

======================================

1,案例一:产生问题

客户(Customer表)和订单(Order表)之间的关系是一对多的关系,即一个用户可以有多个订单。

(1)建立表,并建立一对多关联。

主表


create table Customer(
  id int primary key,
  name varchar(32)
);
从表,customer_id是指向Customer表的主键id的外键

create table `order`(
  id int primary key,
  name varchar(32),
  customer_id int
);
建立主外键关系

alter table `order` add constraint foreign key(customer_id) references customer(id);

向两个表中插入数据:

Customer


Order


(2)建立Javabean,Customer和Order

Customer

public class Customer {
    private Integer id;
    private String name;
    private List<Order> orderList = new ArrayList<Order>();
    ………………getter and setter ……………………
}
Order

public class Order {
    private Integer id;
    private String name;
    private Customer customer;
    ……………………getter and setter ………………………
}
(3)建立Customer的Mapper.xml配置文件CustomerMapper.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="cn.itcast.test1.domain">
    <!-- 建立一对多关系,多表查询解析 -->
    <!-- 配置结果映射,解析结果集,id为定义的名称,指向SQL的返回结果类型 -->
    <resultMap type="cn.itcast.test1.domain.Customer" id="customerTest">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <!-- 一对多配置,建立集合,集合中存放多表对应的对象 -->
        <!-- property是实体类中的集合的名字,ofType是集合中存放的对象的全限定名称 -->
        <collection property="orderList" ofType="cn.itcast.test1.domain.Order">
            <!-- id用于定义主表的唯一标识,是从表识别主表主键的唯一标识,column是结果集对应的字段名 -->
            <id property="id" column="id" />
            <!-- result是普通属性,其他解释同上 -->
            <result property="name" column="name" />
        </collection>
    </resultMap>
    
    <!-- 配置resultType,则每返回一条结果类型,就建立一个对象 -->
    <!-- 配置resultMap,则会将返回的结果集进行解析,对应上面的resultMap解析器,根据定义的字段产生实体对象 -->
    <select id="getinfo" parameterType="int" resultMap="customerTest">
        select * from customer c,`order` o where c.id = o.customer_id and c.id=#{id}
    </select>
</mapper>
(4)在mybatis-config.xml中配置mapper的映射

<mappers>
        <mapper resource="cn/itcast/domain/UserMapper.xml" />
        <mapper resource="cn/itcast/test1/domain/CustomerMapper.xml"/>
    </mappers>

(5)建立测试类,TestCustomer.java,测试查询

public class TestCustomer {
    //MyBatis的Session工厂,底层是jdbc实现,
    private SqlSessionFactory factory;
    @Before
    //在初始化方法中初始化配置文件,并构建工厂
    public void init() throws IOException {
        //读取核心配置文件,这个配置文件配置了数据库的相关信息以及映射的mapper配置文件的读取配置
        String resource = "mybatis-config.xml";
        //通过inputstream将配置文件读取到内存中,用于构建session工厂,
        //Resources类封装的是类加载器,通过类加载器的getResourceAsStream获取输入流。
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //通过build方法构建工厂对象
        factory = new SqlSessionFactoryBuilder().build(inputStream);
    }
    
    @Test
    public void findCustomer() {
        //获取session对象,session对象底层通过jdbcAPI实现,通过反射的方式,获取配置文件配置的类信息以及方法
        SqlSession session = factory.openSession();
        //CURD其实是使用jdbc的ResultSet结果集实现,
        Customer customer = session.selectOne("cn.itcast.test1.domain.getinfo", 1);
        System.out.println(customer);
        //记得释放资源
        session.close();
    }
}

(6)通过junit运行findCustomer,

结果如下:


使用我们的sql语句

select * from customer c,`order` owhere c.id = o.customer_id and c.id=1;

在数据库查询,结果是:


案例一的问题:

存入集合的对象应该是order对象,但是里面的数据却是customer的数据,为什么呢?

这涉及到ResultMap的实现机制。ResultMap是把实体类也就是我们的javabean的属性与返回结果集的字段进行匹配,如果匹配,则建立对象,并通过设个对象的这个属性的set方法将值设置进去。

详解:

ResultMap接收的结果集信息,当前的返回的结果集信息也就是上面通过sql语句在数据库查询出来的图上所示的信息。ResultMap会从返回的结果集逐条读取记录(每一行数据),当前获取第一行数据:

每次获取一行后,又会逐列读取信息。读取到id字段,值为1。根据配置文件

<resultMaptype="cn.itcast.test1.domain.Customer"id="customerTest">,发现这是一个Customer对象,就new一个Customer对象,然后再将结果集中的字段值id与column属性值逐一匹配,<id property="id"column="id" />匹配,就通过Customer的set方法将id的值1设置进id属性;然后对结果集这一列的匹配并没有结束,ResultMap会继续拿着这个id字段向下匹配,下面一个配置是<resultproperty="name" column="name" />,不匹配,则不做任何动作;继续向下匹配,发现<collection property="orderList"ofType="cn.itcast.test1.domain.Order">建立了一对多关系,实体是Order,于是就建立了一个Order对象,然后继续拿着id字段向下匹配,接着发现在Order中也有id,<id property="id"column="id" />匹配了,再用Order的serId方法将id字段的值1设置进去,所以现在List集合中的一个Order对象的id值是1。继续拿着id向下匹配,<resultproperty="name" column="name" />,不匹配,且配置结束,则结束这个id字段的配置。

进入下一个name字段的配置,与id一样,进去匹配,当前记录Customer的name还没有通过set方法赋值,就set进去。后面的Order也是一样。

至此,结果集中建立的对象包括:一个id=1,name=小强的Customer对象,一个id=1,name=小强的Order对象,并且Order对象存储在Customer对象的List集合中。

进行下一个结果集字段的匹配,下一个也是id,同样进入配置文件逐一匹配,需要注意的是,如果之前有一个与当前字段id相同的字段,在同一条记录中对同一个对象做了set操作,那么当前这个id字段将不会再对当前对象做set操作。

也就是说,当前的字段是id,这个id在配置文件中匹配的时候,遍历到column为id的列,就要通过创建的当前对象Customer的setId()方法设值,但是,在同一条记录中,之前有一个字段相同(同为id)的字段已经通过这个Customer对象的serId()方法设值,那么当前这个id字段就不会再set赋值,所以,这个id匹配过后,Customer的id值不变,Order的值也不变,后面的name字段同理,所以,集合中的Order的id是1,name是小强。

另外,执行完毕后,集合里面只有一条记录,而查询出来的是两条记录。因为查询的是Customer,而且是通过selectOne查询的,MyBatis根据返回的记录数判断,通过selectOne查询,返回必须是没有或者只有一条,如果超过一条,就会报错。但是通过selectList查询,就不会出现这个问题了。

如果查询的是Order,因为order表中有两条记录,所以查询结果是两条,如果通过selectOne查询,会报异常,必须通过selectList查询。

2,案例二:解决了案例一中的问题

为了解决案例一中的问题,需要修改一下我们的sql语句。通过上面的解析可以知道,如果字段名重复,则后面的字段不会再通过set方法设值。所以解决办法就是如果出现重复字段,就换个名称,让所有的字段不一样即可。

具体方案:

查询语句中,为字段建立别名,通过别名返回结果集(为重复的字段建立别名)。只需要修改CustomerMapper.xml文件即可。

具体如下:

<mapper namespace="cn.itcast.test1.domain">
    <resultMap type="cn.itcast.test1.domain.Customer" id="customerTest">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <collection property="orderList" ofType="cn.itcast.test1.domain.Order">
            <id property="id" column="o_id" />
            <result property="name" column="o_name" />
        </collection>
    </resultMap>
    <select id="getinfo" parameterType="int" resultMap="customerTest">
        select c.*,o.id as o_id,o.name as o_name from customer c,`order` o where c.id = o.customer_id and c.id=#{id}
    </select>
</mapper>

这样所有的字段就都不一样了,可以获取正确结果,通过junit执行,可以获取如下控制台信息:


可以看到,orderList中有两条数据,且正是我们设置到数据库的数据。

文章只是个人观点,有什么错误欢迎指出,谢谢

猜你喜欢

转载自blog.csdn.net/insis_mo/article/details/85163927