1 概述
原来是Apache的一个开源项目,叫iBatis, 2010年6月这个项目由 Apache Software Foundation 迁移到了 Google Code,随着开发团队转投Google Code 旗下,从 iBatis3.0正式更名为MyBatis。
-
MyBatis 是一款优秀的持久层框架,对jdbc功能(加载驱动、获得数据库连接、封装sql、发送sql、获得结果、关闭连接)进行轻量级的封装;
-
分层思想:
-
servlet(负责接收前端请求、调用其他的java程序处理、响应),也称web层
-
service(业务逻辑层),进行逻辑处理,业务组装,也称服务层
-
dao(data access Object[数据访问对象]) 指的就是提供jdbc功能的类,也称数据持久层
-
-
-
MyBatis 避免了几乎所有的 JDBC 代码手动设置参数以及手动获取结果集的操作;
-
将sql提取封装到一个xml文件中,提供了动态sql功能以及数据缓存,提供了结果自动映射封装,是一个orm(ORM Object Relational Mapping对象关系映射)实现;
-
提供了统一的数据库配置信息,统一放在一个xml文件中,读取就行;
-
orm指的是将数据中的记录与java中的对象进行关系映射;
-
-
Mybatis 将基本的 JDBC 常用接口封装,提供了一些mybatis自己的接口和类来实现,对外提供操作即可。
目标:简化代码
Mybatis 中文官网 mybatis – MyBatis 3 | 入门
Mybatis 源码下载
Releases · mybatis/mybatis-3 · GitHub
2 MyBatis 环境搭建
1.创建一张表和表对应的实体类
2.创建一个 maven 项目,把项目添加到 git 仓库
-
创建maven项目 教程见:Maven[项目构建工具]_chen☆的博客-CSDN博客
-
添加到git仓库:
3.在文件 pom.xml 添加 mybiatis 相关依赖(导入 MyBatis jar 包,mysql 数据库驱动包)
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
4.创建 MyBatis 全局配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDConfig3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${classDrvierName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${uname}"/>
<property name="password" value="${pwd}"/>
</dataSource>
</environment>
</environments>
</configuration>
-
type="POOLED" 数据库连接池,可以理解为一个集合,假设我们事先在集合中创建5个connection对象放到集合中,有请求要与数据库连接,就可以从池子里直接拿一个connection对象去与数据库连接,用完后不要真正意义上把这个对象销毁,而是放到池子中,假设有5个请求同时来了,也就用这5个创建好的对象就可以了,不用再创建新的,假如访问量比较大,这5个对象都在用,它也有一套机制来处理,比如说让后面来的连接请求先等待一会,如果还没有空闲的,再创建新的对象
-
我们可以把项目中所有值都提取到一个属性配置文件中(.properties文件),该文件应放在resources目录下,以后要改配置信息就只需要改此文件中的信息,注意在核心配置文件(在我们这里就是mybatis-config.xml文件)中配置此文件
<!--导入配置属性文件config.propertise-->
<properties resource="config.properties"></properties>
放在resources目录下,创建一个file文件,名字可以随便起,后缀名以xml结尾:
例如:创建全局配置文件mybatis-config.xml
5. 定义接口
在接口中定义方法:
public interface UserDao{
}
-
例如:
6.创建 sql 映射文件
<?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="接口地址">
定义 sql 语句
</mapper>
- 在resources目录下创建文件夹(Directory)命名为 mappers ,在文件夹中再创建一个文件,名为AdminMappers.xml(这个文件就是专门用来为admin提供映射的)
创建后要在mybatis-config.xml核心配置文件中进行配置:
7.测试MyBatis
读取配置文件:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
创建 SqlSessionFactory:
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
创建 SqlSession:
SqlSession sqlSession = sessionFactory.openSession();
获得接口代理对象:
sqlSession.getMapper(接口.class);
sqlSession .close();关闭
API 接口说明
SqlSessionFactory 接口
-
SqlSessionFactory 主要用来创建 SqlSession对象,由于SqlSessionFactory 对象创建开销较大,所以 SqlSessionFactory 一旦创建就会在整个应用过程中始终存在,没有理由去销毁再创建它,一个应用运行中也不建议多次创建 SqlSessionFactory。
SqlSession 接口
-
Sqlsession 用来每次与数据库会话使用,每与数据库交互一次,就需要创建一个 Sqlsession ,该接口中封装了对数据库操作的方法,与数据库会话完成后关闭会话。
Mybatis-Dao 层 Mapper 接口化开发
Mapper 接口开发方式只需要程序员编写 Mapper 接口,由 Mybatis 框架创建接口的动态代理对象,然后由动态代理对象调用与接口中方法名相同的 id 的 sql
sqlsession.getMapper(接口.class); //获得代理对象
Mapper 接口开发需要遵循以下规范:
1、 Mapper.xml 文件中的 namespace 与 mapper 接口的类路径相同.
2、 Mapper 接口方法名和 Mapper.xml 中定义的每个 statement 的 id 相同.
3、 Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同.
4、 Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同.
3 Mybatis 日志
具体选择哪个日志实现由 MyBatis 的内置日志工厂确定,它会使用最先找到的。
Mybatis 内置的日志工厂提供日志功能,具体的日志实现有以下几种方式:
SLF4J|LOG4J|JDK_LOGGINGCOMMONS_LOGGING|STDOUT_LOGGING
配置日志(在核心配置文件中配置):
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
-
这样在运行时就会打印日志信息:
更多配置信息(如:属性、类型别名等)参考api:mybatis – MyBatis 3 | 配置
4 Mybatis辅助插件安装
MybatisX安装和使用
mabiatisX主要作用:
-
Mapper接口与xml自动跳转功能
-
自动生成代码功能,包含自动生成数据库实体和XML配置文件,根据Mapper的接口方法名自动生成xml配置
安装mabatis插件:
5 junit单元测试插件(在核心配置文件中配置)
<!-- junit 单元测试插件-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>provided</scope>
</dependency>
6 参数传递
1.单个参数直接传递
//基本类型不需要做任何处理
Admin findAdminById(int id);
<select id="findAdminById" parameterType="int" resultType="Admin" >
select id,account,password from admin where id = #{id}
</select>
注意:字符串类型单独传递时,需要进行绑定处理
//字符串类型单独传递时,需要进行绑定处理
Admin find1(@Param("column") String column);
<select id="find1" parameterType="string" resultType="Admin">
select * from admin order by ${column}
</select>
2.多个参数使用 @Param("d") 绑定
//多个参数处理方式
Admin login(@Param("acc") String account, @Param("pwd") String password);
<select id="login" resultType="Admin">
select * from admin where account=#{acc} and password = #{pwd}
</select>
3.如果传入一个复杂的对象,就需要使用 parameterType 参数进行类型定义,例如:
//多个参数封装到一个对象中
Admin login1(Admin admin);
<select id="login1" parameterType="Admin" resultType="Admin">
select * from admin where account=#{account} and password = #{password}
</select>
#{}和${}的区别
#{参数名} 占位符
-
传值时是经过预编译的,编译好 SQL 语句再取值,能够防止 sql 注入,更安全,主要用于向sql中传值
select * from admin where account=#{account} and password = #{password}
${参数名} 拼接符
-
传值时是直接将参数拼接到sql中,会传入参数字符串,取值以后再去编译 SQL 语句,无法防止 Sql 注入 ,不建议用它来传值
select * from admin where account='${account}' and password = '${password}'
-
所以我们主要用它来动态的向sql中传列名,用来排序等,比如按价格(列)升序排序
select * from admin order by ${column}
注意:MyBatis 排序时使用 order by 动态参数时需要注意,用$而不是#
7 单张表增删改查
在这里先补充一个概念:事务
什么是事务?
事务,一般指要做的或所做的事情,在计算机术语中是指访问并可能更新数据库中各项数据的一个程序执行最小单元。事务通常由高级数据库操纵语言或编程语言(eg:sql,java)书写的用户程序所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始和事务结束之间执行的全体操作组成。
为什么需要事务?
-
事务就是为了解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。
举一个简单的例子:
-
我们在微信进行转账操作,要将账户A的200员转到B账户下面,A账户首先要减去200元,然后B账户要增加200元,但是如果在中间出现网络异常或者其它问题,这时A账户已经减了200元,而B账户却并没有增加200元,那么整个操作就是失败的,必须要做出控制,要撤销A账户转账操作,这才能保证业务的正确性,要正确的完成整个操作流程,就需要引入事务,将A账户资金减少和B账户资金增加放入一个事务中,只有所有的操作都成功,才进行提交,这样就保证了数据的安全性。
事务的4个特性(ACID):
1) 原子性(atomicity):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
2) 一致性(consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。(实例:转账,两个账户余额相加,值不变。)
3) 隔离性(isolation):一个事务的执行不能被其他事务所影响。
4) 持久性(durability):一个事务一旦提交,事物的操作便永久性的保存在DB中。即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
新增
//在接口中定义方法
void savaAdmin(Admin admin);
<!--
useGeneratedKeys="true" keyProperty="id" keyColumn="id"
开启将生成的主键列,自动封装到对象中,在这里也就是把数据库中生成的主键赋给Admin对象中的id属性
-->
<insert id="savaAdmin" parameterType="Admin" useGeneratedKeys="true" keyProperty="id"
keyColumn="id">
insert into admin(account,password) value (#{account},#{password})
</insert>
-
useGeneratedKeys="true" keyProperty="id" keyColumn="id"
-
这几个属性的意思是开启将生成的主键列自动封装到对象中
-
useGeneratedKeys是否开启新增加的主键赋值到自己定义的类的属性中
-
keyProperty 接收主键的属性,也就是类中属性
-
keycolumn 数据库中列名
-
parameterType 参数类型
-
-
在新增的时候还没有分配主键号,所以在新增完后我们想立即拿到id号进行其它操作的时候就可以使用这几个属性
-
比如在新增管理员后保存管理员的爱好时,我们就可以通过这些属性在新增成功后立即拿到分配的主键号,接着进行修改操作,不需要再通过其它属性去查询主键号
-
测试:
@Test
public void save() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao1 adminDao = sqlSession.getMapper(AdminDao1.class);
Admin admin = new Admin();
admin.setAccount("zhangsan");
admin.setPassword("111");
adminDao.savaAdmin(admin);//保存管理员,立即拿到id
System.out.println(admin.getId());
//需要在所有的业务代码执行成功后,手动提交事务
sqlSession.commit();//提交事务
sqlSession.close();//关闭连接
}
- 注意:下面的新增、修改、删除操作基本都在事务控制中进行
修改
//定义方法
void updateAdmin(Admin admin);
<update id="updateAdmin" parameterType="Admin">
update admin set account=#{account},password=#{password}
where id = #{id}
</update>
测试:
@Test
public void update() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao1 adminDao = sqlSession.getMapper(AdminDao1.class);
Admin admin = new Admin();
admin.setId(3);
admin.setAccount("lisi");
admin.setPassword("111");
adminDao.updateAdmin(admin);
sqlSession.commit();//提交事务
sqlSession.close();
}
删除
//定义方法
void deleteAdminById(int id);
<delete id="deleteAdminById">
delete from admin where id = #{id};
</delete>
查询
<select id="唯一标识" resultType="返回结果集类型">
select * from ts_user where id= #{id}
</select>
8 结果处理
8.1 简单类型输出映射
返回简单基本类型
//查询管理员总数
int adminCount();
<select id="adminCount" resultType="int">
select count(*) from admin
</select>
-
返回结果需要定义后才能使用简称
-
eg:resultType="Admin" 已经定义过别名了,所以就可以使用简称
-
那么java中其它常用类型简称如何使用?(不使用简称就需要用全类名,eg:java.util.List)
-
在mybatis底层源码中定义了一些常用的类型的简称
-
-
- 也可以在官网中查看定义好的类型简称或者自己可以重写已有的类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型,具体参考mybatis – MyBatis 3 | 配置
8.2 对象映射
-
mybatis会将查询到的结果自动封装到一个对象中,会自己创建给定类型的类的对象(通过是否执行无参构造方法我们可以看到)
-
自动封装的条件:
-
开启了全局的自动结果映射,PARTIAL默认是单张表开启的
-
数据库列名与属性名名字相同,mybatis会自动将查询结果封装到pojo对象中;如果不一样,比如java中使用标准驼峰命名,数据库中使用下划线连接命名,这个时候可以通过开启驼峰命名自动映射或开启全局设置实现自动转换
-
例如:
定义的类中的属性名:
<select id="findAdminById" parameterType="int" resultType="Admin">
// 定义别名acc、adminGender
select id,account acc,password,admin_gender adminGender from admin where id =#{id};
</select>
可以看到列名与属性名相同的,mybatis会自动将结果封装到pojo对象中,不同的则不会映射:
开启驼峰命名自动映射后即使不设置别名,也可以自动实现驼峰映射(eg:admin_gender ---> adminGender)
-
在全局配置文件中开启
查询多行数据,也就是返回多个对象
-
mybatis会自动创建多个对象,并通过set方法为属性赋值,然后将这些对象自动的封装到List集合中
List<Admin> findAdmins();
<select id="findAdmins" resultType="Admin">
select id,account,password,admin_gender from admin
</select>
8.3 特殊处理定义 resultMap
定义 resutlMap
-
在resutlMap 标签中,我们可以自定义结果映射
<resultMap id="adminMap" type="Admin">
<id column="id" property="id"></id><!-- 封装映射主键列 -->
<result column="account" property="account"></result>
<result column="password" property="password"></result>
<result column="admin_gender" property="gender"></result>
</resultMap>
(1) resutlMap 的 id 属性是 resutlMap 的唯一标识,本例中定义为“adminMap”
(2) resutlMap 的 id 属性是映射的 POJO 类
(3) id 标签映射主键,result 标签映射非主键
(4) property 设置 POJO 的属性名,column 数据库中列名
使用 resutlMap
<select id="findAdmins" resultMap="adminMap">
select id,account,password,admin_gender from admin
</select>
(1) 本例的输出映射使用的是 resultMap,而非 resultType
(2) resultMap 引用了 adminMap
在说关联查询之前我们先说一个问题:
在关联查询的时候我们需要同时获取几张表中的数据,在模型类中进行封装的时候,我们会在一个类中把其他类中已有的属性再定义一遍,比如以前我们会在Student类会中再定义一遍我们需要的Dorm类中已有的属性,这样显然是不合理的,所以我们现在直接在Student中关联Dorm类中的属性,它就会将数据封装到关联的类中去,以减少代码冗余。
eg:
8.4 多表关联处理结果集及嵌套查询
8.4.1 多表关联处理结果
resultMap 元素中 association , collection 元素.
-
association :是一对一使用的,用来封装类中所关联的对象信息,会创建一个关联对象
-
property="类中的属性名" ,javaType="类型",select表示要执行的sql语句
-
-
collection:关联元素处理一对多关联
8.4.2 嵌套查询
-
将一个多表关联查询拆分为多次查询,查询主表数据,然后查询关联表数据
<association property="dorm" column="dormid"
select="findDormById" javaType="Dorm">
</association>
(1) select:指定关联查询对象的 Mapper Statement ID 为 findDormById
(2) column="dormid" :关联查询时将 dormid 列的值传入 findDormById,传多个参数的话就是{"属性名"="参数","属性名"="参数"}
(3) collection 和 association 都需要配置 select 和 column 属性,两者配置方法相同
案例数据库表:
student表:
dorm表:
admin表:
案例一
-
在查询学生信息的时候将宿舍号和操作人也查询出来,这时就要用到多表关联结果处理
方法一:通过三张表关联查询,一次把信息都查询出来,在封装信息的时候再处理
Student findStudentById(int id);
<resultMap id="studentMap" type="com.ffyc.mybatispro.model.Student">
<!--
mybatis默认配置,一旦出现了嵌套关联查询,就把自动映射关闭了,所以这里我们需要自定义结果映射
要想让它继续自动映射,就需要在全局配置文件中设置autoMappingBehavior属性值为true,
我们是不建议在多表查询时开启的,因为这需要高度规范的命名
-->
<id column="id" property="id"></id>
<result column="num" property="num"></result>
<result column="name" property="name"></result>
<result column="gender" property="gender"></result>
<result column="birthday" property="birthday"></result>
<result column="oper_time" property="operTime"></result>
<!--
association:用来封装关联的对象信息
property="dorm", 就是创建一个关联的对象
-->
<association property="dorm" javaType="Dorm">
<!--
这里的column是数据库中的列名或者我们定义的列的别名
property是Dorm类中的属性
-->
<result column="dormNum" property="num"></result>
</association>
<association property="admin" javaType="Admin">
<result column="account" property="account"></result>
</association>
</resultMap>
<select id="findStudentById" parameterType="int" resultMap="studentMap">
SELECT
s.id,
s.num,
s.name,
s.gender,
s.birthday,
d.num dormNum,
a.account,
s.oper_time
FROM student s
LEFT JOIN dorm d ON s.dormid = d.id
LEFT JOIN admin a ON s.adminid = a.id
WHERE s.id = #{id}
</select>
测试:
@Test
public void find() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
Student student = studentDao.findStudentById(1);
sqlSession.commit();
sqlSession.close();
}
-
在这里还要补充一点:如果在拿到学生信息后,需要获取学生的宿舍号,首先要拿到学生对象中的Dorm对象,然后再获取Dorm对象中属性
System.out.println(student.getDorm().getNum());
-
前端也一样
-
-
注意:查询多个学生信息只需要将sq语句中的WHERE s.id = #{id}这个条件给去掉,resultMap和查询一个是一样的
List<Student> findStudents();
方法二:嵌套查询(把sql分成多次查询,先查询学生信息,再通过外键查询宿舍和操作人信息)
<resultMap id="studentMap" type="Student">
<id column="id" property="id"></id>
<result column="num" property="num"></result>
<result column="name" property="name"></result>
<result column="gender" property="gender"></result>
<result column="birthday" property="birthday"></result>
<result column="oper_time" property="operTime"></result>
<!--
select="findDormById" 查询语句id
column="dormid" 将查询到的dormid作为条件再查询
javaType="Dorm" 类型
-->
<association property="dorm" column="dormid"
select="findDormById" javaType="Dorm"></association>
<association property="admin" column="adminid"
select="findAdminById" javaType="Admin"></association>
</resultMap>
<select id="findStudentById" resultType="Student" resultMap="studentMap">
select id,num,name,gender,birthday,dormid,adminid from student where id = #{id}
</select>
<select id="findDormById" resultType="Dorm">
select num from dorm where id = #{dormid}
</select>
<select id="findAdminById" resultType="Admin">
select account from admin where id = #{adminid}
</select>
测试:
案例二
-
在查询宿舍的同时查询出这个宿舍中所住学生的信息
方法1:关联查询
类中属性定义:
Dorm findDormById(int id);
<resultMap id="dormMap" type="Dorm">
<id column="id" property="id"></id>
<result column="num" property="num"></result>
<association property="admin" javaType="Admin">
<result column="account" property="account"></result>
</association>
<!--
property="students" 属性
javaType="list" students的类型(list为类型简称)
ofType="Student" 集合里所装数据的类型
-->
<collection property="students" javaType="list" ofType="Student">
<result column="snum" property="num"></result>
<result column="name" property="name"></result>
</collection>
</resultMap>
<select id="findDormById" resultMap="dormMap" resultType="Dorm">
SELECT
d.id,
d.num,
s.num,
s.name,
a.account
FROM dorm d
LEFT JOIN admin a ON d.adminid = a.id
LEFT JOIN student s ON d.id = s.dormid
WHERE d.id = 1
</select>
测试:
@Test
public void find(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
DormDao dormDao = sqlSession.getMapper(DormDao.class);
//通过id查询宿舍和这个宿舍中所住学生的信息
Dorm dorm = dormDao.findDormById(1);
sqlSession.close();
}
方法2:嵌套查询
<resultMap id="dormMap2" type="Dorm">
<id column="id" property="id"></id>
<result column="num" property="num"></result>
<association property="admin" column="adminid" javaType="Admin" select="findAdminById1"></association>
<collection property="students" javaType="list" ofType="Student" column="id"
select="findStudents1"></collection>
</resultMap>
<select id="findDormById1" resultMap="dormMap2" resultType="Dorm">
SELECT id,num,adminid FROM dorm WHERE id = #{id}
</select>
<select id="findAdminById1" resultType="Admin">
SELECT account FROM admin WHERE id = #{adminid}
</select>
<select id="findStudents1" resultType="Student">
SELECT num,NAME FROM student WHERE dormid = #{id}
</select>
测试:
-
查询所有的宿舍及关联的学生信息
方法1:关联查询
List<Dorm> findDorms();
<select id="findDorms" resultMap="dormMap">
SELECT
d.id,
d.num,
s.num,
s.name,
a.account
FROM dorm d
LEFT JOIN admin a ON d.adminid = a.id
LEFT JOIN student s ON d.id = s.dormid
</select>
测试:
@Test
public void find(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
DormDao dormDao = sqlSession.getMapper(DormDao.class);
//查询每个宿舍,并关联每个宿舍中有多少个学生
List<Dorm> dorms = dormDao.findDorms();
/*
对查询结果进行获取输出
*/
// 遍历所有宿舍
for (Dorm dorm:dorms){
System.out.println(dorm);
// 获取到宿舍中所有学生信息
for (Student student:dorm.getStudents()){
System.out.println(student.getNum()+":"+student.getName());
}
}
sqlSession.close();
}
可以看到查询出了所有的宿舍以及宿舍所住学生的信息:
方法2:嵌套查询
<resultMap id="dormMap" type="Dorm">
<id column="id" property="id"></id>
<result column="num" property="num"></result>
<association property="admin" column="adminid" javaType="Admin" select="findAdminById"></association>
<collection property="students" javaType="list" ofType="Student" column="id"
select="findStudents"></collection>
</resultMap>
<select id="findDorms" resultType="Dorm" resultMap="dormMap">
SELECT id,num,adminid FROM dorm
</select>
<select id="findAdminById" resultType="Admin">
SELECT account FROM admin WHERE id = #{adminid}
</select>
<select id="findStudents" resultType="Student">
SELECT num,NAME FROM student WHERE dormid = #{id}
</select>
测试:
9 注解方式
常用注解标签:
注解 | 标签 | 说明 |
---|---|---|
@Insert | insert | 添加操作 |
@Select | select | 查询操作 |
@Update | update | 更新操作 |
@Delete | delete | 删除操作 |
@Param | 入参,eg:Admin find(@Param("column") String column); | |
@Results | resultMap | 封装结果集,与@Result一起使用,@Results({@Result(),@Result()})或@Results(@Result()) |
@Result | result | 结果集中表字段与类属性映射 |
@One | association | 一个结果,@Result属性one的赋值,@Result(one=@One(select=“查询方法的全路径”)) |
@Many | collection | 多个结果,@Result属性many的赋值,@Result(many=@Many(select=“查询方法的全路径”)) |
使用案例
查询所有信息
@Select("select * from t_emp")
@Results(id = "empMap",
value = {@Result(column = "emp_id",property = "empId",id = true),
@Result(column = "emp_name",property = "empName"),
@Result(column = "emp_tel",property = "empTel"),
@Result(column = "emp_education",property = "empEducation"), @Result(column = "emp_birthday",property = "empBirthday")})
List<Employee> getAll()
查询单个信息
@Select("select num,name from student where id = #{id}")
Student findStudent(int id);
删除信息
@Delete("delete from student where id = #{id}")
void deleteStudent(int id);
更新信息
@Update("update student set num = 1003 where id = #{id}")
void updateState(int id);
插入信息
@Insert("insert into t_emp (emp_id,emp_name,emp_tel,"+"emp_education,emp_birthday, fk_dept_id"+")"
values (#{empId},#{empName},#{empTel},"+"#{empEducation},#{empBirthday},# {fkDeptId}"+")")
int insert(Employee record)
10 动态 SQL
案例引入:
点击学生管理和查询按钮查询的都是学生信息,那么这两个功能用的是同一条查询语句还是不同的(或者说是后端调用的是一个方法还是两个方法)?
- 答案当然是调用同一个方法,只不过点击学生管理时没有姓名或学号约束,在点击查询的时候有可能是通过姓名查询,有可能是通过学号查询,还有可能通过姓名和学号两个条件查询,但是两个功能的核心sql其实只有一条(select * from student),在点击学生管理时只不过姓名和学号传入的是NULL值,在点击查询通过学号或者姓名查询时,学号和姓名哪个不为空就将哪个值拼接到where子句后,这就要用到动态 sql 来实现了,用不同的方法起码需要三条sql(姓名为空,学号为空,两个都为空),非常麻烦,所以最好的办法就是使用mybatis提供的动态sql,在sql中加入判断。
什么是动态sql
-
可以根据具体的参数条件,来对 sql 语句进行动态拼接
-
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
比如在以前的开发中,由于不确定查询参数是否存在,大多程序员会使用类似于where 1 = 1 来作为前缀,然后后面用and拼接要查询的参数,这样就算要查询的参数为空,也能够正确执行查询,如果不加1 = 1,则参数为空的话, SQL 语句就会变成SELECT * FROM student WHERE ,SQL不合法。
<select id="findStudents" resultType="Student">
select * from student
where 1 = 1
<if test="num!=null">
and num = #{num}
</if>
<if test="name!=null">
and name = #{name}
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
</where>
</select>
MyBatis 中用于实现动态 SQL 的元素主要有:
-
If
-
可以对传入的条件进行判断
-
if test= "条件"
//方法定义
Student findStudent(Student student);
<!-- 示例 -->
<select id="findStudent" resultType="Student">
SELECT * FROM student WHERE gender = #{gender}
<if test="name != null">
AND name like #{name}
</if>
</select>
测试:
@Test
public void findStu() {
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
StudentDao studentDao1 = sqlSession1.getMapper(StudentDao.class);
Student student = new Student();
student.setName("张三");
student.setGender("男");
studentDao1.findStudent1(student);
sqlSession1.close();
}
-
where
-
对于查询条件个数不确定的情况,可使用元素。
-
当 where 标签内的 if 有成立的,就会动态添加一个 where 关键字
-
如果 where 后面有 and、or 这种关键字,也会动态删除
-
<!-- 示例 -->
<select id="findStudents" resultType="Student">
select * from student
<where>
<if test="num!=null">
and num = #{num}
</if>
<if test="name!=null">
and name = #{name}
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
</where>
</select>
测试:
- trim
- where 标签其实用 trim 也可以表示,当 WHERE 后紧随 AND 或则 OR 的时候,就去除 AND 或者 OR。
- prefix 前缀,prefixOverrides 覆盖首部指定内容
<!--
<trim prefix="where" prefixOverrides="and|or">
trim 可以自定义指定关键字,覆盖掉指定开头的关键字
-->
<select id="findStudents" resultType="Student">
select * from student
<trim prefix="where" prefixOverrides="and|or">
<if test="num!=null">
and num = #{num}
</if>
<if test="name!=null">
and name = #{name}
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
</trim>
</select>
- set
- set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
-
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
-
为什么要修改的值会是空的?这里我们来说一个使用场景:
-
在疫情期间,我们如果要回家,需要向学院办理请假手续,在学院审批同意后再回家,在填写请假条时,我们只填写我们需要填写的部分(请假原因、开始时间、结束时间...),还有一部分是需要导员填写的(意见、时间、审批人...),这时我们在提交时导员需要填写的部分就是空值,导员在提交时,也就是做一次修改操作,我们需要填写的部分就是空值,通过动态 sql,我们只需要写一个方法就可以实现,哪些不为空就修改哪些。
-
<update id="updateStudent">
update student
<set>
<if test="num!=null">
num = #{num},
</if>
<if test="name!=null">
name = #{name},
</if>
<if test="gender!=null">
gender = #{gender},
</if>
</set>
where id = #{id}
</update>
或者,你可以通过使用*trim*元素来达到同样的效果:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
- choose (when, otherwise)
-
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
-
传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。
-
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
- foreach
-
主要用在构建 in 条件中,它可以在 SQL 语句中进行迭代一个集合。
-
foreach 元素的属性主要有 item,index,collection,open,separator,close。
-
item 表示集合中每一个元素进行迭代时的别名
-
index 指定一个名字,用于表示在迭代过程中,每次迭代到的位置
-
open 表示该语句以什么开始
-
separator 表示在每次进行迭代之间以什么符号作为分隔符
-
close 表示以什么结束
-
在使用 foreach 的时候最关键的也是最容易出错的就是 collection 属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的。
-
如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
-
如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
-
-
-
案例:模拟批量删除
void deleteStudent(List<Integer> list);
<!--
从list集合中每次拿出一个元素,赋给item变量
-->
<delete id="deleteStudent">
delete from student where id in
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
测试:
@Test
public void delete(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(6);
list.add(7);
studentDao.deleteStudent(list);
sqlSession.commit();
sqlSession.close();
}