Mybatis学习
简介
1.什么是mybatis
MyBatis 是一款优秀的持久层框架
它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis 可以通过简单的XML或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis
2013年11月迁移到Github
2.持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 数据库(Jdbc),io文件持久化。
为什么需要Mybatis
- 帮助程序猿将数据存入到数据库中。
- 传统的JDBC代码太复杂了。简化。框架。自动化。
- 更容易上手。
- 优点:
- 简单易学。灵活
- sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射。提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql。
搭建Mybatis
1.开发环境
- IDE:idea 2019.2
- 构建工具:maven 3.5.4
- MySQL版本:MySQL 5.7
- MyBatis版本:MyBatis 3.5.7
2.创建maven工程
- 打包方式:jar
- 引入依赖
1 |
|
3.创建MyBatis的核心配置文件
习惯上命名为
mybatis-config.xml
,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。
核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息
核心配置文件存放的位置是src/main/resources目录下
1 |
|
- 注意在数据库和核心配置文件内都要设置字符集为utf8,否则中文会乱码
4.创建mapper接口
MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要提供实现类
1 |
|
5.创建MyBatis的映射文件
- 相关概念:ORM(Object Relationship Mapping)对象关系映射。
- 对象:Java的实体类对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java概念 | 数据库概念 |
---|---|
类 | 表 |
属性 | 字段/列 |
对象 | 记录/行 |
- 映射文件的命名规则
- 表所对应的实体类的类名+Mapper.xml
- 例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
- 因此一个映射文件对应一个实体类,对应一张表的操作
- MyBatis映射文件用于编写SQL,访问以及操作表中的数据
- MyBatis映射文件存放的位置是src/main/resources/mappers目录下
- MyBatis中可以面向接口操作数据,要保证两个一致
- mapper接口的全类名和映射文件的命名空间(namespace)保持一致
- mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
6.通过junit测试功能
- SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)
- SqlSessionFactory:是“生产”SqlSession的“工厂”
- 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象
1 |
|
- 事务的提交需要手动提交!
7.加入log4j日志功能
加入依赖
1
2
3
4
5
6<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>加入log4j的配置文件
log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下
日志的级别:FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试) 从左到右打印的内容越来越详细
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
8.编写SqlSessionUtils
1 |
|
MyBatis获取参数值的两种方式
单个字面量类型的参数
- 若mapper接口中的方法参数为单个的字面量类型,此时可以使用${}和#{}以任意的名称(最好见名识意)获取参数的值。
- ${}和#{}本质上还是字符串拼接与占位符
- 注意${}需要手动加单引号
1 |
|
1 |
|
多个字面量类型的参数
若mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中
- 以arg0,arg1…为键,以参数为值;
- 以param1,param2…为键,以参数为值;
因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。
arg是从arg0开始的
param是从param1开始的
1 |
|
1 |
|
map集合类型的参数
- 若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
1 |
|
1 |
|
实体类类型的参数
- 若mapper接口中的方法参数为实体类对象时此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号
1 |
|
1 |
|
使用@Param标识参数
可以通过@Param注解标识mapper接口中的方法参数,此时,会将这些参数放在map集合中
- 以@Param注解的value属性值为键,以参数为值;
- 以param1,param2…为键,以参数为值;
只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
1 |
|
1 |
|
总结
建议分成两种情况进行处理
- 实体类类型的参数
- 使用@Param标识参 数
MyBatis的各种查询功能
- 如果查询出的数据只有一条,可以通过
- 实体类对象接收
- List集合接收
- Map集合接收,结果
{password=123456, sex=男, id=1, age=23, username=admin}
- 如果查询出的数据有多条,一定不能用实体类对象接收,会抛异常TooManyResultsException,可以通过
- 实体类类型的LIst集合接收
- Map类型的LIst集合接收
- 在mapper接口的方法上添加@MapKey注解
查询一个实体类对象
1 |
|
1 |
|
查询一个List集合
1 |
|
1 |
|
查询一条数据为map集合
1 |
|
1 |
|
查询多条数据为map集合
方法一
- 返回值为list集合
1 |
|
1 |
|
方法二
- @Mapkey注解
- 返回值为map
1 |
|
1 |
|
特殊SQL的执行
模糊查询
1 |
|
1 |
|
- 使用’%${}%’
- 使用concat(‘%’,#{mohu},’%’)
- 使用”%”#{mohu}”%”
- 其中
select * from t_user where username like "%"#{mohu}"%"
是最常用的
批量删除
- **只能使用${}**,如果使用#{},则解析后的sql语句为
delete from t_user where id in ('1,2,3')
,这样是将1,2,3
看做是一个整体,只有id为1,2,3
的数据会被删除。正确的语句应该是delete from t_user where id in (1,2,3)
,或者delete from t_user where id in ('1','2','3')
1 |
|
1 |
|
1 |
|
动态设置表名
- 只能使用${},因为表名不能加单引号
1 |
|
1 |
|
添加功能获取自增的主键
使用场景
t_clazz(clazz_id,clazz_name)
- t_student(student_id,student_name,clazz_id)
- 添加班级信息
- 获取新添加的班级的id
- 为班级分配学生,即将某学的班级id修改为新添加的班级的id
在mapper.xml中设置两个属性
useGeneratedKeys:设置使用自增的主键
- keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中
1 |
|
1 |
|
1 |
|
自定义映射resultMap
resultMap处理字段和属性的映射关系
- resultMap:设置自定义映射
- 属性:
- id:表示自定义映射的唯一标识,不能重复
- type:查询的数据要映射的实体类的类型
- 子标签:
- id:设置主键的映射关系
- result:设置普通字段的映射关系
- 子标签属性:
- property:设置映射关系中实体类中的属性名
- column:设置映射关系中表中的字段名
- 若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射,即使字段名和属性名一致的属性也要映射,也就是全部属性都要列出来
- 配置子标签时主键为id,其他为result
- 属性为property,字段为column
1 |
|
若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性名符合Java的规则(使用驼峰)。此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系
- 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致
1
2
3
4<!--List<Emp> getAllEmp();-->
<select id="getAllEmp" resultType="Emp">
select eid,emp_name empName,age,sex,email from t_emp
</select> - 可以在MyBatis的核心配置文件中的
setting
标签中,设置一个全局配置信息mapUnderscoreToCamelCase,可以在查询表中数据时,自动将_类型的字段名转换为驼峰,例如:字段名user_name,设置了mapUnderscoreToCamelCase,此时字段名就会转换为userName.1
2
3<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
- 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致
多对一映射处理
查询员工信息以及员工所对应的部门信息
1 |
|
1.级联方式处理映射关系
- 通过属性.的形式访问
1 |
|
2.使用association处理映射关系
- association:处理多对一的映射关系(多个员工对应一个部门)
- property:需要处理多对的映射关系的属性名
- javaType:该属性的类型
- 可以看做是查询结果赋值给了Dept对象,之后dept对象再赋值给emp的属性
1 |
|
3.分步查询
1. 查询员工信息
- select:设置分布查询的sql的唯一标识(namespace.SQLId或mapper接口的全类名.方法名)
- column:设置分步查询的条件(也就是传给下一个sql查询的条件)
1 |
|
1 |
|
2. 查询部门信息
1 |
|
1 |
|
一对多映射处理
1 |
|
collection
- collection:用来处理一对多的映射关系
- ofType:表示该属性对应的集合中存储的数据的类型
1 |
|
分步查询
1. 查询部门信息
1 |
|
1 |
|
2. 根据部门id查询部门中的所有员工
1 |
|
1 |
|
延迟加载
- 分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:
- lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
- aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载
- 此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType=”lazy(延迟加载)|eager(立即加载)”
1 |
|
1 |
|
- 关闭延迟加载,两条SQL语句都运行了
- 开启延迟加载,只运行获取emp的SQL语句
1 |
|
- 开启后,需要用到查询dept的时候才会调用相应的SQL语句
- fetchType:当开启了全局的延迟加载之后,可以通过该属性手动控制延迟加载的效果,fetchType=”lazy(延迟加载)|eager(立即加载)”
1 |
|
动态SQL
- Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题
if
- if标签可通过test属性(即传递过来的数据)的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行
- 在where后面添加一个恒成立条件
1=1
- 这个恒成立条件并不会影响查询的结果
- 这个
1=1
可以用来拼接and
语句,例如:当empName为null时 - 如果不加上恒成立条件,则SQL语句为
select * from t_emp where and age = ? and sex = ? and email = ?
,此时where
会与and
连用,SQL语句会报错- 如果加上一个恒成立条件,则SQL语句为
select * from t_emp where 1= 1 and age = ? and sex = ? and email = ?
,此时不报错
- 如果加上一个恒成立条件,则SQL语句为
- 这个
1 |
|
where
- where和if一般结合使用
- 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
- 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and/or去掉
1 |
|
- 注意:where标签不能去掉条件后多余的and/or
1 |
|
trim
- trim用于去掉或添加标签中的内容
- 常用属性
- prefix:在trim标签中的内容的前面添加某些内容
- suffix:在trim标签中的内容的后面添加某些内容
- prefixOverrides:在trim标签中的内容的前面去掉某些内容
- suffixOverrides:在trim标签中的内容的后面去掉某些内容
- 若trim中的标签都不满足条件,则trim标签没有任何效果,也就是只剩下
select * from t_emp
1 |
|
1 |
|
choose、when、otherwise
choose、when、otherwise
相当于if...else if..else
- when至少要有一个,otherwise至多只有一个
1 |
|
1 |
|
- 相当于
if a else if b else if c else d
,只会执行其中一个
foreach
- 属性:
- collection:设置要循环的数组或集合
- item:表示集合或数组中的每一个数据
- separator:设置循环体之间的分隔符,分隔符前后默认有一个空格,如
,
- open:设置foreach标签中的内容的开始符
- close:设置foreach标签中的内容的结束符
- 批量删除
1 |
|
1 |
|
- 批量添加
1 |
|
1 |
|
SQL片段
- sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入
- 声明sql片段:
<sql>
标签
1 |
|
- 引用sql片段:
<include>
标签
1 |
|
MyBatis的缓存
MyBatis的一级缓存
一级缓存默认开启
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
MyBatis的二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件
- 在核心配置文件中,设置全局配置属性cacheEnabled=”true”,默认为true,不需要设置
- 在映射文件中设置标签cache
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
二级缓存的相关配置
- 在mapper配置文件中添加的cache标签可以设置一些属性
- eviction属性:缓存回收策略
- LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
- 默认的是 LRU
- flushInterval属性:刷新间隔,单位毫秒
- 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句(增删改)时刷新
- size属性:引用数目,正整数
- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
- readOnly属性:只读,true/false
- true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
- false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false
MyBatis缓存查询的顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存
MyBatis的逆向工程
- 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的
- 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
添加依赖和插件
1 |
|
创建MyBatis的核心配置文件
1 |
|
创建逆向工程的配置文件
- 文件名必须是:
generatorConfig.xml
1 |
|
执行MBG插件的generate目标
- 如果出现报错:
Exception getting JDBC Driver
,可能是pom.xml中,数据库驱动配置错误 - dependency中的驱动
- mybatis-generator-maven-plugin插件中的驱动
- 两者的驱动版本应该相同
- 执行结果
分页插件
分页插件使用步骤
添加依赖
1 |
|
配置分页插件
- 在MyBatis的核心配置文件(mybatis-config.xml)中配置插件
1 |
|
分页插件的使用
开启分页功能
- 在查询功能之前使用
PageHelper.startPage(int pageNum, int pageSize)
开启分页功能 - pageNum:当前页的页码
- pageSize:每页显示的条数
1 |
|
分页相关数据
方法一:直接输出
1 |
|
- 分页相关数据:
1 |
|
方法二使用PageInfo
- 在查询获取list集合之后,使用
PageInfo<T> pageInfo = new PageInfo<>(List<T> list, intnavigatePages)
获取分页相关数据 - list:分页之后的数据
- navigatePages:导航分页的页码数
1 |
|
- 分页相关数据:
1 |
|
- 其中list中的数据等同于方法一中直接输出的page数据
常用数据:
- pageNum:当前页的页码
- pageSize:每页显示的条数
- size:当前页显示的真实条数
- total:总记录数
- pages:总页数
- prePage:上一页的页码
- nextPage:下一页的页码
- isFirstPage/isLastPage:是否为第一页/最后一页
- hasPreviousPage/hasNextPage:是否存在上一页/下一页
- navigatePages:导航分页的页码数
- navigatepageNums:导航分页的页码,[1,2,3,4,5]
MybatisPlus
实体类
1 |
|
通用mapper
开启包扫描该包下的所有mapper都将被纳入spring
1 |
|
接口继承basemapper即可
1 |
|
增删改
//插入一条数据 int insert(T entity);
1
2
3
4
- ```java
//根据主键id删除
int deleteById(Serializable id);//根据map中条件删除 int deleteByMap(Map<String, Object> columnMap); //eg:删除name=张三&&age=25的数据 public void deleteByMap() { Map<String, Object> map = new HashMap<>(); map.put("name", "张三"); map.put("age", 25); int rows = userMapper.deleteByMap(map); System.out.println("删除条数:" + rows); }
1
2
3
4
- ```java
//根据warpper条件构造器删除
int delete(Wrapper<T> queryWrapper);//根据id集合删除一批数据(不能为 null 以及 empty) int deleteBatchIds(Collection idList); //eg:删除id为1和2的 public void deleteByBatchIds() { int rows = userMapper.deleteBatchIds(Arrays.asList(1,2)); System.out.println("删除条数:" + rows); }
1
2
3
4
- ```java
//根据id修改
int updateById(T entity);```java
//根据条件构造器修改
//@param entity 实体对象 (set 条件值,可以为 null)
//@param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
int update(T entity,WrapperupdateWrapper); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#### 查询
```java
//根据 ID 查询
T selectById(Serializable id);
//查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
//查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
//根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
//根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
//根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
//根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
//根据 Wrapper 条件,查询全部记录
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
通用service
1.写接口继承IService
1 |
|
2.实现类继承ServiceImpl,实现上边的接口
1 |
|
常用方法
1 |
|
条件构造器Wrapper
配置文件(springboot)
1 |
|
原生查询
1.配置文件配置包扫描,也就是实现类的位置(mapper.xml)
1 |
|
2.创建对应mapper及其方法实现即可,跟mybatis一样
1 |
|