1.Mybatis的介绍
Mybatis是针对持久层的框架,能力只能和数据库进行交互
Mybatis的底层也并没有其他新奇的东西,只是对jdbc的封装
Mybatis是一个半自动化的ORM框架
- ORM(Object,Relation,Mapping)
- Java中的实体类通过映射文件和关系表映射到一起,以面向对象的方式和实体类进行交互,操作会持久化到数据库的关系表
由于Mybatis是半自动化的ORM框架,Mybatis是将sql语句的返回结果和实体类进行映射
我所了解的自动化的ORM框架有hibernate,他可以实现包括连sql语句都为用户生成,但是由于我们在开发的过程中,sql语句的编写具体是依赖于具体的业务需求,这一点他无法帮我们实现,所以逐渐被Mybatis替代
2.Mybatis基于mapper接口实现
Mybatis提供了基于动态代理的方式实现与数据库的交互
提供好mapper层的接口,提供好接口层对应的映射文件,Mybatis会基于接口的映射文件自动的创建一个代理对象(接口的实现类)
代理对象内部会自动的调用Sqlsession的select,insert等方法与数据库交互
当前这种实现的方式需要满足4个要求:
- 接口名与映射文件名称保持一致
- 接口与映射文件放在同一目录下(后期可以不满足)
- 映射文件的namespace属性写接口的全路径
- 接口中的抽象方法和映射文件的sql一一对应(方法名&id,返回结果&resultType,方法参数¶meterType )
PS:如果出现org.apache.ibatis.binding.BindingException异常,说明四个要求没有满足……
3.Mybatis的核心类
- Resources:是用于加载配置文件生成流,反射也可以实现
SqlSessionFactoryBuilder
:构建SqlSession工厂,由于工厂一个足矣,所以SqlSessionFactoryBuilder
生命周期用过即丢- SqlSessionFactory:应当保证
SqlSessionFactory
全局唯一 - SqlSession:与数据库交互的核心对象
- Executor:
SimpleExecutor
底层调用了原生的JDBC,与数据库交互
以下是Mybatis中的executor(执行器):
默认使用的是SimpleExecutor,最简单的执行器,直接根据sql执行,不会进行额外的操作
ReuseExecutor:可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能
BatchExecutor,执行批量操作的执行器
CachingExecutor:启用于二级缓存时的执行器.
4.Mybatis的核心配置文件
4.1 properties
可以将连接数据库的信息保存在外部的properties文件中,通过properties标签引入外部的properties文件,通过${key}的方式引入properties文件中的value
4.2 settings
设置Mybatis的一些运行时的信息
具体可以查看官网的配置信息:mybatis – MyBatis 3 | 配置
介绍一下常用的驼峰映射的开启:
4.3 typeAliases(了解)
基本不会使用,了解型的知识
别名,在映射文件编写SQL时,传入参数或者返回结果是Entity实体类,需要编写实体类的全路径。
由于全路径太长,可以通过typeAliases配置别名,编写映射文件时,更方便
4.4 plugins
4.5 environments
环境
4.5.1 transactionManager标签
事务的话后期我们都是交给spring去管理的,这里的话不需要太多的关注
4.5.2 dataSource标签
"POOLED"这是mybatis内置的一款数据源,实际的开发中我们一般使用的都是阿里的德鲁伊连接池,这里为了方便测试,使用的是内置的数据源
4.6 mappers
加载外部的映射文件
<mappers>
这个是扫描使用逆向工程生成的xml文件:
<!-- resource: 编写类路径
url: 编写配置文件绝对路径
class:基于Mapper接口实现时的Mapper接口
如果后期项目表较多,需要引入大量的mapper标签,成本太高
-->
<!-- <mapper resource="com/项目名/mapper/UserMapper.xml"/>-->
这个扫描的是使用逆向工程生成的mapper接口:
<!-- 采用package标签,扫描包的形式找映射文件-->
<package name="com.项目名.mapper"/>
</mappers>
5.Mybatis的映射配制文件(mapper.xml文件)_重要
5.1 select(引入参数,传入多个参数)
在编写select查询语句时,需要将语句放到select标签内
select标签常用的属性:
id:与抽象方法的方法名一致
resultType:与抽象方法的返回值一致,如果返回的结果是List或者Set,需要写泛型内的类型
parameterType:与抽象方法的参数类型一致,如果传入一个参数可以使用parameterType,多个参数后面建议使用注解的方式
穿插的小问题:
①为什么#{}可以防止SQL注入,为什么${}不能防止SQL注入,什么时候用${}
#{}:可以防止SQL注入,采用的是预编译的方式,使用的是PreparedStatement,而且使用了占位符
${}:不可以防止SQL注入,使用的是直接将值拼接到SQL语句上,使用的是Statement,使用的场景:当我们传入的参数要作为字段名,表名等内容时,必须使用${}
②传入多个参数的处理方案(只写建议使用的)
<!--
/**
* 根据名字和年龄范围查询用户信息
* @return
*/
List<User> findByNameAndAgeLessThan(@Param(value = "name") String name,@Param("lessAge") Integer age);
-->
<!--<select id="findByNameAndAgeLessThan" resultType="User">
select * from user where name = #{name} and age <![CDATA[ < ]]> #{lessAge}
</select>-->
<select id="findByNameAndAgeLessThan" resultType="User">
select * from user where name = #{name} and age <= #{lessAge}
</select>
在mapper接口中使用@Param()注解指定,mapper.xml文件中无法使用一些常用的特殊符号,只能使用转义字符,比如上面<相当于<;建议使用<![CDATA[ < ]]>,这个符号里写啥代表啥
③采用实体类传参,传入实体类后,在映射文件通过parameterType设置类型,通过属性名获取属性值
④采用Map传参 ,传入参数比较复杂,比较多时,可以采用Map传参
5.2 insert
增删改操作的返回结果都是int类型,代表几行受影响
如果添加的数据表主键生成的策略是自增,要获取自增后的主键值,Mybatis提供了两种方案(主键回填)
在<insert>标签中,添加两个属性useGeneratedKeys="true"
, keyProperty="实体类的oid名称"
在<insert>标签体内,追加额外的标签,采用MySQL的函数获取自增后的主键值
具体实现:
5.3 update和delete
正常的修改和删除,没有什么特别之处
5.4 resultMap
编写<select>标签时,需要编写resultType属性,目的是将ResultSet结果集封装到resultType类型中。
因为select语句的返回结果的列名与实体类的属性名一致,所以可以自动映射到实体类中……
如果SQL语句返回结果的列名与实体类的属性名不一致,需要手动映射结果集和实体类
……
MyBatis提供了<resultMap>标签,来做手动映射操作。
默认情况下,MyBatis的autoMappingBehavior
属性为Partial
,代表没有嵌套结果可以自动映射,但是,即便提供了自动映射,只要用了<resultMap>就全部手动映射。
6. Mybatis的多表查询
6.1多对一关系
关系表:
实体类:
映射文件:
PS:resultMap中,这里的映射为一个实体类,所以使用的是association,与下面的集合映射区分开
6.2 一对多关系
关系表:
实体类:
映射文件:
PS:这里的映射是一个集合,所以使用的是collection,注意区分
6.3 多对多关系
关系表
实体类
映射文件:
7.动态sql
7.1 if+where
if标签解决了逻辑判断问题,可以在判断非空后,再拼接到SQL中
where标签解决了条件前多一个and/or,或者where中没有查询条件时的问题
比如上述的sql语句,如果传入的实体类中的brand字段为空,因为我们加了if标签,brand为null的情况下,动态sql就不会为我们拼接,假如下面的两个字段不为空的话,如果没有where标签,此时的sql语句就是 select * from shoe where and color="" and style="",很显然,这样的语句不满足sql的语法就会执行错误,where标签就可以帮助我们解决这个问题.
7.2 if+set
if标签解决了逻辑判断问题,可以判断非空后,再拼接到SQL中
set可以代替SET关键字,并且去掉后缀多余的一个,
Ps:和上面的where基本一样,不做赘述
7.3 if+trim
trim可以实现set标签和where标签的功能
trim提供了四个属性:
-
prefix=追加前缀
-
suffix=追加后缀
-
prefixOverrides=去除多余前缀
-
suffixOverrides=去除多余后缀
7.4 foreach 批量操作
如果SQL语句的拼接需要用到循环的方式,在映射文件中可以使用<foreach>
<foreach
collection="array或者list或者map-key"
open="以什么开始"
separator="间隔"
close="以什么结束"
item="遍历出来的值的别名"
index="遍历次数">
#{遍历出来的值的别名}, #{遍历次数}
</foreach>
Ps:foreach的执行的本质就是sql的拼接,细品
8.Mybatis的缓存
8.1 Mybatis的一级缓存
一级缓存的特点:
- 默认开启
- 作用域是sqlSession级别的(sqlSession对象中的一个hashMap)
- 一级缓存直接缓存的是对象
- 一级缓存基于BaseExecutor实现,直接找PerpetualCache查询map中的数据
执行流程:userMapper.select(参数) ---- DefaultSqlSession.selectList() ----- BaseExecutor.query()查询PerpetualCache中的Map是否有数据,有数据直接返回,没有数据查询数据库,将查询结果存放到一级缓存.
8.2 二级缓存
二级缓存的特点:
- 二级缓存有三开关,全局开关默认开启,映射文件开关默认关闭,select语句开关默认开启
- 二级缓存的作用域是SqlSessionFactory级别
- 二级缓存存储对象序列化的byte[]
- 二级缓存基于CachingExecutor实现,二级缓存会结果一系列的Cache链(Synchronized,Logging,Serialized,Lru),最终找到PerpetualCache中的Map集合
- 二级缓存优先级高于一级缓存……
执行流程:userMapper.select(参数) ---- DefaultSqlSession.selectList() ----- CachingExecutor.query(),经历一套Cache链,然后查询PerpetualCache中的Map是否有数据,有数据直接返回,没有数据查询一级缓存,如果一级缓存没有,查询数据库,将查询结果存放到一级缓存,在commit提交事务后,将数据存储到二级缓存
9.Mybatis的插件
9.1 分页插件
采用的MyBatis支持的PageHelper分页插件
9.1.1 导入依赖
<!-- 分页助手-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
9.1.2 编写核心配置文件
<!-- 插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
9.1.3 实现分页效果
// 设置当前页,和每页显示条数 11条
PageHelper.startPage(1,5);
List<User> userList = userMapper.findAll();
PageInfo<User> pageInfo = new PageInfo<>(userList);
使用这样的方式实现分页,我们的返回的对象就是PageInfo类型的,然后将查询出来的数据直接放入new出来的pageInfo对象中(注意这是使用的传统分页的方式,基本的分页效果可以实现,但是存在问题)
问题的描述:
这是我们使用传统分页的语句:
select * from studnet order by id desc limit (pageNumber-1)*pageSize,pageSize
假设查到的数据如下图:
此时假设我们前端用户每页展示的数据条数为3条,如下图:
此时假设有小编在后台添加了多条数据,传统的分页就会出现问题,如下假设小编添加了3条数据
此时为前端用户假如请求第二页的数据展示数据为:
用户发现数据没有发生变化,出现了问题
Ps:解决的方法:每次将前端用户展示的数据的最后一条的数据的id传给后端
使用下面的语句可以解决传统分页存在的问题:
求下一页数据: select id,name,age from user where id<4 and id>=4-20 order
by id desc limit 2
请求第一页数据: select id,name,age from user where id<=(select max(id) from user) and id>(select convert(max(id),SIGNED)-20 from user) order by id desc limit 2
10.逆向工程
根据表自动生成实体类,mapper接口,mapper映射文件,并且编写好全部单表的增删改查。
MyBatis Generate是MyBatis官方提供的功能,IDEA中有一个款插件,更方便的使用这款插件。
10.1 下载插件
下载Free MyBatis Plugins
10.2 用IDEA的database连接数据库
10.3 生成实体类,Mapper接口,Mapper映射文件