在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);
}
可以看出,接口方法返回User对象然后在<select>标签中定义resultType="com.dahuici.zyb.entity.User",mybatis就会把返回的记录自动封装成User对象返回。这里没有对mysql查询的结果记录与实体类的属性建立任何的关联,但是当没有定义关联时,mybatis会默认将mybatis查询出的结果字段与其同名的实体类属性建立关联,进行实体类属性的封装,所以这里返回的user对象的各个属性才有值。实体类与数据库表的对应关系如下:
除了使用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;
}
数据库表结构
需求描述:获取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之间的联系关系如下:
主要介绍一下<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);
}
突然灵机一动,当获取多个学生及其一卡通信息时,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;
}
需求,查出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>
以上代码都是为了方便自己回忆复习时使用的,仅供参考。