先说下问题产生的背景:

  最近在做一个用到MyBatis的项目,其中有个业务涉及到关联查询,我是将两个查询分开来写的,即嵌套查询,个人感觉这样更方便重用;

  关联的查询使用到了动态sql,在执行查询时就出现了如下错误:Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'id' in 'class java.lang.Integer'

  因为出现了这个问题,在解决的时候一连串又发现了一些其它新的问题,比如关联查询时的效率问题,以及映射。通过百度,看文档基本解决,做个笔记。

现在简单的用学生和年级两个对象来做实验,学生有一个年级属性,年级有一个学生集合属性。

先把一些基础的代码贴出来:

学生实体:

 package com.lizhou.entity;

 import java.io.Serializable;

 /**
* 学生
* @author bojiangzhou
* @date 2016年3月30日
*/
public class Student implements Serializable { private static final long serialVersionUID = -4165939686905301187L; private Integer id;
//学生姓名
private String name;
//年龄
private int age;
//学生所属年级
private Grade grade; //getter/setter... }

年级实体:

 package com.lizhou.entity;

 import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; /**
* 年级
* @author bojiangzhou
* @date 2016年3月30日
*/
public class Grade implements Serializable { private static final long serialVersionUID = -3469035301959740223L; private Integer id;
//年级名称
private String name;
//年级下的学生集合
private List<Student> studentList = new ArrayList<Student>(); //getter/setter... }

学生数据接口:

 package com.lizhou.dao;

 import java.util.List;

 import com.lizhou.entity.Student;

 /**
* 数据层
* @author bojiangzhou
* @date 2016年3月30日
*/
public interface StudentDao { /**
* 获取学生信息
* @param student
* @return
*/
Student getStudent(Student student); /**
* 获取学生集合
* @param student
* @return
*/
List<Student> getStudentList(Student student);
}

年级数据接口:

 package com.lizhou.dao;

 import com.lizhou.entity.Grade;

 /**
* 数据层
* @author bojiangzhou
* @date 2016年3月30日
*/
public interface GradeDao { /**
* 获取某个年级
* @param grade
* @return
*/
Grade getGrade(Grade grade); /**
* 获取年级集合
* @param grade
* @return
*/
Grade getGradeList(Grade grade); }

加载配置文件获取SqlSession工具类:

 package com.lizhou.tools;

 import java.io.IOException;
import java.io.InputStream; import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder; /**
* 加载配置文件获取SqlSession工具类
* @author bojiangzhou
* @date 2016年3月30日
*/
public class SqlSessionFactoryTool { private static SqlSessionFactory sqlSessionFactory; public static SqlSessionFactory getSqlSessionFactory(){
if(sqlSessionFactory == null){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
return sqlSessionFactory;
} public static SqlSession openSession(){
return getSqlSessionFactory().openSession();
} }

测试类:

 package com.lizhou.service;

 import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test; import com.lizhou.entity.Grade;
import com.lizhou.entity.Student;
import com.lizhou.dao.GradeDao;
import com.lizhou.dao.StudentDao;
import com.lizhou.tools.SqlSessionFactoryTool; /**
* 测试类
* @author bojiangzhou
* @date 2016年3月30日
*/
public class TestService { private SqlSession sqlSession = null; private StudentDao studentDao = null;
private GradeDao gradeDao = null; /**
* 测试方法前调用
* @throws Exception
*/
@Before
public void setUp() throws Exception {
sqlSession = SqlSessionFactoryTool.openSession();
studentDao = sqlSession.getMapper(StudentDao.class);
gradeDao = sqlSession.getMapper(GradeDao.class);
} /**
* 测试方法后调用
* @throws Exception
*/
@After
public void tearDown() throws Exception {
sqlSession.close();
} /**
* 测试方法
*/
@Test
public void test() { } }

**************************************************************************************************************************

一、mybatis嵌套查询(即SQL语句分离的)时报类似于There is no getter for property named 'id' in 'class java.lang.Integer' 的错误

先看看产生问题的与学生和年级对应的映射配置:

StudentMapper.xml:这里是通过association查询年级的(第10行代码)。

 <?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.lizhou.dao.StudentDao"> <resultMap type="Student" id="studentResult">
<id property="id" column="id"/> <association property="grade" column="gradeId" select="com.lizhou.dao.GradeDao.getGrade"></association>
</resultMap> <select id="getStudent" resultMap="studentResult">
SELECT * FROM student
<where>
<if test="id != null">
AND id=#{id}
</if>
</where>
</select> </mapper>

GradeMapper.xml:动态Sql,本意是想如果有一个查询所有年级和根据id来查询年级的业务时就可以使用同一个查询了。

注意这里年级还有一个学生集合没有映射到gradeResult里面,如果映射了会循环查询最终导致栈溢出的,后面再说。

 <?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.lizhou.dao.GradeDao"> <resultMap type="Grade" id="gradeResult">
<id property="id" column="id"/> </resultMap> <select id="getGrade" parameterType="Grade" resultMap="gradeResult">
SELECT * FROM grade
<where>
<if test="id != null">
AND id=#{id}
</if>
</where>
</select> </mapper>

测试代码:

 @Test
public void test() {
Student student = new Student();
student.setId(1);
//根据id查询学生
student = studentDao.getStudent(student); System.out.println(student);
}

我的本意是各自的业务分开来写,我要查询学生时就调用getStudent,要查询年级时就调用getGrade,而我要查询学生的同时还要查询其关联的年级,这样我也可以调用getGrade来完成,这样能很好的实现重用。

但结果并不是这样的,报错:Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'id' in 'class java.lang.Integer'

刚开始真的没明白怎么回事,在Integer中找不到id属性??

我将GradeMapper.xml中的select语句改成如下时,就可以:

 <mapper namespace="com.lizhou.dao.GradeDao">

     <resultMap type="Grade" id="gradeResult">
<id property="id" column="id"/> </resultMap> <select id="getGrade" parameterType="Grade" resultMap="gradeResult">
SELECT * FROM grade WHERE id=#{id}
</select> </mapper>

百度了很久,原因是在查询到一条记录的时候就会将id注入,而且不管#{id}的名称,就是说你随便写也能查出结果来,第9行代码:

 <mapper namespace="com.lizhou.dao.GradeDao">

     <resultMap type="Grade" id="gradeResult">
<id property="id" column="id"/> </resultMap> <select id="getGrade" parameterType="Grade" resultMap="gradeResult">
SELECT * FROM grade WHERE id=#{id-xxxxxx}
</select> </mapper>

结论》》联合查询时:

不管输入参数名称是什么,mybatis最终会执行:

效果为:select * from grade where id =resultSet.getInt("id");

所以我之前用动态sql时去判断id,其实是不存在id的,所以导致那样的错误!!

这里参考原文解释:MyBatis中Association联合select使用

------------------------------------------------------------------------------------------------------------------------------------------------------------------

二、嵌套结果查询导致的循环查询栈溢出

如果这样使用联合查询,其实还存在另外一个问题:学生下有年级,年级下有学生集合,这样查询下去只有栈溢出了....

看代码:

StudentMapper.xml:年级也有映射

 <mapper namespace="com.lizhou.dao.StudentDao">

     <resultMap type="Student" id="studentResult">
<id property="id" column="id"/> <association property="grade" column="gradeId" select="com.lizhou.dao.GradeDao.getGrade"></association>
</resultMap> <select id="getStudent" resultMap="studentResult">
SELECT * FROM student WHERE gradeId=#{gradeId}
</select> </mapper>

GradeMapper.xml:注意此时映射了学生集合的,第6行代码

 <mapper namespace="com.lizhou.dao.GradeDao">

     <resultMap type="Grade" id="gradeResult">
<id property="id" column="id"/> <collection property="studentList" column="id" ofType="Student" select="com.lizhou.dao.StudentDao.getStudent"></collection>
</resultMap> <select id="getGrade" parameterType="Grade" resultMap="gradeResult">
SELECT * FROM grade WHERE id=#{id}
</select> </mapper>

不管是查询学生还是年级,结果报错:java.lang.StackOverflowError

这样看来,最终都还是要分开写,就是说我想查询年级和查询学生时联合查询年级要写两个select才行,并没能达到重用的目的。

但是相信程序员都是有强迫症的,这样有点不爽,我就又百度,看能不能很好的重用,结果是没有百度到。

但是在看文档的时候,发现了另一个问题:联合查询分开写查询语句是很消耗性能的,明明可以只用执行一次查询,却执行了两次甚至更多次。

文档是这样说的:这种方式很简单,但是对于大型数据集合和列表将不会表现很好。问题就是我们熟知的“N+1 查询问题”。

N+1 查询问题可以是这样引起的:

1. 你执行了一个单独的 SQL 语句来获取结果列表(就是“+1”)

2.对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是“N”)

这个问题会导致成百上千的 SQL 语句被执行。这通常不是期望的。

------------------------------------------------------------------------------------------------------------------------------------------------------------------

三、关联的嵌套结果

上面的叫做[关联的嵌套查询]

所以自然有另一种方法----[关联的嵌套结果](文档上这样说的):联合两张表在一起,代替了执行一个分离的语句。

看代码:

 <mapper namespace="com.lizhou.dao.StudentDao">

     <resultMap type="Student" id="studentResult">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<association property="grade" column="gradeId" javaType="Grade">
<id property="id" column="id"/>
<result property="name" column="name"/>
</association>
</resultMap> <select id="getStudent" resultMap="studentResult">
SELECT * FROM student LEFT OUTER JOIN grade ON grade.id=student.gradeId WHERE student.id=#{id}
</select> </mapper>

映射的时候是将年级和学生一起映射的,这个就叫做嵌套结果映射。然后是sql语句,使用的是联合查询。

------------------------------------------------------------------------------------------------------------------------------------------------------------------

四、映射、效率

这里又遇到了几个问题:

1.在之前写resultMap映射时,我是一般不爱将属性映射写出来的,因为mybatis会自动映射的,但是这里不行了。

在嵌套结果映射时,哪个属性没有映射那么这个值就是空的,查询出来的结果不会注入到对象中。所以必须将所有属性都显示映射出来。

2.先看看映射都OK时输出的结果:Student [id=2, name=罗若亚卓落, age=22, grade=Grade [id=2, name=罗若亚卓落, studentList=[]]]

输出结果中,grade中的数据和student中的一样了....因为Student的id和name跟Grade的id和name属性名一样。

mybatis其实就是使用resultSet.getString("name")这样的方式来获取值的,所以联合查询时两张表的字段名不能重复。

或者可以在配置文件中配置好映射和sql语句。如下:注意第8、9行代码,重新配置column;以及sql语句。但是如果属性过多,sql代码可能就有点长了,所以最好还是保持字段名不一样吧。

 <mapper namespace="com.lizhou.dao.StudentDao">

     <resultMap type="Student" id="studentResult">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<association property="grade" column="gradeId" javaType="Grade">
<id property="id" column="grade_id"/>
<result property="name" column="grade_name"/>
</association>
</resultMap> <select id="getStudent" resultMap="studentResult">
SELECT
s.id,
s.name,
s.age,
g.id as grade_id,
g.name as grade_name
FROM student s LEFT OUTER JOIN grade g ON g.id=s.gradeId
WHERE s.id=#{id}
</select> </mapper>

3.最后是在文档中看到的一个比较重要的问题,最好是要显示映射出id。直接复制原话:

非常重要:在嵌套结果映射中 id 元素扮演了非常重要的角色。应该通常指定一个或多个属性,它们可以用来唯一标识结果。

实际上就是如果你不使用它(id 元素) ,但是会产生一个严重的性能问题,不过 MyBatis 仍然可以正常工作。

选择的属性越少越好,它们可以唯一地标识结果。主键就是一个显而易见的选择(即便是联合主键)。

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

刚学完mybatis不久,现在正在做的这个项目(视频教程,当练手)本来是struts+spring+ibatis的,我想着ibatis也算是过去式了,恰好前阵刚学了mybatis还没实战过,就把ibatis换成了mybatis。

不过只是知道怎么使用,很多细节性的东西还没深入了解。所以这篇博客也只是开发中遇到的一些问题解决后做个笔记,大部分都属于个人理解,有些可能不全面,希望有不同见解的朋友一起交流~~O(∩_∩)O~~

MyBatis关联查询 (association) 时遇到的某些问题/mybatis映射的更多相关文章

  1. mybatis 关联查询 association

    <resultMap id="DutyPersonAndFileAndScoreMap" type="com.cares.asis.duty.entity.Duty ...

  2. Mybatis关联查询<association> 和 <collection>

    一.背景 1.在系统中一个用户存在多个角色,那么如何在查询用户的信息时同时把他的角色信息查询出来啦? 2.用户pojo: public class SysUser { private Long id; ...

  3. MyBatis关联查询、多条件查询

    MyBatis关联查询.多条件查询 1.一对一查询 任务需求; 根据班级的信息查询出教师的相关信息 1.数据库表的设计 班级表: 教师表: 2.实体类的设计 班级表: public class Cla ...

  4. mybatis关联查询基础----高级映射

    本文链接地址:mybatis关联查询基础----高级映射(一对一,一对多,多对多) 前言: 今日在工作中遇到了一个一对多分页查询的问题,主表一条记录对应关联表四条记录,关联分页查询后每页只显示三条记录 ...

  5. Mybatis关联查询和数据库不一致问题分析与解决

    Mybatis关联查询和数据库不一致问题分析与解决 本文的前提是,确定sql语句没有问题,确定在数据库中使用sql和项目中结果不一致. 在使用SpringMVC+Mybatis做多表关联时候,发现也不 ...

  6. MyBatis基础:MyBatis关联查询(4)

    1. MyBatis关联查询简介 MyBatis中级联分为3中:association.collection及discriminator. ◊ association:一对一关联 ◊ collecti ...

  7. MyBatis关联查询,一对多关联查询

    实体关系图,一个国家对应多个城市 一对多关联查询可用三种方式实现: 单步查询,利用collection标签为级联属性赋值: 分步查询: 利用association标签进行分步查询: 利用collect ...

  8. mybatis 关联查询实现一对多

    场景:最近接到一个项目是查询管理人集合  同时每一个管理人还存在多个出资人   要查询一个管理人列表  每个管理人又包含了出资人列表 采用mybatis关联查询实现返回数据. 实现方式: 1 .在实体 ...

  9. MyBatis关联查询和懒加载错误

    MyBatis关联查询和懒加载错误 今天在写项目时遇到了个BUG.先说一下背景,前端请求更新生产订单状态,后端从前端接收到生产订单ID进行查询,然后就有问题了. 先看控制台报错: org.apache ...

随机推荐

  1. 从零开始学Linux[一]:基本命令:系统信息、目录、文件、文件编辑

    摘要:linux基础学习:系统信息.目录.文件查找.文件操作.查看文件内容及大小.软链接.VIM使用. 现在Linux的使用非常普遍.对于一个小白来说,满屏幕的字母,看起来就是一头雾水~   目前由于 ...

  2. javascript jsscript .js xml html json soap

    javascript ecma标准的脚本语言用于 jsscript 微软标准的一种脚本语言 .js javascript或jsscript保存成文件的形式可用于在html里重复引用 jsscript只 ...

  3. 0518 Scrum项目5.0

    一,组员任务完成情况 首页设计初步完成但是需要优化界面,只能简单的输出信息和在首页进行登录.界面极其简单. 鸡汤版面设计有困难,问题在于用何种形式来管理用户的数据上传,但是经过小组间的讨论确定设计方向 ...

  4. HDU 2571 命运

    命运 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submissi ...

  5. PHP安全性

    一.防sql注入 用户通过输入完整的字符,来和sql语句拼接成带有破坏性的sql语句,服务器执行该语句,造成破坏. 1使用mysql_real_escape_string()过滤数据,该方法在未来版本 ...

  6. Excel应该这么玩——2、命名列:消除地址引用

    命名列:通过名称引用列,让公式更容易理解. 下面继续举上次的栗子. 1.历史遗留问题 之前虽然把数字编成了命名单元格,但其中还是有单元格地址B2.C2之类,要理解公式需要找到对应的列标题. 特别是像下 ...

  7. create thread的时候发生core dump

    #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h& ...

  8. mysql 配置文件 value

    在xml配置文件中配置数据库utl时,要使用&的转义字符也就是& 例如:<property name="url" value="jdbc:mysql ...

  9. Hibernate的关联映射——双向1-N关联

    Hibernate的关联映射--双向1-N关联 对于1-N的关联,Hibernate推荐使用双向关联,而且不要让1的一端控制关联关系,而是用N的一端控制关联关系.双线的1-N关联和N-1关联是两种相同 ...

  10. 单调队列 hdu2823

    Sliding Window Time Limit: 12000MS   Memory Limit: 65536K Total Submissions: 48608   Accepted: 14047 ...