MyBatis 是针对映射器构造的 SQL 构建的轻量级框架,并且通过配置生成对应的 JavaBean 给调用者,而这些配置主要便是映射器,在 MyBatis 中可以根据情况定义动态 SQL 来满足不同场景的需要,因此这就是它比其他框架更加灵活的原因。
映射器的主要元素:
元素 | 描述 |
---|---|
select | 查询语句 |
insert | 插入语句 |
update | 更新语句 |
delete | 删除语句 |
parameterMap | 定义入参映射关系 |
resultMap | 定义结果集映射关系 |
sql | 自定义SQL,可以在其他地方引用 |
cache | 给定命名空间的缓存配置 |
cache-ref | 其他命名空间缓存配置的引用 |
映射器详解
select 元素
select 元素是我们最常用的 SQL 语言。在执行 select 语言之前,我们需要定义参数,参数类型可以是基本数据类型,例如:int、float、String,也可以是复杂的参数类型,例如:javaBean、Map等。执行完 SQL,MyBatis 也支持参数映射,可以通过自动映射来把返回的结果集绑定到 JavaBean 中。
下面以 select 中常间的元素:
元素 | 描述 |
---|---|
id | 标识唯一,提供调用,并与接口的方法对应 |
parameterType | 参数类型,可以用类的全命名,也可以用别名,但别名要事先定义好,可以使用JavaBean、Map |
resultType | 定义类的全路径,在自动匹配的情况下结果集可以通过JavaBean的规范映射,可以使用别名,也可定义为int,double、float等参数 |
resultMap | 它是映射集的引用,我们可以使用 resultMap 或者 resultType,其中 resultMap 给了我们更好的自定义规则 |
flushCache | 调用查询之后清空本地缓存和二级缓存 |
useCache | 启动二级缓存 |
timeOut | 超时参数,指定SQL超出规定时间会抛出异常 |
fetchSize | 获取记录的总条数设定 |
「例子」:
定义一个 student.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="cbuc.ssm.mapper.StudentMapper">
<select id="selectCountBySelective" resultType="int" parameterType="string">
select count(*)
from student
where name like CONCAT('%',#{remark},'%')
</select>
</mapper>
然后定义一个studentMapper.class:
package cbuc.ssm.mapper;
public interface StudentMapper {
int selectCountBySelective(String remark);
}
其中mapper的 namespace(命名空间)需要对应 StudentMapper类的全路径,select标签的「id」,需要与StudentMapper的selectCountBySelective() 方法对应,「parameterType」 定义参数类型,「resultType」定义返回值类型。
自动映射
「自动映射」 也是 MyBatis 的一个特点,当autoMapperingBehavior 不设置为 NONE 的时候,MyBatis 会给我们提供自动映射的功能。前提就是 SQL 返回的列名,需要和 JavaBean 的属性一致,这样 MyBatis 就会自动帮我们回填这些字段值,当表中列名是以下划线命名的时候我们可以在配置文件中开启驼峰映射规则。
<settings>
<!-- 开启驼峰命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
「例子」:
Student.class:
public class Student {
private Long id;
private String name;
private String remark;
// 省略get/set
}
StudentMapper.class:
public interface StudentMapper {
Student selectByName(String name);
}
Student.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="cbuc.ssm.mapper.StudentMapper">
<select id="selectByName" resultType="cbuc.ssm.entity.Student" parameterType="string">
select id, name, remark
from student
where name like concat('%',#{name},'%')
</select>
</mapper>
在上面例子中,我们是通过name来查找一条学生记录,其中表字段为:
字段 | 类型 | 描述 |
---|---|---|
id | INT(10) | 主键ID |
name | VARCHAR(8) | 名称 |
remark | VARCHAR(64) | 备注 |
可以看出表字段的名称是和实体属性对应的,如果多了一个表字段 create_time,而实体中属性的命名是createTime,那么只要在配置中开启驼峰映射,也是可以自动映射上的。除此之外,配置中 settings属性还可以配置 autoMappingBehavior来设置其他映射策略。
名称 | 描述 |
---|---|
NONE | 取消自动映射 |
PARTIAL | (默认) 只会进行自动映射,没有定义嵌套结果集映射的结果集 |
FULL | 会自动映射任意复杂的结果集(无论是否嵌套) |
多参数传递
上面例子中我们是通过一个 String 类型的参数进行传递,但是很多时候我们传递的参数不止一个,这个时候怎么办呢?
当然这个时候 MyBatis 也帮我们解决了后顾之忧
「Map」
遇到多个参数的时候我们可以借助 Map 来进行参数的传递
StudentMapper.class:
public interface StudentMapper {
Student selectByCondition(Map conditionMap);
}
Student.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="cbuc.ssm.mapper.StudentMapper">
<!--根据条件查询-->
<select id="selectByCondition" resultType="cbuc.ssm.entity.Student" parameterType="map">
select id, name, remark
from student
where name like CONCAT('%',#{name},'%') and remark like CONCAT('%',#{remark},'%')
</select>
</mapper>
执行查询:
Map<String, Object> conditionMap = new HashMap<>();
conditionMap.put("name", "A");
conditionMap.put("remark", "好好学习");
Student resStudent = studentMapper.selectByCondition(conditionMap);
/*** 输出结果 ***/
/*
Student{id=1, name='小A', remark='好好学习'}
*/
在查询的xml文件中,我们定义的 parameterType 是 map,成功实现了多个参数进行传递,但是这个也有个不好的地方就是,map 对于我们来说就是个黑箱,我们不清楚里面存在哪些字段,可读性也相对比较低,因此我们不妨再看看下面几种方法!
「JavaBean」
既然我们在返回结果的时候能够将结果集自动映射到 JavaBean 中,那么我们在传递参数的时候是否也可以通过 JavaBean 的方式进行传递呢?答案是可以的
Student.class:
public class Student {
private Long id;
private String name;
private String remark;
// 省略get/set
}
StudentMapper.class:
public interface StudentMapper {
Student selectBySelective(Student student);
}
Student.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="cbuc.ssm.mapper.StudentMapper">
<!--根据条件查询-->
<select id="selectBySelective" resultType="cbuc.ssm.entity.Student" parameterType="cbuc.ssm.entity.Student">
select id, name, remark
from student
where name like CONCAT('%',#{name},'%') and remark like CONCAT('%',#{remark},'%')
</select>
</mapper>
执行查询:
Student conditionStudent = new Student();
conditionStudent.setName("A");
conditionStudent.setRemark("好好学习");
Student resStudent = studentMapper.selectBySelective(conditionStudent);
System.out.println(resStudent);
/*** 输出结果 ***/
/*
Student{id=1, name='小A', remark='好好学习!'}
*/
在查询的xml文件中,我们定义的 parameterType 是 Student这个 JavaBean的全路径,然后我们就可以实现通过 JavaBean作为参数传递进行查询
「@Param 注解」
最后一种传递参数的方式就是通过 @Param 标记入参的键方式来传递方式
StudentMapper.class:
public interface StudentMapper {
Student selectByParams(@Param("name") String name, @Param("remark") String remark);
}
Student.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="cbuc.ssm.mapper.StudentMapper">
<!--根据条件查询-->
<select id="selectByParams" resultType="cbuc.ssm.entity.Student" parameterType="string">
select id, name, remark
from student
where name like CONCAT('%',#{name},'%') and remark like CONCAT('%',#{remark},'%')
</select>
</mapper>
当我们把参数传递到后台的时候,通过@Param 提供的名称 MyBatis 就会知道 #{name} 代表了 name 参数,可读性是加强了,但是如果传递的参数很多,那么这个方法是不是会很长,这就是使用注解的弊端之一,因此在传递多个参数的时候还是推荐使用 JavaBean 的方式。
resultMap 结果集映射
在上面我们看到的查询返回的定义都是使用 resultType来映射结果集,其实还有一个标签也能用来映射结果集,那就是使用 resultMap 来映射结果集,而且功能更加强大
「简单例子」:
Student.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="cbuc.ssm.mapper.StudentMapper">
<!--自定义接收结果集-->
<resultMap id="myResultMap" type="cbuc.ssm.entity.Student">
<id column="id" property="id" javaType="long" jdbcType="BIGINT"/>
<result column="name" property="name" javaType="string" jdbcType="VARCHAR"/>
<result column="remark" property="remark" javaType="string" jdbcType="VARCHAR"/>
</resultMap>
<!--根据条件查询-->
<select id="selectBySelective" resultMap="myResultMap" parameterType="cbuc.ssm.entity.Student">
select id, name, remark
from student
where name like CONCAT('%',#{name},'%') and remark like CONCAT('%',#{remark},'%')
</select>
</mapper>
在 select 标签上我们使用 resultMap来定义接收结果集,更强大的用法请往下看,这里先留个悬念!图片
insert 元素
上面讲完了 select 元素,接下来就进入insert 元素环节
insert 元素常用配置:
属性名称 | 描述 |
---|---|
id | 标识唯一,提供调用,并与接口的方法对应 |
parameterType | 参数类型,可以用类的全命名,也可以用别名,但别名要事先定义好,可以使用JavaBean、Map |
flushCache | 执行 SQL 之后清空本地缓存和二级缓存 |
timeOut | 超时参数,指定SQL超出规定时间会抛出异常 |
keyProperty | 标识以哪个列作为属性的主键,不能 keyColumn 同时使用 |
useGeneratedKeys | 让 MyBatis 使用 JDBC 的 getGenerateKeys 方法取出数据库生成的主键 |
keyColumn | 标识第几列是主键,不能和 keyProperty 同时使用,只接受整形参数 |
有很多配置是和 select 相似的,学完前面的 select 相信对这些标签也不会太陌生
「简单例子」:
StudentMapper.class:
public interface StudentMapper {
int insertBySelective(Student student);
}
Student.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="cbuc.ssm.mapper.StudentMapper">
<insert id="insertBySelective" parameterType="cbuc.ssm.entity.Student" keyProperty="id" useGeneratedKeys="true">
insert into student
(name, remark)
values
(#{name},#{remark})
</insert>
</mapper>
执行SQL:
Student insStudent = new Student();
insStudent.setName("小明");
insStudent.setRemark("好好学习");
studentMapper.insertBySelective(insStudent);
System.out.println(insStudent.getId());
/*** 输出结果 ***/
/*
2
*/
从输出结果中我们可以得到这条记录的ID为2。
那么如果我们不想使用 MySql 的自动生成主键,而是用自己的生成策略那么该如何定义了,MyBatis 一样为我们准备好了!
规则:如果数据库中没有数据的情况下,也就是 id 为空,那么插入数据的 id为1,如果有数据的情况下,插入数据的 ID 是最大 ID 的两倍
「实现」:
「Student.xml」:
<insert id="insertBySelective" parameterType="cbuc.ssm.entity.Student" keyProperty="id" useGeneratedKeys="true">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
select IF(ISNULL(MAX(id)), 1, MAX(id)*2) from student
</selectKey>
insert into student(id, name, remark) values (#{id}, #{name}, #{remark})
</insert>
执行SQL:
Student insStudent = new Student();
insStudent.setName("小A");
insStudent.setRemark("好好学习");
studentMapper.insertBySelective(insStudent);
System.out.println(insStudent.getId());
/*** 输出结果 ***/
/*
4
*/
因为此时数据库中已经有两天数据了,所以返回的 ID 为4,这也许便是 MyBatis 的性感之处吧,我们只需要简单定义配置,它就为我们做好了所有事。
update 元素
update元素和insert元素一样,执行完 SQL,返回的结果便是数据库执行 SQL 影响的记录条数
「Student.xml」:
<update id="updateBySelective" parameterType="cbuc.ssm.entity.Student">
update student set remark = #{remark} where name = #{name}
</update>
「StudentMapper.class」:
public interface StudentMapper {
int updateBySelective(Student student);
}
「执行更新」:
Student updateStudent = new Student();
updateStudent.setRemark("好好学习");
updateStudent.setName("小A");
int res = studentMapper.updateBySelective(updateStudent);
System.out.println(res);
/*** 输出结果 ***/
/*
1
*/
delete 元素
delete 元素也和上面两个元素相似,返回的结果是数据库执行 SQL 影响的记录条数
「Student.xml」:
<delete id="deleteByPrimaryKey" parameterType="int">
delete from student where id = #{id}
</delete>
「StudentMapper.class」:
public interface StudentMapper {
int deleteByPrimaryKey(int id);
}
「执行SQL」:
int res = studentMapper.deleteByPrimaryKey(1);
System.out.println(res);
/*** 输出结果 ***/
/*
1
*/
讲完了增删改查四大元素,接下来我们来点别的,换换口味
sql 元素
开发中我们讲究的是 高内聚低耦合,那么 sql 元素存在的意义就是把相同的代码片段抽出来,达到可以复用的作用。
<!--根据条件查询 -->
<select id="selectBySelective" resultMap="resultMap"
parameterType="cbuc.ssm.entity.Student">
select id,name,remark
from student
where name like CONCAT('%,#{name},%and remark like CONCAT('%,#remark},'%
</select>
<!--根据条件查询 -->
<select id="selectByCondition" resultType="cbuc.ssm.entity.Student" parameterType="map">
select id,name,remark
from student
where name like CONCAT(%,#name},')and remark like CONCAT('%,#remark},'%)
</select>
<!-- 根据条件查询 -->
<select id="selectByParams" resultType="cbuc.ssm.entity.Student" parameterType="string">
select id,name,remark
from student
where name like CONCAT('%,#{name},'%and remark like CONCAT('%,#{remark},%)
</select>
我们书写的 3 个 select 查询都用到了相同的字段(id,name,remark)。那么这部分冗余的代码我们就可以通过 sql 元素标签抽取出来,如下:
<!--定义了基本查询字段-->
<sql id="baseColumns">
id, name, remark
</sql>
<!--通过 include 标签引用sql-->
<select id="selectByName" resultType="cbuc.ssm.entity.Student" parameterType="string">
select <include refid="baseColumns"/>
from student
where name like concat('%',#{name},'%')
</select>
我们也可以给 sql 元素中传递值:
<sql id="baseColumns">
#{tableName}.id, #{tableName}.name, #{tableName}.remark
</sql>
<!--通过 property 元素,将 t 这个值传递给 #{tableName}-->
<select id="selectByName" resultType="cbuc.ssm.entity.Student" parameterType="string">
select
<include refid="baseColumns">
<property name="tableName" value="t"/>
</include>
from student t
where name like concat('%',#{name},'%')
</select>
cache 元素
缓存的出现是为了在读取的时候无需再从磁盘读入,具备快速读取和使用的特点,MyBatis 可以通过缓存将数据保存在内存中,如果缓存命中率高,那么可以极大地提高系统的性能。
一级缓存和二级缓存
Mybatis 在没有配置缓存的情况下是默认开启一级缓存的(一级缓存只相对同一个 SqlSession 有效)。当参数和 SQL 完全一样的情况下,使用同一个 SqlSession 对象调用同一个 mapper 方法的时候,往往只会执行一次 SQL,因为在第一次查询后,MyBatis 会将结果放在缓存中,下次再查询的时候,没有使用flushCache 刷新缓存,并且缓存没有超时的情况下,SqlSession都只会取出当前缓存的数据,而不会再次发送SQL。当使用不同 SqlSession 对象执行mapper,就不会去缓存中的数据,因为每个 SqlSession 之间是相互隔离的。
SqlSessionFactory 的二级缓存是默认不开启的,二级缓存是作用在命名空间上的,如果开启我们需要在配置中声明,并且MyBatis要求返回的 JavaBean 必须要可序列化的,可以通过以下方法声明缓存:
<?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="cbuc.ssm.mapper.StudentMapper">
<!--开启二级缓存-->
<cache/>
</mapper>
如果我们只配置 这个定义的话,那么默认配置为:
- 映射语句文件中所有的 SELECT 语句将会被缓存
- 映射语句文件中所有的 INSERT、UPDATE、DELETE 语句都会刷新缓存
- 缓存会使用默认的 LRU(Least Recently Used 最近最少使用)算法来回收
- 缓存会存储列表集合或对象(无论查询方法返回什么)的引用
- 缓存会被视为 read/write(可读/可写)的,可以安全地被调用者修改,不干扰其他调用者线程所做的潜在修改
添加配置后,我们要记得让 JavaBean 实现 Serializable 接口,否则会抛出异常
用过这个标签的小伙伴就会知道,cache 元素里面还存在其他属性,如下:
- eviction:代表的是缓存回收策略
- LRU:最近最少使用,移除最长时间不用的对象
- FIFO:先进先出,按对象进入缓存的顺序来移除它们
- SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象,采用的是 LRU,移除最长时间不用的对象
- flushInterval:刷新间隔时间,单位为毫秒,如果不配置的话,那么只有当 SQL 被执行的时候才会去刷新缓存
- size:引用数目,代表缓存最多可以存储多少个对象,实际开发中不要设置过大,否则会导致内存溢出
- readOnly:只读,意味缓存数据只能读取而不能修改,可以加快缓存读取速度
自定义缓存
MyBatis 的性感之处就是在乎它愿意包容我们,允许我们自定义
缓存我们也是可以自定义的,只需要实现 org.apache.ibatis.cache.Cache接口,然后在配置中声明,步骤如下:
定义MyCache.class:
public class MyCache implements Cache {
@Override
public string getId(){
System.out.println("获取缓存编号");
return null;
}
@Override
public void putobject(object key,object value){
System.out.println("保存key值缓存对像");
}
@Override
public object getobject(object key){
System.out.println("通过key值获取缓存对像");
return null;
}
@override
public object removeobject(object key){
System.out.println("通过key删除缓存对像");
return null;
}
@Override
public void cLear(){
System.out.println("清空缓存");
}
@override
public int getsize(){
System.out.println("获取缓存对象大小");
return 0;
}
@override
public ReadwriteLock getReadwriteLock(){
System.out.println("获取缓存的读写锁");
return null;
}
}
在配置声明:
<cache type="cbuc.ssm.custom.MyCache>
这样子我们就可以使用我们自定义的缓存啦
resultMap 元素
上面讲到 SELECT 元素的时候,我们留下了一个悬念,在这里就为大家介绍啦(可真坏坏!)
resultMap 是MyBatis 里面最复杂的元素,它的作用是定义映射规则,级联的更新,定制类型转化器等。听到这么多强大的功能,内心是否有点小激动,莫慌,待我细细道来
resultMap元素的构成
<resultMap>
<constructor>
<idArg/>
<arg/>
</constructor>
<id/>
<result/>
<association/>
<collection/>
<discriminator>
<case/>
</discriminator>
</resultMap>
constructor
使用constructor 元素可以用来配置构造方法,一个 POJO 可能不存在无参构造方法,这个时候我们就可以使用 constructor 进行配置。在Student类中不存在无参构造函数,只要一个构造函数:
public Student(Long id, String name, String remark) {
this.id = id;
this.name = name;
this.remark = remark;
}
然后我们需要配置这个结果集:
<resultMap id="myResultMap" type="cbuc.ssm.entity.Student">
<constructor>
<idArg column="id" javaType="int"/>
<arg column="name" javaType="string"/>
<arg column="remark" javaType="string"/>
</constructor>
</resultMap>
通过这样子配置之后,MyBatis 就知道需要使用哪个构造方法来构建 POJO了
id 和 result
id 元素是用来标识哪个列是主键,允许多个主键,多个主键称为联合主键。
result 元素是配置 POJO 到 SQL 列名的映射关系
两个元素的共同属性如下:
元素名称 | 说明 |
---|---|
property | 映射到列结果的字段或属性,也就是 POJO 的属性名 |
column | 对应数据表中的列名 |
javaType | POJO 属性的 Java 类型 |
jdbcType | 数据表中列的类型 |
typeHandler | 类型处理器 |
级联属性
association、collection都是属于比较常用到级联属性
- association:代表一对一关系,必须一个人有一张身份证是一对一个的关系
- collection:代表一对多关系,一个班级有多个学生
association
一对一关系,比如一个学生有一张学生卡,这个时候需要一个学生类和学生卡类
「student.class」:
public class Student {
private Long id;
private String name;
private String remark;
private StudentCard studentCard;
//省略get/set方法
}
「studentCard.class」:
public class StudentCard {
private Long id;
private Date createTime;
private String address;
private Long studentId;
//省略get/set方法
}
「studentCard.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="cbuc.ssm.mapper.StudentCardMapper">
<!--自定义接收结果集 -->
<resultMap id="myResultMap" type="cbuc.ssm.entity.StudentCard">
<id column="id" property="id"/>
<result column="student_id" property="studentId" javaType="long"
jdbcType="BIGINT"/>
<result column="create_time" property="createTime" javaType="date"
jdbcType="TIMESTAMP"/>
<result column="address" property="address" javaType="string"
jdbcType="VARCHAR"/>
</resultMap>
<!-- 查询学生证 -->
<select id="getCardBystudentID" resultMap="myResultMap" parameterType="long">
select id,create_time,address,student_id
from studentcard
where student_id #{studentId}
</select>
</mapper>
「student.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="cbuc.ssm.mapper.StudentMapper">
<resultMap id="myResultMap" type="cbuc.ssm.entity.Student">
<id column="id" property="id" javaType="long" jdbcType="BIGINT"/>
<result column="name" property="name"/>
<result column="remark" property="remark"/>
<association property="studentCard" column="id" select="cbuc.ssm.mapper.StudentCardMapper.getCardByStudentID"/>
</resultMap>
<!-- 根据主键查询 -->
<select id="selectByPrimaryKey" resultMap="myResultMap" parameterType="int">
select id,name,remark
from student
where id =#{id}
</select>
</mapper>
「执行查询」:
Student resStudent = studentMapper.selectByPrimaryKey(1);
System.out.println(resStudent);
/*** 输出结果 ***/
/*
Student{id=1, name='小菜', remark='关注小菜不迷路!', studentCard=StudentCard{id='1', createTime=Wed Jul 01 23:16:29 CST 2020, address='中国', studentId=1}}
*/
这样我们查到学生信息的同时也能够把学生证信息查到。上面的级联方式是通过 select去引入其它mapper 的查询结果,那么我们能不能直接用表连接的方式查询结果,然后自定义结果集呢,答案是可以的,方式如下:
「student.xml」:
<resultMap id="cusResultMap" type="cbuc.ssm.entity.Student">
<id column="id" property="id" javaType="long" jdbcType="BIGINT"/>
<result column="name" property="name"/>
<result column="remark" property="remark"/>
<association property="studentCard" column="id"
javaType="cbuc.ssm.entity.Studentcard">
<result property="id" column="sid"/>
<result property="studentId" column="student_id"/>
<result property="address" column="address"/>
<result property="createTime" column="create_time"/>
</association>
</resultMap>
<!-- 根据主键查询 -->
<select id="selectByPrimaryKey" resultMap="cusResultMap" parameterType="int">
select s.id,name,remark,sc.id as sid,sc.student_id,sc.address,
sc.create time
from student s
left join studentcard sc on sc.student_id s.id
where s.id #{sid}
</select>
collection
一对多关系,比如一个学生有多张电话卡,这时候需要一个学生类和一个电话卡类
「student.class」:
public class Student {
private Long id;
private String name;
private String remark;
private List<PhoneCard> phoneCardList;
//省略get/set方法
}
「phoneCard.class」:
public class StudentCard {
private long id;
private long studentId;
private String phoneNum;
//省略get/set方法
}
「phoneCard.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="cbuc.ssm.mapper.PhoneCardMapper">
<!-- 自定义接收结果集 -->
<resultMap id="myResultMap" type="cbuc.ssm.entity.PhoneCard">
<id column="id" property="id"/>
<result column="student_id" property="studentId" javaType="long"
jdbcType="BIGINT"/>
<result column="phone_num" property="phoneNum" javaType="string"
jdbcType="VARCHAR"/>
</resultMap>
<!-- 查询电话卡 -->
<select id="getPhoneCardByStudentId" resultMap="myResultMap" parameterType="long">
select id,phone_num,student_id
from phonecard
where student_id = #{studentId}
</select>
</mapper>
「student.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="cbuc.ssm.mapper.StudentMapper">
<!-- 自定义接收结果集 -->
<resultMap id="myResultMap" type="cbuc.ssm.entity.Student">
<id column="id" property="id" javaType="long" jdbcType="BIGINT"/>
<result column="name" property="name"/>
<result column="remark" property="remark"/>
<collection property="phoneCardList" column="id"
select="cbuc.ssm.mapper.PhoneCardMapper.getPhoneCardByStudentId"/>
</resultMap>
<!-- 根据主键查询 -->
<select id="selectByPrimaryKey" resultMap="myResultMap" parameterType="int">
select id,name,remark
from student
where id #{sid}
</select>
</mapper>
「执行查询」:
Student resStudent = studentMapper.selectByPrimaryKey(1);
System.out.println(resStudent);
/*** 输出结果 ***/
/*
Student{id=1, name='小A', remark='好好学习!', phoneCardList=[PhoneCard{id=1, studentId=1, phoneNum='13512138979'}, PhoneCard{id=2, studentId=1, phoneNum='13688888888'}]}
*/
在学生类中包含了一个类型为 List phoneCardList电话卡集合,使用 collection 级联就会使用select 中的查询方法然后自动将结果映射到 phoneCardList 中。
同样的也可以使用另一种级联方式:
<resultMap id="cusResultMap" type="cbuc.ssm.entity.Student">
<id column="id" property="id" javaType="long" dbcType="BIGINT"/>
<result column="name" property="name"/>
<result column="remark" property="remark"/>
<collection property="phoneCardList" ofType="cbuc.ssm.entity.PhoneCard">
<result property="studentId" column="student_id"/>
<result property="phoneNum" column="phone_num"/>
</collection>
</resultMap>
<!-- 根据主键查询 -->
<select id="selectByPrimaryKey" resultMap="cusResultMap" parameterType="int">
select s.id,name,remark,pc.student_id,pc.phone_num
from student s
left join phonecard pc on pc.student_id =s.id
where s.id =#{sid}
</select>
级联用起来确实挺方便的,能够方便快捷地获取数据,但是多层关联时(超过3层)尽量少用级联,因为不仅用处不大,而且户造成复杂度的增加,不利于他人的理解和维护。
动态SQL
我们在使用 JDBC 或者其他框架的时候需要根据实际条件去拼接 SQL ,这是件挺麻烦的事情。但是在我们使用 MyBatis 只有仿佛就不存在这些麻烦事。
MyBatis 提供对 SQL 语句动态的组装能力,而且它只有几个基本的元素,用起来也十分简单明了,大量的判断都可以在 MyBatis 的映射 XML 文件里面配置,以达到许多我们需要大量代码才能实现的功能,大大减少了我们编写代码的工作量。
MyBatis 的动态 SQL 包含以下几种元素:
名称 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose(when、otherwise) | 相当于 Java 中的 switch case default 语句 | 多条件分支判断 |
trim、where、set | 辅助元素 | 用于处理一些 SQL 拼装问题 |
foreach | 循环语句 | 在 in 语句等列举条件常用 |
bind | 定义上下文变量 | 通过 OGNL 表达式自定义 |
if 元素
看到 if 就莫名有一种亲切感,因为我们在平时开发中用到最多的应该就是if 了。
if 不仅在开发中用到比较多,在 MyBatis 中也是属于 C位的存在。它常常与test属性联合使用。在大部分情况下,if元素使用方法简单,下面是使用用例:
<!--根据条件查询-->
<select id="selectByCondition" resultType="cbuc.ssm.entity.Student" parameterType="map">
select id, name, remark
from student
where 1 = 1
<if test="name != null and name != ''">
and name like CONCAT('%',#{name},'%')
</if>
<if test="remark != null and remark != ''">
and remark like CONCAT('%',#{remark},'%')
</if>
</select>
就这?没错,if元素用起来就是这么简单,在test元素中添加判断的逻辑,成立则构造这个条件,这种场景在我们实际的开发中是十分常见的,通过 MyBatis 的条件语句我们可以节省许多拼接 SQL 的工作。
choose 元素
有choose元素的地方就会有when,有时也会带上otherwise一起玩,相对于if元素非此即彼的关系,显然choose元素为我们选择了更多判断的机会,(你看我还有三连的机会吗)。choose when otherwise元素的用法跟 Java 中 switch case default相似
有个场景如下:
如果传入的name不为空,那我们就用name作为模糊查询条件,如果name为空而remark不为空,那么就用remark作用查询条件,如果两者都为空,就查询name不是空的记录。
实现如下:
<!--根据条件查询-->
<select id="selectByCondition" resultType="cbuc.ssm.entity.Student" parameterType="map">
select id, name, remark
from student
where 1 = 1
<choose>
<when test="name != null and name != ''">
and name like CONCAT('%',#{name},'%')
</when>
<when test="remark != null and remark != ''">
and remark like CONCAT('%',#{remark},'%')
</when>
<otherwise>
and name is not null
</otherwise>
</choose>
</select>
一顿操作猛如虎,三下五除二的实现了功能,公司老大都忍不住给你加薪水了呢!
通过以上语句 MyBatis 就会根据参数的设置进行判断来动态组装 SQL,来满足不同的业务要求,语句清晰明了,功能一看便知。
trim、where、set 元素
where
不知道细心的小伙伴有没有发现以上的 SQL 都带有 where 1 = 1 这样的语句,实际上像这种写法是十分不美观的,而且还影响 SQL 查询的性能,但是如果不加入这种写法,语句又不成立:
select id, name, remark from student where and name like CONCAT('%',#{name},'%')
在你左右为难,写也不是不写也不是的情况下,你看到了小菜写的这篇文章,不禁感叹,来的真**及时。
在 MyBatis 中我们需要用到 where 查询的时候,它也给我们提供了一个 where 元素,用法如下:
<!--根据条件查询-->
<select id="selectByCondition" resultType="cbuc.ssm.entity.Student" parameterType="map">
select id, name, remark
from student
<where>
<if test="name != null and name != ''">
and name like CONCAT('%',#{name},'%')
</if>
<if test="remark != null and remark != ''">
and remark like CONCAT('%',#{remark},'%')
</if>
</where>
</select>
虽然每个判断中都带有 and,但是用了where元素,MyBatis 会自动帮我们拼接 SQL,把多余的and 去掉,达到我们预期的效果。
set
update 更新语句相信我们在实际开发中用的也不少了,当然 MyBatis 中也支持 set标签来帮助我们书写 SQL,用法如下:
<update id="updateBySelective" parameterType="cbuc.ssm.entity.Student">
update student
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="remark != null and remark != ''">
remark = #{remark},
</if>
</set>
where name = #{id}
</update>
虽然每个判断后面都跟着逗号,但是没有关系,使用set标签,MyBatis 会帮我们自动拼接 SQL,把多余的逗号去掉。
trim
相对于前两种写法,trim的功能似乎更加强大,都能兼容前两种写法:
「where」:
<!--根据条件查询-->
<select id="selectByCondition" resultType="cbuc.ssm.entity.Student" parameterType="map">
select id, name, remark
from student
<trim prefix="where" prefixOverrides="and">
<if test="name != null and name != ''">
and name like CONCAT('%',#{name},'%')
</if>
<if test="remark != null and remark != ''">
and remark like CONCAT('%',#{remark},'%')
</if>
</trim>
</select>
「set」:
<update id="updateBySelective" parameterType="cbuc.ssm.entity.Student">
update student
<trim prefix="set" suffixOverrides=",">
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="remark != null and remark != ''">
remark = #{remark},
</if>
</trim>
</update>
trim元素意味着我们需要去掉一些特殊的字符串,prefix 代表的是语句的前缀,而prefixOverrides代表的是你需要去掉的字符串
foreach 元素
foreach名如其意,就是循环语句,它的作用是遍历集合,它能够很好的支持数组和List、Set接口的集合,对此提供遍历的功能。
<select id="selectByIdList" resultType="cbuc.ssm.entity.Student">
select name, remark from student
where id in
<foreach collection="idList" item="id" open="(" close=")" separator="," index="index">
#{id}
</foreach>
</select>
List<Student> selectByIdList(@Param("idList") List<Long> idList);
- collection:配置的是传进来的参数名称,它可以是一个数组或者LIst、Set等集合
- item:配置的是循环中当前的元素
- index:配置的是当前元素在集合的位置下标
- open 和 close:配置的是以什么符号将这些元素包装起来
- separator:是各个元素的间隔符
对于大量数据的in语句需要我们特别注意,因为它会消耗大量的性能,还有一些数据库的 SQL 对执行的 SQL 长度也有限制,所以我们使用它的时候需要预估一下这个 collection 对象的长度。
bind 元素
bind 元素的作用是通过 OGNL 表达式去自定义一个上下文变量,这样更方便我们使用
以下是一个使用例子:
<!--根据条件查询-->
<select id="selectBySelective" resultMap="resultMap" parameterType="cbuc.ssm.entity.Student">
<bind name="keyWord" value="'%' + name + '%'"/>
select id, name, remark
from student
where name like #{keyWord}
</select>
这里的name就是传递进来的参数,与%拼接后赋值给了keyWord,然后在select 中可以使用这个变量进行模糊查询。
关于 MyBatis的使用我们就讲到这里啦,MyBatis 中还要挺多知识需要学习的,每天进步一点点,不仅要看,平时也要多加练习哦!