MyBatis
JavaEE三层框架:表现层、业务层、持久层。
现在开始学习持久层。持久层就是负责与数据库打交道的代码。
框架:就是一个半成品软件。在框架的基础上,可以更加高效地写出代码。
1、MyBatis快速入门
1、准备工作(创建springboot工程、导入mybatis和mysql的依赖、编写实体类、编写配置类)
2、注解开发:编写接口,使用注解编写sql语句(注解开发比较简单,先使用注解开发来进行)
3、测试。
2、数据库连接池
什么是数据库连接池?
-
数据库连接池是个容器,负责分配、管理数据库连接( Connection )
-
它允许应用程序重复使用一个现有的数据库连接/而不是再重新建立一个
-
释放空闲时间超过最大空闲时间的连接/来避免因为没有释放连接而引起的数据库连接遗漏
如果没有数据库连接池会怎么样?每一次连接数据库都要创建一个数据库连接,用完后需要立刻销毁连接以免造成资源浪费。
数据库连接池的优势:
- 资源重用
- 提升系统响应速度
- 避免数据库连接泄漏
sun公司在Java中提供了一个数据库连接池的标准接口,之后其他公司生产了不同的数据库连接池产品。C3P0和DBCP现在基本上也已经不用了,用的是德鲁伊和追光者Hikari。追光者是springboot默认使用的连接池。
切换druid连接池非常方便,导入依赖然后配置文件中改一下即可。
3、注解开发
就是直接在接口方法上写个注解,注解里直接写sql语句就可以了。但是,注解只适合简单的语句开发,如果想要复杂的功能的话,注解开发是非常麻烦的,这时候还是使用xml文件完成sql语句的编写更好。
在接口方法上的注解有以下几类:
-
@Select
接口方法的返回值需要是一个实体,且实体的字段必须跟表字段一致。这样一来,查询到的结果会自动封装到实体之中。如果实体字段和表字段名字不一致,封装是不会成功的。然而问题来了,Java字段命名方式是小驼峰命名,mysql字段命名方式是下划线命名,总不能为了兼容强行把Java的字段名改成下划线命名或者强行把MySQL字段名改成小驼峰命名吧?
解决方式有多种。
第一种就是在@Select的sql语句中用
as
给MySQL表字段起别名,别名跟Java的实体字段名字一致。第二种是使用mybatis提供的@Results注解和@Result注解进行手动映射。
**第三种方式是最方便的。**在配置文件中写入配置让mybatis开启下划线和驼峰命名的映射。
另外,在进行条件查询时,会出现问题。
方法是使用字符串拼接函数
concat
。 -
@Delete
-
@Insert
如果想要获得插入之后的主键,可以添加一个注解,让插入之后生成的主键赋值给实体的id属性。
-
@Update
接口方法之中可以传入参数,注解中对应位置使用#{}
进行代替。
Java会通过反射获取到接口方法上的注解中的sql语句,然后把sql语句发送给MySQL数据库执行。MySQL数据库接收到sql语句,会先去缓存中查找是否存在这条sql语句,如果是直接执行,如果否,先进行sql语法检查,然后对sql进行优化,然后编译sql,然后把编译好的sql存入缓存以便下次执行一样的sql语句时提高性能。这种方式称为预编译sql。预编译SQL语句的过程是将SQL语句的结构和参数分离,先将SQL语句编译为一个可执行的执行计划,然后在每次执行时只需传入参数,无需重新编译,从而减少了重复解析和编译的开销。
delete from emp where id = #{id}
实际上传给MySQL的是delete from emp where id = ?
以及id的值。如果直接传给MySQL的语句是delete from emp where id = id
,那预编译sql就不起作用了。
预编译sql可以提高性能,另外,也可以防止sql注入攻击。
sql注入攻击是怎么一回事呢?假设Java传给mysql的不是预编译sql形式的sql语句,而是一个拼接成的完整sql语句select count(*) from user where name='' and password=''
,以查询账号和密码进行登录操作。这时候,有人在表单中,随便写了个账号,但是密码写入'or '1'='1
,传给MySQL的语句是select count(*) from user where name='fjewa;ofj' and password=''or '1'='1'
,这条sql语句永远会成功,用户无论是何种身份都可以登录。
预编译sql可以解决这个问题,因为它明确了where的字段只有name和password,不会有后面的or ‘1’ = ‘1’。
4、日志输出
为了更加方便查看执行什么sql语句以及执行的结果,需要查看日志。Mybatis默认是不打开日志的,需要在配置文件中进行配置。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
5、xml映射文件开发
注解开发虽然方便,但是只能执行简单的sql逻辑。想要执行复杂的sql逻辑,需要使用xml文件编写sql语句进行开发。xml映射文件有一些规范。
- xml映射文件的名称与Mapper接口名称一致,并且将xml映射文件和mapper接口放置在相同包下(同包同名)。在maven项目中,最后编译之后,main包下的java和resource两个目录都会消失的。也就是说最后java和resource里面的文件都会在一个目录下。这样一来,就可以在resource下创建sql映射文件,在java里面创建接口,只要sql映射文件和接口文件的目录写成一样的,最后编译它们就会是一样的。注意,在resource下创建层次接口不能是“com.ghl”的形式,而是"com/ghl"的形式。
- xml映射文件的namespace属性为Mapper接口全限定名一致。(方便映射)
- xml映射文件中sql语句的id与mapper接口中的方法名一致,并保持返回类型一致。(方便映射)
xml文件长什么样呢?
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.iss.mapper.EmpMapper"><!--resultType:实体的全限定名,执行结果会封装到实体之中--><select id="selectEmp" resultType="com.iss.entity.Emp">select * from emp where id = #{id}</select>
</mapper>
想要调用接口中的方法来执行sql语句,接口中的方法肯定是没有这样的功能的,只有xml文件中才有sql语句。所以关键在于,如何将接口中的方法与xml文件进行映射。只要按照上边的规范正确地在xml文件中写内容,当调用接口中的方法时,mybatis会自动根据接口找到对应的xml文件,然后根据id找到方法对应的sql语句。
有一款插件可以挺方便根据接口跟xml文件的映射关系进行相互定位。MybatisX
。
查询
xml映射文件中使用select标签来进行查询。这个标签的最重要的属性有两个,一个是id(用来和接口方法进行对应的),一个是resultmap(用来把查询到的数据封装进实体类的)
准备工作:
-
首先,由于在实体对象里,字段名使用驼峰命名,但是在数据库的字段名有时候并不是使用驼峰命名,所以有时候可能数据库的字段名与实体类的驼峰命名对应不上,这样会使得数据无法完成封装。解决方法是:在映射文件里的mapper标签里写一个resultmap标签,这个resultmap标签里面主要有两种小标签id和result。id主要用来进行主键进行映射,result主要用于对普通字段进行映射。两种小标签都有两个属性,分别是column(真正的字段名)和property(实体类的驼峰命名)。写完之后把resultmap作为查询标签的属性就可以进行映射了。
-
接着,查询语句里要有参数占位符用来指代接口方法传入的参数,有两种参数占位符:#{参数名},${参数名},前一种可以用来防止sql恶意注入(黑客攻击),后一种无法防止sql注入,安全性会差一点。
-
注意,在核心配置文件里面,datasource标签里url那里一定要设置一个参数指定utf-8字符集,不然字符串字段查询到的内容总是null
跟普通的sql查询差不多。就是可能有参数占位符存在。
要注意一点,小于号要使用转义字符来写,因为在xml文件里边,小于号被认为是标签的起始,不会被认为是小于号。使用转义字符& lt ; (这里由于markdown自动识别成小于号了所以我直接这里加了两个空格进行处理)。
动态查询/动态sql
有这么多个字段,怎么知道用户想要查询哪个字段或者想要查询哪些字段呢?当然可以把所有可能全部写一遍,但是这样如果字段太多,会显得非常愚蠢。所以可以使用一种比较好的方法:动态查询。来实现动态查询多个字段的功能。
- 使用if标签(配合where标签使用)来实现动态查询
要实现动态查询,其实就是在每一个条件上加上一个判断,只要判断传入的参数不为空,就使用这个参数进行查询就可以了。
所以使用if标签把条件给括起来(逻辑符也要)就可以了。但是if标签有一个问题,就是如果传入的第一个参数为空,这个参数恰好在第一个条件,那么会变成 where and 字段名 = 参数2 这样的形式,这不符合sql语法要求。**可以直接在where关键字的第一个条件写上1=1,这样就不会出现这样的问题。不过mybatis提供了一个where标签,直接使用where标签来代替where关键字就不会出现问题了,**这个标签会自动处理。where标签只会在子元素有内容的情况下才会插入子句,而且会自动去除子句开头的and和or。
if标签有一个属性test,要在test里面写判断,在标签体里写查询条件。
添加、修改和删除
- 添加
使用insert标签进行数据添加。里面直接写insert into语句进行添加就可以了。
一个小细节。如果主键是默认自增,而且在执行接口方法的时候传入了一个没有定义主键的实体对象去添加,数据库里产生了主键,然而如何知道主键是什么呢?这个问题的解决方法可以在insert标签里设置两个属性来解决。两个属性配置上,实体中的主键字段在数据库添加完毕之后就会自动被赋值。
- 修改
使用update标签进行修改。
调用接口方法传入一个实体对象,会根据实体对象的主键值和实体对象中每一个对象对应的值对对应记录的值进行修改。
动态修改:如果按照上边那种方法来写的话,那么每一次必定是修改数据库表中的全部字段。如果只想要修改某一些字段的话,就可以使用动态修改。即在set关键字下方写上if标签。这里同样会有一些bug出现,所以要将set关键字换成set标签。mybatis会自动解决bug冲突。
- 删除
**删除单条记录:**写在delete标签里面。同样地如果想要动态删除,使用where标签和if标签。
删除多条记录:
这里如果不在xml文件里实现的话,其实可以定义一个数组,将要删除的记录的主键全部放到数组里边去,然后遍历数组,将数组里的元素一个一个传入接口方法里边就行。
不过mybatis对于这个东西有一个标签可以完成删除多条记录的功能。即foreach标签。这个标签用在where后边。
foreach有三个属性,collection用于接收接口方法传入的数组,mybatis会自动把这个数组放到一个map的值里边,键是array。所以collection属性里直接写array就可以了。item属性就是要遍历的字段,写上主键字段的名称就可以了。separator属性和open和close属性都是用来规范sql语法的。因为in后要有括号,括号中的每一个字段之间要有逗号隔开。
两个标签:sql
和include
。这两个标签是配套使用的。
在一个xml文件中,如果出现了大量的重复sql片段,可以使用这两个标签来解决。
<select id="selectAll" resultMap="BrandResultMap">select id,brand_name,enterprise_name,sort,introduction,statusfrom brand;
</select>
<select id="selectById" resultMap="BrandResultMap">select id,brand_name,enterprise_name,sort,introduction,statusfrom brandwhere id = #{id};
</select>
使用这两个标签过后,会变为:
<!--id就是用来标识这段sql代码的-->
<sql id="commonSql">select id,brand_name,enterprise_name,sort,introduction,status
</sql>
<select id="selectAll" resultMap="BrandResultMap">/*把sql的id填入refid*/<include refid="commonSql"/>from brand;
</select>
<select id="selectById" resultMap="BrandResultMap">/*把sql的id填入refid*/<include refid="commonSql"/>from brandwhere id = #{id};
</select>