mybatis的sql传参与返回结果

在mybatis映射文件每编写一条sql语句,我们都必须关注sql的传参以及返回结果集映射。传入参数是为了动态的封装执行的sql语句,而结果集映射就是告诉mybatis如何将从数据库查询到的记录封装成我们需要的结果类型。例如通过id查询user对象,我们就需要将id作为参数传递进去,然后将查询记录封装到一个user对象中,要实现这个功能,我们就必须知道参数id是如何注入sql以及查询结果记录如何封装到user对象里面的。

传参

使用的实体类如下:

package com.dahuici.zyb.entity;
import lombok.Data;
@Data
public class User {
    private String name;
    private Integer age;
    private Integer userid;
}

1)传入基本数据类型参数

传入单个的基本数据类型,以String为例

对于String参数,首先在mapper接口的方法传入参数

User getUserByName(String name);

然后在指定select标签的parameterType="string"(这里指定为string数据类型,与绑定的mapper接口方法的参数保持一致。这里使用了mybatis的系统别名,对于系统别名的数据字典可自行百度),指定返回类型是user实体类,这里暂时不管查询结果记录与user实体类是如何建立联系的。

<select id="getUserByName" parameterType="string" resultType="com.dahuici.zyb.entity.User">
       select * from user where name = #{name}
 </select>

这里需要注意的就是,如果只传入一个参数,并且该参数是基本数据类型,那么当使用#{}对参数值进行注入时,不管#{}里面是什么值,mybatis都会将唯一的参数注入进去,意思就是#{}里面的值不用和mapper接口方法的参数变量名相同,当然,为了方便看出传递关系,一般都将两个值定义成相同的。
但是,如果你需要用传入的参数进行条件判断,即在mybatis的动态sql中引用参数,那么就必须使用@Param("")定义传入参数的引用名,如下

User getUserByName(@Param("name")String name);

当使用了@Param进行定义过后,就可以在sql中通过@Param定义的引用名name使用传入参数的值了,综合来说,建议只要传入基本数据类型的参数,都使用@Param定义引用名。
对于其他的基本数据类型的单个参数传递,与String类型的使用方式基本相同,不在赘述,以后如果使用其他的基本数据类型存在不同之处,再进行补充。

多个基本数据类型,以传入一个String与一个Integer类型参数为例

编写mapper接口的方法,由于是多个参数,所以必须使用@Param定义引用名。

User getUserByString(@Param("name")String name,@Param("age")Integer age);

给mapper接口方法绑定sql。由于是多个参数,参数类型不一定相同,这里就不需要parameterType指定参数类型了。

<select id="getUserByString" resultType="com.dahuici.zyb.entity.User">
       select * from user where name = #{name} and age = #{age}
</select>

2)传入实体类参数

当在mapper接口方法中,传递实体类时,方法如下

User getUserByUser(User user);

由于参数是实体类,在映射文件的select标签的sql里面,通过 #{实体类属性名} 的方式就可以使用该属性的值了,在动态sql里面也可以通过属性名直接使用该属性的值。

<select id="getUserByUser" parameterType="com.dahuici.zyb.entity.User" resultType="com.dahuici.zyb.entity.User">
       select * from user 
      <if test="name != null">
           where name = #{name} and age = #{age}
      </if> 
</select>

这里使用if只是简单的做了一个非空的判断。

也可以使用@Param进行定义参数,如下

User getUserByUser(@Param("user")User user);

使用时在前面加上@Param定义的值就行了。使用如下

<select id="getUserByUser" parameterType="com.dahuici.zyb.entity.User" resultType="com.dahuici.zyb.entity.User">
       select * from user 
      <if test="user.name != null">
           where name = #{user.name} and age = #{user.age}
      </if> 
</select>

与多个基本数据类型同理,当传入多个实体类对象时,必须用@Param指定引用别名并不能使用parameterType指定传入参数类型。

3)传入一个Map

需要提一下的是,当传入多个参数时,其实mybatis都会把这些参数封装到一个map中,通过@Param指定key,value表示传入的具体参数值,所以当需要传入多个参数时,其实都可以用一个Map代替。

首先定义mapper接口方法,定义map时,由于value的类型不一定相同,这里就定义成Object对象。

User getUserByMap(Map<String, Object> map);

给接口方法绑定sql,如果map里面value是基本数据类型,那么就直接通过key注入,如果value是一个对象,那么就使用 key.属性名 的方式注入。

<select id="getUserByMap" parameterType="map" resultType="com.dahuici.zyb.entity.User">
       select * from user 
      <if test="name != null">
           where name = #{name} and age = #{user.age}
      </if> 
    </select>

执行测试

@Test
    public void getUserByMap() {
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("name", "zyb");
        User user1 = new User();
        user1.setAge(22);
        map.put("age", 22);
        map.put("user", user1);
        User user = mapper.getUserByMap(map);
        System.out.println(user);
        
    }

目前觉得,对于参数的传递,个人觉得以上方式就够用了,如果遇见新的需求再进行补充。另外,还可以传入集合对象,里面存储了很多实体,用于批量插入,需要动态sql,所以在动态sql里面详解。

结果集映射

返回一个实体对象

mapper接口方法

User getUserById(Integer id);

给接口方法绑定sql

 <!-- 查询 -->
    <select id="getUserById" parameterType="int" resultType="com.dahuici.zyb.entity.User">
       select * from user where userid = #{id}
    </select>

测试代码和执行结果

@Test
    public void JunitTest() {
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //User user1 = (User)sqlSession.selectOne("getUserById", 2);
        //System.out.println(user1);
        User user = mapper.getUserById(2);
        System.out.println(user);
    }
image.png

可以看出,接口方法返回User对象然后在<select>标签中定义resultType="com.dahuici.zyb.entity.User",mybatis就会把返回的记录自动封装成User对象返回。这里没有对mysql查询的结果记录与实体类的属性建立任何的关联,但是当没有定义关联时,mybatis会默认将mybatis查询出的结果字段与其同名的实体类属性建立关联,进行实体类属性的封装,所以这里返回的user对象的各个属性才有值。实体类与数据库表的对应关系如下:


image.png
image.png

除了使用resultType建立默认的映射关系外,还可以使用resultMap属性结合<resultMap>标签手动的建立实体类属性与mysql结果集字段映射关系。主要代码如下:

<!-- 查询 -->
    <select id="getUserById" parameterType="int" resultMap="usermap">
       select * from user where userid = #{id}
    </select>
    
    <resultMap type="com.dahuici.zyb.entity.User" id="usermap">
       <id column="userid" property="userid"/>
       <result column="name" property="name"/>
       <result column="age" property="age"/>
    </resultMap>

这里首先就是要定义一个<resultMap>标签,通过type属性指定实体类,id定义该<resultMap>的唯一标识,然后通过<id>子标签定义主键字段与实体类属性的映射关系,<result>子标签定义普通字段与实体类属性的映射关系,这里只需记住的是column的值为数据库表的字段名,property的值为实体类的属性名。当定义好后,将<select>的resultMap属性值设置为<resultMap>标签的id值就可以使用该映射关系,查询的结果将使用该映射关系对实体类进行封装。<resultMap>标签的作用就是建立数据库查询结果与实体类的映射关系,并且只要定义好后,可以被任何有需求的<select>标签使用。

一对一关系

简单描述,一般在大学里面,一个学生都只有一个有效的一卡通,这里就定义一个学生与一卡通的实体类,代码和数据库表结构如下。
student类

@Data
public class Student {
    private Integer studentid;
    private String name;
    private Integer age;
    private Card card;
}

card类

@Data
public class Card { 
    private Integer cardid;
    private String schooolname;
}

数据库表结构

image.png

image.png

需求描述:获取id为1的学生以及它的一卡通信息。

方式一 引用其他select标签。

首先在mapper接口中定义两个方法

       Student getStudentById(Integer studentid);
    Card getCardById(Integer cardid);

给接口方法绑定sql

    <select id="getStudentById" parameterType="int" resultMap="studentmap">
       select * from student where studentid = #{studentid}
    </select>

    <select id="getCardById" parameterType="int" resultMap="cardmap">
        select * from card where cardid = #{cardid}
    </select>

   <resultMap type="com.dahuici.zyb.entity.Student" id="studentmap">
       <id column="studentid" property="studentid"/>
       <result column="name" property="name"/>
       <result column="age" property="age"/>
       <association property="card" column="cardid" select="getCardById"></association>
    </resultMap>

    <resultMap type="com.dahuici.zyb.entity.Card" id="cardmap">
       <id column="cardid" property="cardid"/>
       <result column="schooolname" property="schooolname"/>
    </resultMap>

大概的sql之间的联系关系如下:


image.png

主要介绍一下<resultMap>的子标签<association>,使用的代码如下:

<association property="card" column="cardid" select="getCardById"></association>

<association>就是为了用于绑定实体类的属性中有另一个实体类的情况,这里是为了封装学生对象(Student)的一卡通对象属性(Card),通过property指定一卡通对象对应与学生对象的名为card的属性,并引用id为getCardById的<select>标签并指定将id为getStudentById的<select>的查询结果的cardid字段值作为参数传递过去。所以,这种方式执行了两条sql,执行代码与输出结果如下:

@Test
    public void getStudentById() {
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Student student = mapper.getStudentById(1);
        System.out.println(student);
    }
image.png

突然灵机一动,当获取多个学生及其一卡通信息时,sql会怎样执行。结果和其原理差不多,每封装一个学生对象,都会执行一卡通的查询sql,严重影响性能,不推荐获取集合时使用该方式,测试的sql执行情况如下。

Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 1301664418.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d95d2a2]
==>  Preparing: select * from student 
==> Parameters: 
<==    Columns: studentid, name, age, cardid
<==        Row: 1, zyb, 23, 1
====>  Preparing: select * from card where cardid = ? 
====> Parameters: 1(Integer)
<====    Columns: cardid, schooolname
<====        Row: 1, *****大学
<====      Total: 1
<==        Row: 2, 张三, 33, 2
====>  Preparing: select * from card where cardid = ? 
====> Parameters: 2(Integer)
<====    Columns: cardid, schooolname
<====        Row: 2, *****大学1
<====      Total: 1
<==        Row: 3, 李四, 34, 3
====>  Preparing: select * from card where cardid = ? 
====> Parameters: 3(Integer)
<====    Columns: cardid, schooolname
<====        Row: 3, *****大学2
<====      Total: 1
<==      Total: 3
[Student(studentid=1, name=zyb, age=23, card=Card(cardid=1, schooolname=*****大学)), Student(studentid=2, name=张三, age=33, 
card=Card(cardid=2, schooolname=*****大学1)), Student(studentid=3, name=李四, age=34, card=Card(cardid=3, schooolname=*****大
学2))]

方式二 使用sql连接的方式

接口方法

Student getStudentById(Integer studentid);

绑定给方法绑定sql

   <select id="getStudentById" parameterType="int" resultMap="studentmap">
       select * from student inner join card on card.cardid = student.studentid  where studentid = #{studentid}
    </select>
    
    <resultMap type="com.dahuici.zyb.entity.Student" id="studentmap">
       <id column="studentid" property="studentid"/>
       <result column="name" property="name"/>
       <result column="age" property="age"/>
       <association property="card" resultMap="cardmap"></association>
    </resultMap>
    <resultMap type="com.dahuici.zyb.entity.Card" id="cardmap">
       <id column="cardid" property="cardid"/>
       <result column="schooolname" property="schooolname"/>
    </resultMap>

该方式使用sql的连接查询将结果都查询出来,然后将属于Card类的属性与对应的字段建立对应的映射关系就可以实现一对一关系了,只要使用<association>标签将Student的card属性区别出来就可以了,这里需要注意的时sql查询出来的结果不能有重复的字段,有重复的字段就不好建立映射关系,会直接取第一个出现该名称的字段。

如果需要查询Student集合,那么映射关系保持不变,只需将mapper接口的对应方法(这里是getStudentById方法)的返回值类型变为 List<实体类>,然后保证执行sql查询出来的结果是多条记录就可以了,mybatis会自动通过映射关系将查询出来的多条记录封装成一个个实体类并存入list。

一对多关系

需要两个实体类,一个Student,一个Teacher类,显然,一个语文老师有多个学生(我这里的设定为一个学生只有一个老师)
相关实体类以及数据库表结构

package com.dahuici.zyb.entity;

import java.util.List;

import lombok.Data;

@Data
public class Teacher {
    private Integer teacherid;
    private String name;
    private Integer age;
    private List<Student> studentlist;
}
package com.dahuici.zyb.entity;

import lombok.Data;

@Data
public class Student {
    
    private Integer studentid;
    private String name;
    private Integer age;
    private Card card;
    private Teacher teacher;
    
}

image.png
image.png

需求,查出id为1的老师的和其所有的学生以及学生的一卡通信息
接口方法

Teacher getTeacherById(Integer id);
<select id="getTeacherById" parameterType="int" resultMap="teachermap">
       select 
       teacher.teacherid as teacherid,teacher.name as tname, 
       teacher.age as tage,studentid,student.name as sname,
       student.age as sage,card.cardid as cardid,schooolname 
       from 
       teacher 
       inner join 
       student 
       on 
       teacher.teacherid = student.teacherid 
       inner join 
       card 
       on 
       card.cardid = student.studentid
       where teacher.teacherid = #{id}
    </select>
    <resultMap type="com.dahuici.zyb.entity.Teacher" id="teachermap">
       <id column="teacherid" property="teacherid"/>
       <result column="tname" property="name"/>
       <result column="tage" property="age"/>
       <collection property="studentlist" resultMap="studentmap"></collection>
    </resultMap>
    <resultMap type="com.dahuici.zyb.entity.Student" id="studentmap">
       <id column="studentid" property="studentid"/>
       <result column="sname" property="name"/>
       <result column="sage" property="age"/>
       <association property="card" resultMap="cardmap"></association>
    </resultMap>
    <resultMap type="com.dahuici.zyb.entity.Card" id="cardmap">
       <id column="cardid" property="cardid"/>
       <result column="schooolname" property="schooolname"/>
    </resultMap>

执行测试代码和输出结果

@Test
    public void getTecherById() {
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Teacher teacher = mapper.getTeacherById(1);
        System.out.println(teacher);
    }
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 990416209.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3b088d51]
==>  Preparing: select teacher.teacherid as teacherid,teacher.name as tname, teacher.age as tage,studentid,student.name as sname, student.age as sage,card.cardid as cardid,schooolname from teacher inner join student on teacher.teacherid = student.teacherid inner join card on card.cardid = student.studentid where teacher.teacherid = ? 
==> Parameters: 1(Integer)
<==    Columns: teacherid, tname, tage, studentid, sname, sage, cardid, schooolname
<==        Row: 1, 张老师, 26, 1, zyb, 23, 1, *****大学
<==        Row: 1, 张老师, 26, 2, 张三, 33, 2, *****大学1
<==        Row: 1, 张老师, 26, 3, 李四, 34, 3, *****大学2
<==      Total: 3
Teacher(teacherid=1, name=张老师, age=26, studentlist=[Student(studentid=1, name=zyb, age=23, card=Card(cardid=1,
 schooolname=*****大学), teacher=null), Student(studentid=2, name=张三, age=33, card=Card(cardid=2, schooolname=*****大学1), 
teacher=null), Student(studentid=3, name=李四, age=34, card=Card(cardid=3, schooolname=*****大学2), teacher=null)])

由于查询结果里存在重复字段名,比如老师与学生的name字段,所以这里将重复的字段都定义了别名。
这里即用到了一对多关系(老师对学生),也用到了一对一关系(学生对一卡通)。通过看映射文件里面的配置,可以看出一对一与一对多唯一的区别就是一对一使用的是<resultMap>的<association>子标签,而一对多用的是<collection>

以上代码都是为了方便自己回忆复习时使用的,仅供参考。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,764评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,235评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,965评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,984评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,984评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,471评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,844评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,818评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,359评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,385评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,515评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,114评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,836评论 3 338
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,291评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,422评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,064评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,581评论 2 365