maven仓库
1 | <dependency> |
持久层:持久化就是将程序的数据在瞬时状态和持久状态转化的过程,一般是数据库,而持久层就是完成持久化的代码块
特点:层界限十分明显
为什么需要Mybatis?
- 方便,帮助程序将数据存入数据库中
- JDBC代码太复杂,简化,框架,自动化
- sql与代码分离,提高可维护性
- 提供映射标签,支持对象与数据库orm字段关系映射
- 提供对象关系映射标签,支持对象关系组件维护
- 提供xml标签,支持动态编写sql
开始Mybatis
在resources目录下创建
mybatis-config.xml
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/lan5th/dao/UserMapper.xml"/>
</mappers>
</configuration>mappers
标签用于注册实现类的mapper.xml编写工具类
MybatisUtils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e){
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}在调用sqlSessionFactory.openSession()时若添加boolean参数为true则可以设置事务自动提交
编写实体类
User
编写接口
UserMapper
(代替Dao接口)1
2
3public interface UserMapper {
List<User> getUserList();
}编写
UserMapper.xml
配置文件(代替DaoImpl实现类)1
2
3
4
5
6
7
8
9
<mapper namespace="com.lan5th.dao.UserMapper">
<select id="getUserList" resultType="com.lan5th.pojo.User">
select * from `user`;
</select>
</mapper>编写测试类
UserMapperTest.java
(使用junit)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class UserMapperTest {
public void test(){
//第一步,获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方法一:getMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
//方法二(已过时):
//List<User> userList = sqlSession.selectList("com.lan5th.dao.UserMapper.getUserList");
for (User user : userList) {
System.out.println(user.toString());
}
sqlSession.close();
}
}
无法找到Mapper.xml解决办法:在pom.xml
中添加
1 | <build> |
CRUD
namespace
namespace中的包名要和Dao/mapper中的包名一致
- id:对应的namespace中的方法名
- resultType:Sql语句执行的返回值
- parameterType:函数传入的参数类型(可以通过
#{}
直接拿到对象中的属性)
增删改查
增加接口函数UserMapper.java
1 | public interface UserMapper { |
修改配置文件UserMapper.xml
引用参数时可选${}或#{},但前者是基本的字符串拼接,无法防止sql注入,后者类似于预编译,可以防止大部分的sql注入,因此能使用#{}时尽量使用#{}来引用参数
1 |
|
测试类UserMapperTest.java
1 | package com.lan5th.dao; |
传递参数
单个基本类型参数
非对象,可以不写parameterType
可以直接在sql中获取,如
1
select * from mybatis.user where id = #{id};
多个参数
基本思想封装成对象或使用map
对象传递参数,parameterType=”Object(根据情况写具体类)”
1
insert into mybatis.user values (#{id},#{name},#{pwd});
缺点:必须封装一个完整的对象,参数较多时不便
map传递参数,parameterType=”map”
先给map添加键值对
1
2
3
4Map<String, Object> map = new HashMap<String, Object>();
map.put("userId",1);
map.put("userName","MapUser");
int res = mapper.updateUserName(map);添加sql语句
1
2
3<update id="updateUserName" parameterType="map">
update mybatis.user set name=#{userName} where id=#{userId};
</update>
拓展:模糊查询
sql语句
1 | <select id="getUserLike" resultType="com.lan5th.pojo.User"> |
但是如上格式可能会产生sql注入的问题,可改为在传入参数时就封装完毕
1 | List<User> userList = mapper.getUserLike("%李%"); |
1 | <select id="getUserLike" resultType="com.lan5th.pojo.User"> |
配置
- mybatis-config.xml
- Mybatis配置文件包含了深深影响Mybatis行为的属性信息
注意:configration标签内部元素顺序是已经指定的,如properties必须在最上层等,configration下所有的标签都需要写在固定的位置
environments
Mybatis可以配置多种环境(尽管最终能够同时运行的只有一个),每个SqlSessionFactory实例只能选择一种环境,通过default选择需要使用的环境
POOLED:连接池,提升sql执行效率(具体在MySql篇查看)
properties
可以通过properties属性来实现引用配置文件
这些属性都是可外部配置且可动态替换的,可以在典型的java属性文件中配置,亦可通过properties元素的子元素来传递
db.properties
1 | driver=com.mysql.jdbc.Driver |
`mybatis-config.xml
1 |
|
还可以在properties标签内配置属性
如果同时在配置文件中的properties标签中和外部properties文件中同时配置了同一个属性,则会优先使用外部配置文件中该属性的值。
typeAliases
类型别名是为java类型设置一个短的名字,存在意义仅在于减少完全限定名的冗余
mybatis-config.xml
1 |
|
也可以制定一个包名,Mybatis会在包名下自动搜索需要的Java Bean
扫描实体类的包,他的默认别名就为这个类的类名,首字母小写
1 | <typeAliases> |
也可以通过给实体类添加注解来修改别名
1 |
|
settings
关于mybatis的拓展功能设置
一个完整配置的settings元素实例如下
1 | <settings> |
每个选项的具体含义请到mybatis官网查看
其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins插件(仅列举出几个典型的)
- mybatis-generator-core(可根据数据库自动生成增删改查代码)
- mybatis-plus(与mybatis互补的简化工具)
- 通用mapper
mappers
MapperRegistry:注册我们的Mapper文件
方式一
1
2
3<mappers>
<mapper resource="com/lan5th/dao/UserMapper.xml"/>
</mappers>方式二:使用class文件绑定注册
1
2
3<mappers>
<mapper class="com.lan5th.dao.UserMapper"/>
</mappers>注意点:接口和接口的Mapper配置文件必须同名,也必须在同一文件夹下
方式三:使用扫描包进行注入绑定,mybatis自动扫描包下的所有接
1
2
3<mappers>
<package name="com.lan5th.dao"/>
</mappers>注意点与方式二相同
生命周期和作用域
SqlSessionFactoryBuildert
作为一个局部变量,一旦创建完SqlSessionFactory便不再需要
SqlSessionFactory
可以理解为数据库连接池
SqlSessionFactory一旦被创建就应该在运行期间一直存在,不能丢弃它或创建另一个实例,因此SqlSessionFactory的最佳作用域是应用作用域
应使用单例模式或静态单例模式
SqlSession
连接到连接池的一个请求
SqlSession的实例不是线程安全的,因此不能被共享,最佳作用域是请求或方法作用域
使用完成后需要立即关闭,否则资源会被占用
其中每一个Mapper代表一个具体的业务
ResultMap
解决属性名和字段名不一致的问题
1 | <resultMap id="UserMap" type="User"> |
日志
logImpl
日志工厂
选项:
- SLF4J
- LOG4J
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING
- NO_LOGGING
在配置文件mybatis-config.xml
中添加
1 | <settings> |
log4j
简单使用:需要导入log4j的maven包
1 | <dependency> |
resources目录下新建log4j.properties
配置文件
1 | #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 |
除了log4j的默认输出,还可以在方法中手动添加需要记录的日志,如在UserDaoTest.java
测试类中添加测试方法
1 | public void testLog4j(){ |
分页
limit实现
用sql语句
1 | select * from mybatis.user limit 0,5; |
其实位置为0,页面大小为5
1 | <select id="getUserByRowBounds" parameterType="map" resultType="User"> |
测试类不再与上方代码类似,不再复述
高级分页
原因:mysql的limit会先将所有的limit2之前的数据加载到内存中,然后取需要的数据,造成内存浪费,这里子查询先取需要的id,加载到内存中的数据较少,由外层查询由in语句再进行查询;
注意点:mysql5.7之前不能在子句中使用limit条件,因此需要把子查询b2中的结果再封装成b3这张表,再从外层取id集合
1 | <select id="getPagination" resultType="com.lan5th.blog.pojo.BlogDetail"> |
RowBounds实现
(近些年不再使用)
将所有数据查出缓存再将需要的取出显示
接口
1
List<User> getUserListByRowBounds();
Mapper.xml
1
2
3<select id="getUserByRowBounds" resultType="User">
select * from mybatis.user;
</select>测试
1
2
3
4
5
6
7
8
9
10
11
12
public void getUserByRowBoundsTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(1, 2);
List<User> userList = sqlSession.selectList("com.lan5th.dao.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
PageHelper插件
通过注解开发
仅适用于一些简短的sql语句,且不能使用结果集映射
接口类
UserMapper.java
1
2
3
4public interface UserMapper {
List<User> getUserByRowBounds();
}配置文件
mybatis-config.xml
(注意是接口类,而不是xml配置文件)1
2
3<mappers>
<mapper class="com.lan5th.dao.UserMapper"/>
</mappers>
本质:反射机制
底层:动态代理
CRUD
关于@Param()注解
- 基本类型的参数或String类型需要添加
- 引用类型不需要添加
- 如果只有一个基本类型可以忽略但按规范一般都需要添加
- 在sql语句中引用的就是@Param()中设定好的属性名
Mybatis具体执行过程
- Resources获取加载全局配置文件
- 实例化SqlSessionFactoryBuilder构造器
- 解析配置文件流XMLConfigBuilder
- Configration包含所有的配置信息
- SqlSessionFactory实例化
- transaction事务管理
- 创建executer执行器
- 创建sqlsession
- 实现CRUD,失败则回滚到6事务管理
- 查看是否执行成功,失败则回滚到6事务管理
- 提交事务
- 关闭
Lombok
idea中安装插件
导入jar包(maven)
1
2
3
4
5<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
可用注解
1 | and |
常用注解
1 | :包含无参构造、get、set、toString、hashCode、equals |
多对一
一个老师有多个学生
对于学生而言,关联,多个学生关联一个老师【多对一】
对于老师而言,集合,一个老师有很多学生【一对多】
创建数据表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');对象接口
StudentMapper
TeacherMapper
xml配置文件
StudentMapper.xml
TeacherMapper.xml
多对一
实体类
Student.java
1
2
3
4
5
6
public class Student {
private int id;
private String name;
private Teacher teacher;
}Teacher.java
1
2
3
4
5
public class Teacher {
private int id;
private String name;
}
按照查询嵌套
类似子查询
StudentMapper.xml
1 | <!--查询所有学生信息,根据查询出的tid查询对应的老师--> |
按照结果嵌套
类似联表查询
StudentMapper.xml
1 | <resultMap id="StudentTeacher2" type="Student"> |
一对多
实体类
Student.java
1
2
3
4
5
6
public class Student {
private int id;
private String name;
private int tid;
}Teacher.java
1
2
3
4
5
6
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
按照查询嵌套处理
1 | <resultMap id="TeacherStudent" type="Teacher"> |
按照结果嵌套处理
1 | <resultMap id="TeacherStudent2" type="Teacher"> |
注意点:
- 保证SQL可读性,尽量保证通俗易懂
- 注意一对多和多对一中属性名和字段问题
- 问题不容易排查时使用日志
动态SQL
IF
1
2
3
4
5
6
7
8
9<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>choose(when,otherwise)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<select id="queryBlogWhere" parameterType="map" resultType="blog">
select * from mybatis.blog
<!--类似于java中的switch语句-->
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
author = #{author}
</when>
<otherwise>
view - #{view}
</otherwise>
</choose>
</where>
</select>trim(where,set)
只有当后面条件至少有一个满足时才会插入where或set
1
2
3
4
5
6
7
8
9
10
11
12<select id="queryBlogTrim" parameterType="map" resultType="blog">
select * from mybatis.blog
<!--where语句当选择的第一项前有and/or时会自动去除-->
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>1
2
3
4
5
6
7
8
9
10
11
12<update id="updateBlog" parameterType="map">
update mybatis.blog
<!--set语句当选择的最后一项后有','时会自动去除-->
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
</update>还可以使用trim自定义选择
1
2
3
4
5
6
7
8
9
10
11<trim prefix="WHERE" prefixOverrides="AND">
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</trim>1
2
3<trim prefix="SET" suffixOverrides=",">
...
</trim>sql片段
1
2
3
4
5
6
7
8
9
10
11
12<!--使用sql标签抽取公共部分
最好根据单表来抽取sql
不要包含where标签
-->
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author !=null">
author = #{author}
</if>
</sql>1
2
3
4
5
6<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<include refid="if=title-author"></include>
</where>
</select>Foreach
1
2
3
4
5
6
7
8
9<!--先传递一个map,其中包含键值对ids,内容为数组-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
动态sql就是在拼接sql语句,我们只需保证sql的正确性,按照sql的格式去排列组合即可;一般先在对应的Dmms中写出完整的sql,在修改动态sql实现通用
缓存
Mybatis缓存
一级缓存
一级缓存也叫本地缓存,在mybatis中是默认开启的,只在一次SqlSession中有效
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 之后如果需要获取相同的数据,直接从缓存中拿到,不再查询数据库
缓存失效情况:
- 查询不同的数据
- 经过增删改操作之后,可能会改变原来的数据,缓存会刷新(如对data1进行修改,虽然没有改变data2数据,但data2数据的缓存也会被清空!)
- 查询不同的Mapper.xml
- 手动清理缓存
sqlSession.clearCache()
二级缓存
二级缓存也叫全局缓存,基于namespace级别的缓存,一个名称空间对应一个二级缓存
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 新的会话查询信息可以从二级缓存中获取内容
- 不同的mapper查处的数据会放在自己对应的缓存中
开启步骤:
开启全局缓存(尽管是默认开启的)
mybatis-config.xml
1
2
3<settings>
<setting name="cacheEnabled" value="true"/>
</settings>在某个Mapper中配置缓存
UserMapper.xml
1
2
3
4
5
6<!--不使用参数可以直接血<cache/>-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>使用FIFO算法清除缓存,每60s刷新,最多可以存储结果或对象的512个引用,且存储的对象为只读
测试问题:使用时需要将实体类序列化,否则会报错!
小结:
- 开启二级缓存后,在同一Mapper下有效
- 所有的数据都先放在一级缓存中,只有当会话提交或者关闭的时候才会提交到二级缓存中
mybatis-ehcache
pom.xml
1 | <dependency> |
新建ehcache配置文件ehcache.xml
1 |
|
UserMapper.xml
1 | <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> |