目录
三、MyBatis的配置(Spring Boot项目中)与使用
四、MyBatis中接口文件与xml文件中内容的学习(很重要!!!)
一、为什么学习MyBatis?
在我们进行项目开发时,肯定避免不了和数据库打交道(用来存储我们的数据)。使用SQL语句就可以完成对数据库或者数据库中表的操作,但在开发中,程序员并不仅仅是编写我们的SQL,而是得将SQL与开发的代码结合起来,然后通过程序代码来操作数据库。在JDBC编程这个文档中介绍了在程序中怎么连接数据库,然后在程序中编写代码来操作数据库。虽然我们能通过代码操作数据库了,但是使用JDBC的方法操作很麻烦,每次还得创建数据库连接池,然后获取连接等等,代码冗余还比较繁琐。但我们还得在程序中操作数据库,所以MyBatis就来了,这个可以帮助我们更快速,更方便的操作数据库。那么什么是MyBatis呢?
二、什么是MyBatis?
先给大家来段比较官方的定义:MyBatis 是⼀款优秀的持久层框架(用于与数据库进行交互),它⽀持⾃定义 SQL、存储过程(很少使用,就是在与数据库交互过程中,可以执行一些复杂的语句(判断语句,循环语句等等)但一般不会在存储过程中编写这些语句,如果后续报错了,不好进行调试)以及⾼级映射。MyBatis 去除了⼏ 乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置 和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的 记录。
说白了,大家先明白MyBatis核心是更简单的实现程序与数据库进行连接,更简单的操作数据库的一个半自动框架(虽然Spring Boot和MyBatis帮我们完成了很多,但真正完成交互,还得依靠我们去编写SQL语句)。MyBatis也是一个ORM框架,即对象关系映射。将数据库中的表与程序中的对象映射起来。一般情况下一张表对应程序中一个实体类。
三、MyBatis的配置(Spring Boot项目中)与使用
MyBatis的学习就分为两个部分:1.配置开发环境;2.学习MyBatis的使用,如何操作数据库。
3.1添加项目支持
如果想在项目中使用MyBatis,就需要先引入相关依赖。
(1)对于新建Spring Boot项目,我们可以在创建的时候,选择依赖的时候选择上。(需要引入两个依赖)一个是MyBatis的框架依赖,一个是数据库的依赖(我的数据库是MySQL,所以我得引入MySQL的依赖,大家看自己数据库是啥的,然后引入相关的依赖)为什么引入数据库依赖呢?因为数据库有很多种,可能每种和每种编写方式还不太一样,你得告诉程序你用的啥数据库呀,要不人家如何帮助我们操作呢!
(2)如果我们已经有了Spring Boot项目,我们可以直接在pom.xml文件中引入下面这两个依赖,别忘记更新maven仓库。大家也可以自己再pom.xml文件中引入。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
3.2设置MyBatis的配置信息
3.2.1添加完依赖后,项目启动失败
可能会有心急得小可爱,配置完上面的依赖后就启动项目了,但是会发现报错了,项目无法启动。
这是为啥呢?我们在Spring Boot项目在启动时会自动根据类路径下的依赖和配置文件来配置应用程序,所以,对于数据源,Spring Boot也提供了自动配置。当我们想要对数据库进行操作时,引入了MyBatis依赖后,启动项目时,会检查数据源的配置(配置文件),如果缺少数据源的URL,就会抛出这个错误信息。(因为URL是连接数据库的关键参数之一,指定了数据库的位置和连接方式,Spring Boot启动后,会期望有一个URL来进行数据库的操作,所以找不到时就会报错)
3.2.2配置连接字符串和MyBatis
url的含义在jdbc编程那篇文章介绍过了,这里就不再介绍
application.yml
#配置数据库的url和用户名密码
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&useSSL=false
username: root
password: rootroot
#配置数据库驱动类的类名(mysql为5.x以上的为com.mysql.cj.jdbc.Driver,为5.x以下的为#com.mysql.jdbc.Driver)加载这个为了保证在项目启动后,知道要加载哪个启动类,因为有很多数据库
driver-class-name: com.mysql.cj.jdbc.Driver
#配置MyBatis中的XML路径
#classpath:表示当前配置文件的根路径
#mapper为一个文件夹(自己创建的,名字可以自己取)用来存放操作数据库的xml文件
#*Mapper.xml表示文件名的后半部分是Mapper,后缀是xml(Eg:UserMapper.xml)这部分也是自己定义的(名字也可以随便起,但是你随便起名字要有个共性,在配置文件中能向下面一样提炼出来)
mybatis:
mapper-locations: classpath:/mapper/*Mapper.xml
3.3一个完整项目的流程
大致流程为:前后端是通过控制层进行交互的,在控制层会注入业务层,调用业务层的一些方法。在业务层进行事务的处理,并且注入了Mapper层,调用Mapper的一些方法。(Mapper层负责与数据库进行交互的)Mapper层:(看上面得图)我们会创建一个接口和一个xml文件,接口得方法由xml文件实现,当项目启动得时候,MyBatis会进行整合,将接口对应得xml整合成一个,然后会生成个代理对象,其他层调用得正是这个代理对象。在Mapper层中,对数据库进行操作后会得到结果,将得到的结果返回给业务层,然后业务层返回给控制层,在通过控制层返回给前端。
3.4编写程序来测试能否正确操作数据库
我们查询表中的数据,如果能查出来,就表示连接成功。
1.创建一个实体类,用来对应数据库中某个表。(映射)
package com.example.mybatisdemo1.enity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserEntity {
//要尽量保证属性名和字段名一致,因为一致得时候MyBatis会自动帮你进行关联
//接收得时候,我们保证Java中类型比对应字段的类型大或者相等就行
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer state;
}
2.添加Mapper层接口,并编写相关方法。我们在这个类上添加了一个注解@Mapper,这个注解主要用于指示该接口是MyBatis的Mapper接口,告诉这个接口到时候生成这个接口的代理对象,代理对象会组合接口和对应的xml文件,然后完成数据库的操作。
package com.example.mybatisdemo1.dao;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper//表示当前类不是一个普通类,是MyBatis得组件之一
public interface UserMapper {
List<UserEntity> getAll();
}
3.创建接口对应的xml文件,我们在3.2.2的时候配置了MyBatis的xml路径,我们要根据我们已经配置好的路径,在对应得地方创建对应得文件,到时候MyBatis框架进行整合得时候就不容易出错了, 因为我们在配置文件中已经告诉了xml的位置,你如果放在其他地方,那MyBatis会找不到的。
这段代码大家不用记住,每次创建了xml文件后,就把这段代码粘贴过去,但是mapper标签中的namespace这个属性值大家要改成自己对应接口的。
<?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.example.mybatisdemo1.dao.UserMapper">
</mapper>
编写接口对应的xml文件中的sql语句(会用到select标签,这个标签会有一个属性为id,这个id名为接口中对应方法的名字(不可以出错!!!))
4.为了符合开发的规范,我们再创建控制层,业务层,一层层调用。
Controller层
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getAll")
public List<UserEntity> getAll() {
return userService.getAll();
}
}
service层
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<UserEntity> getAll() {
return userMapper.getAll();
}
}
5.在地址栏中输入url,进行测试,如果出现下面结果表示,连接成功。
四、MyBatis中接口文件与xml文件中内容的学习(很重要!!!)
xml文件是用来盛放sql语句的。我们都知道对数据库最基本的操作就是CEUD(增删改查),所以在xml中最重要的标签也正是这几个标签,但是,xml中最重要的还不仅仅是这个,我们来继续往下看。
4.1常用的增删改查的标签
4.1.1select标签
这个标签必须包含两个属性,否则编译都不通过。一个属性是id,用来记录接口中的方法名(实现接口中哪个方法),另外一个属性是返回类型,用来接收结果的返回值。填写返回的时候又分为两种resultType和resultMap两种,后面这种用于数据库中字段名和实体类中名字不对应得情况,或者多表的时候。(resultMap的使用后面来详细讲解)
目前我们接收的查询结果无非两种,一种是接收一个对象的,一种是接收多个对象的,但我们将返回类型都设置为实体类即可,不用设置为List,在Mapper接口层和业务层控制层中用List接收。
#返回类型为普通实体类的时候(单表操作的时候)
<select id="getAll" resultType="com.example.mybatisdemo1.enity.UserEntity">
select *from userinfo
</select>
4.1.2insert标签
这个标签用于插入操作的,标签中至少必须包含一个属性,就是id,这个id用法与select中的一样。默认的返回类型为整型,表示受影响的行数。我们也可以返回自增的id。通过设置标签中的属性useGeneratedKeys,keyProperty等。
1)useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段,默认值:false。(想返回自增的id,则需要将这个值设置为true)
2)keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)。如果⽣成列 不⽌⼀个,可以⽤逗号分隔多个属性名称。
3)keyColumn:设置⽣成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。
#默认返回受影响行数
<insert id="insertOne">
insert into userinfo
values (1,'张三',18)
</insert>
#返回自增的id
<insert id="insertOne" useGeneratedKeys="true" keyProperty="id">
insert into userinfo
values (1,'张三',18)
</insert>
4.1.3update标签
这个标签用于处理更新数据的时候,要去属性值至少有一个,也正是id。默认返回类型为整型,表示受影响的行数。
<update id="updateOne">
update userinfo set name = '张总' where id = 1
</update>
4.1.4delete标签
这个标签用于删除表中数据,开发中很少使用到,因为这个操作是不可逆的,要去也是至少有一个属性,那就是id。默认返回类型为整型,同样表示受影响的行数。
<delete id="deleteOne">
delete from userinfo where id = 1
</delete>
4.2在接口的方法中传递参数以及在xml中接收参数
我们可以传递单个属性,也可以传递一个对象
4.2.1传递单个属性
我们传递单个属性的时候可以直接这样写,xml中取的时候就要注意必须和参数名字保持一致,因为MyBatis自己会进行映射,如果不一致,那肯定报错!
List<UserEntity> getAllUser(String name);
我们也可以使用注解@Param给参数起别名,只不过起了别名后,我们在xml中就不可以用参数的原名字了,必须使用别名了!!否则也会报错。
List<UserEntity> getAllUser(@Param("n")String name);
4.2.2传递对象
如果传递的参数很多,我们可以封装成一个对象,然后传递对象过去。只不过我们使用对象里面的属性的时候不需要对象.属性,我们直接使用其中的属性就可以,只不过要保证,名字要和对象中的属性要一样!!!!
List<UserEntity> getAll(UserEntity user);
eg:
xml中
<select id="getAll" resultType="UserEntity">
select * from userinfo where id = #{id}//直接使用传过来的user对象中的id属性,而不是user.id
</select>
4.2.3在xml中使用传过来的参数
有两种方式,一种是${},这种是字符直接替换即把${}部分直接替换成变量的值(我们传过来的)。另一种是#{},这种是预编译处理,把SQL中的#{}替换成?号,然后使用PreparedStatement的set方法进行赋值。
(1)如果传递过来的是数值类型,我们使用这两种中的哪一种都可以。
(2)如果传过来的为数据库中的关键字,我们必须使用${}。
(3)其他情况下建议使用#{},比较安全。${}容易发生SQL注入问题:因为这种方式是直接进行替换,所以很多有坏心思的人可以进行字符串的拼接,然后传递过去,来获取或者操作我们的数据库,很不安全。
(4)模糊查询的时候使用#{}也会出错,这时候我们可以使用concat函数concat('%',#{},'%'),或者在其他层将字符串拼接成String s = "%" + username + "%",在把s传过来。
4.3xml中返回类型
4.3.1resultType
这个返回类型绝大多数场景都会用到,可以接收整型,对象等。使用对象接收的时候要求数据库中字段必须和实体类中的完全一致
<select id="getAll" resultType=User>
.....
</select>
4.3.2resultMap
4.3.2.1数据库中字段和实体类中字段不一致
在实际开发中,可能我们创建的实体类的属性与数据库中的字段并不一样,此时我们还想用实体类进行接收,那我们我们需要通过resultMap进行映射。
4.3.2.2一对一表的映射,实体类中存在另外一个类的对象
在resultMap映射的时候,对实体类中的属性也进行映射,有很多种方式这里就展示一种。
<resultMap id="bookResultMap" type="com.abc.Book">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="type" column="type"/>
<!--关联属性-->
<association property="bookShelfDto" javaType="com.abc.BookShelf">
<id property="id" column="shelf_id"/>
<result property="number" column="number"/>
<result property="num" column="num"/>
</association>
</resultMap>
4.3.2.3一对多表的映射
在实体类中存在集合的时候,我们可以在ResultMap映射的时候通过collection标签来进行操作。
<resultMap id="bookShelfResultMap" type="com.abc.BookShelf">
<id property="id" column="shelf_id"/>
<result property="number" column="number"/>
<result property="num" column="num"/>
<!--关联属性-->
<collection property="bookList" javaType="com.abc.Book">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="type" column="type"/>
</collection>
</resultMap>
4.4动态SQL使用
4.4.1if标签
在xml中,可以添加if标签作为判断。一般常用于判断某个字段是否为空。test后面表示传过来的参数。
<if test="name != null and name != ''">
</if>
4.4.2trim标签
<trim>标签中有如下属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
insert into user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">
username,
</if>
<if test="password != null">
password,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="username != null">
#{username},
</if>
<if test="password != null">
#{password},
</if>
</trim>
4.4.3where标签
where标签让代码编写起来更加容易,where的作用等同于<trim prefix="where" prefixOverrides="and">。注意where只会帮助我们去除最前面的前缀,而不会帮我们取出最后面的后缀。
4.4.4set标签
set标签也是为了让代码更简单,编写起来更容易。set标签的作用等同于<trim prefix="set" suffixOverrides=",">这个恰恰相反,只会帮我们去除最后面的后缀,而不会帮我们去除最前面的前缀。
4.4.5foreach标签
如果进行批量删除操作,会需要返回给多个ID给后端,然后数据库对这些ID对应记录进行操作,我们可以在后端代码中依次传递一个ID,然后传递多次进行删除。我们也可以在xml中借助foreach标签进行循环。
foreach标签中属性有:
collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象 ;
item:遍历时的每⼀个对象 ;
open:语句块开头的字符串 ;
close:语句块结束的字符串
separator:每次遍历之间间隔的字符串
<delete id="deleteByIds">
delete from article
where id in
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
五、拓展知识
5.1查看MyBatis帮我们生成的sql
在配置文件中输入下面代码,由于日志打印级别默认是info,如果我们想看到更多打印信息,可以更改级别。
# 打印 MyBatis 执行 SQL
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
level:
com:
example:
mybatisdemo1
5.2单元测试
有时候我们只是想测试一下我们的代码,是否能成功运行,是否有问题等等。在测试的时候我们并不想让测试的数据污染我们的数据库,此时我们就可以使用单元测试进行测试。
(1)在Mapper接口层,可以使用快捷键alt + insert,然后选择Test。
(2)选择需要测试的方法
(3)点击OK生成测试类,我们在生成的类上面添加注解@SpringBootTest,表示测试的类运行在SpringBoot环境下,为啥要加上这个注解呢?因为我们要进行测试,肯定避免不了会注入其他Bean。
(4)编写对应的方法,进行测试
(5)我们进行单元测试的目的是什么呢?不就是不想让测试数据影响我们的数据库,起关键作用的是@Transactional这个注解,在对应方法上加上这个注解,然后执行的时候表示开启事务了,执行完毕后,会自行进行回滚,这样既能帮助我们测试代码还能不污染数据库中数据。
(6)当我们在一个接口中第一次通过上述方法添加测试方法的时候,不会有任何问题,但当我们在同一个接口尝试添加第二个测试方法时,问题出现了。如下图,这是因为,我们在创建第一个测试方法时已经创建了一个类,第二次的时候会告诉我们无法创建这个类,我们不要管这个错误,直接点击OK就行,这样方法直接就会创建到原来已经存在那个类中。