1、简介
1.1、什么是MyBatis?
- MyBatis 是一款优秀的持久层框架
- 它支持定制化 SQL、存储过程以及高级映射。
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
- MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
1.2、持久化
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转变的过程。
瞬时状态的数据是存储在内存中的,内存具有断电即失的特性,所以我们要将之持久化
1.3、持久层
Dao层,Service层,Controller层。。。
持久层:完成持久化工作的代码块
2、第一个MyBatis程序
与springboot使用的Mybatis需要写以下文件才能使用
- 编写MyBatis的核心配置文件
- 编写Mybatis的工具类
实现如下:
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
System.out.println(userList);
}
3、CRUD
3.1、namespace
namespace中的包名和mapper中的接口类一致
3.2、insert
插入用户的时候关于ID的设置:由于id是自动增长的所以在新增的时候我们不能给其指定id
解决办法:mapper新增的时候id=0,后台新增的时候就会自动赋值+1的值
4、配置解析
4.1、核心配置文件
Mybatis-config.xml
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
4.2、环境配置
mybatis可以适配成多种环境
但是每个SqlSessionFactory实例只能选择一种环境(根据配置文件中的elements中的dafult进行切换)
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
mybatis默认的事务管理器是JDBC,连接池是POOLED
4.3、属性(properties)
我们可以通过properties属性实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。(db.peoperties)
创建一个配置文件(db.peoperties)
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatisStudy?useUnicode=true&characterEncoding=UTF-8&;useJDBCCompliantTimezoneShift=true&;useLegacyDatetimeCode=false&;serverTimezone=Asia/Shanghai&;allowMultiQueries=true
username = root
password = 123123123
将之引用到配置文件中去
<!--引入外部配置文件 优先级:优先使用外部配置文件-->
<properties resource="db.properties" />
<!--或者另一种用法,外部配置文件的优先级是更高的-->
<properties resource="db.properties">
<property>name="username" value="root"</property>
<property>name="pwd" value="111"</property>
</peoperties>
4.4、起别名
<!--给实体类起别名:方式一-->
<typeAliases>
<typeAlias type="com.psy.pojo.User" alias="User"/>
</typeAliases>
<!--给实体类起别名:方式二->扫描包下的实体类是否有相关的别名注解,如果没有的话该别名为小写-->
<typeAliases>
<package name="com.psy.pojo"/>
</typeAliases>
4.5、Mapper映射器
注册并绑定我们的Mapper文件
<!--方式1-->
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方式二的注意点:
- 接口必须和他的Mapper文件同名
- 必须在一个包下面
<!--方式二-->
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
方式三的注意点和方式二的注意点一样
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
4.6、生命周期和作用域
生命周期和作用域是至关重要的,因为错误会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
- 一旦创建了SqlSessionFactory,就不再需要他了
- 局部变量
SqlSessionFactory:
- SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,不能丢弃或者重新创建另一个实例
- 因此他的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式
SqlSession
- 连接到连接池的一个请求
- SqlSession的实例不是线程安全的,因为不能被共享的,所以他的最佳作用域是请求或方法作用域
- 用完之后赶紧关闭,否则资源被占用!
5、解决属性名和字段名不一致的问题
结果映射集
// 实体类属性
id username password
// 数据库字段
id username pwd
解决办法1:在查询语句中使用as
select id,username,pwd as password from users
解决办法2:将resultType替换为resultMap,此外需要写一个结果映射集
<resultMap id="UserMap" type="User">
<result column="id" property="id" />
<result column="username" property="username" />
<!--假设数据库中的密码字段名为pwd则column中的值为pwd,此外我们只需要映射字段名不匹配的就行-->
<result column="password" property="password" />
</resultMap>
<select id="getUserList" resultMap="UserMap">
select * from mybatisStudy.users
</select>
resultType的话会自动生成一个映射但是那个映射是基于实体类的,而且column和property的值是一样的
6、日志
6.1、日志工厂
数据库出现异常的情况下我们可以通过日志去排错。
- SLF4J
- LOG4J 【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING :控制台输出
- STDOUT_LOGGING 【掌握】:标准日志输出
- NO_LOGGING:没有日志输出
具体使用哪个日志由设置(没有默认值)决定。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
6.2、log4j
什么事log4j?
- Log4j是Apache的一个开源项目
- 我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
- 我们也可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别(error、debug等等),我们能够更加细致地控制日志的生成过程
- 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
1、导包
2、log4j.properties
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
3、配置log4j为日志实现
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
4、log4j的使用
public class UserDaoTest{
// 使用的话必须获得此类的反射
static Logger logger = Logger.getLogger(UserDaoTest.class);
@Test
public void testLog4j(){
logger.info("info:进入了testLog4j");
logger.info("debug:进入了testLog4j");
logger.info("error:进入了testLog4j");
}
}
7、分页
8、使用注解开发
package com.psy.dao;
import com.psy.pojo.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper {
@Select("select * from mybatisStudy.users")
List<User> getUsers();
}
xml文件中需要绑定这个接口
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--引入外部配置文件 优先级:优先使用外部配置文件-->
<properties resource="db.properties" />
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--绑定接口-->
<mappers>
<mapper class="com.psy.dao.UserMapper"/>
</mappers>
</configuration>
9、Mybatis的执行流程
10、多对一处理
多对一:多个学生对应一个老师,即多个学生关联一个老师(关联)
一对多:一个老师对应多个学生,即一个老师有个学生集合(集合)
// 学生类
class Student{
private int id;
private String name;
private Teacher teacher;
}
class Teacher{
private int id;
private String name;
}
/**
* 实体类对应的数据库表
* 学生表字段:id,name,tid(老师的id)
* 教师表字段: id,name
*/
//接口类
public interface StudentMapper {
public List<Student> getStudent();
public List<Student> getStudent2();
}
如何将数据库中的Student(Teacher以JavaBean的形式)查询出来呢?
1、使用查询的嵌套处理
<?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.psy.dao.StudentMapper">
<resultMap id="Student" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--由于最后是一个属性是Teacher对象,类似以上的写法是行不通的,所以我们要使用association-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher" />
</resultMap>
<select id="getStudent" resultMap="Student">
select * from mybatisStudy.student
</select>
<select id="getTeacher" resultType="Teacher">
select * from mybatisStudy.teacher where id = #{id}
</select>
</mapper>
查询的结果如下:
Student(id=1, name=邹嘉懿, teacher=Teacher(id=1, name=洪文轩))
Student(id=2, name=蔡博文, teacher=Teacher(id=1, name=洪文轩))
Student(id=3, name=覃明哲, teacher=Teacher(id=1, name=洪文轩))
Student(id=4, name=覃绍辉, teacher=Teacher(id=1, name=洪文轩))
2、按照结果嵌套处理
<select id="getStudent2" resultMap="Student2">
select s.id sid,s.name sname,t.name tname,t.id tid
from mybatisStudy.student s, mybatisStudy.teacher t
where s.tid = t.id
</select>
<resultMap id="Student2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
查询结果如下:
Student(id=1, name=邹嘉懿, teacher=Teacher(id=1, name=洪文轩))
Student(id=2, name=蔡博文, teacher=Teacher(id=1, name=洪文轩))
Student(id=3, name=覃明哲, teacher=Teacher(id=1, name=洪文轩))
Student(id=4, name=覃绍辉, teacher=Teacher(id=1, name=洪文轩))
Student(id=5, name=邵昊天, teacher=Teacher(id=2, name=冯思远))
Student(id=6, name=黎晓啸, teacher=Teacher(id=2, name=冯思远))
Student(id=7, name=金凯瑞, teacher=Teacher(id=2, name=冯思远))
Student(id=8, name=沈雪松, teacher=Teacher(id=2, name=冯思远))
Student(id=9, name=夏煜祺, teacher=Teacher(id=3, name=江浩轩))
Student(id=10, name=贾擎宇, teacher=Teacher(id=3, name=江浩轩))
11、一对多处理
实体类
public class Teacher {
private int id;
private String name;
// 一对多
private List<Student> students;
}
public class Student {
private int id;
private String name;
private int tid;
}
// 接口类
public interface TeacherMapper {
//List<Teacher> getTeacher();
//获取指定老师下的所有学生及老师的信息
Teacher getTeacher2(@Param("tid") int id);
Teacher getTeacher3(@Param("tid") int id);
}
1、使用查询嵌套处理
<!--按照查询嵌套进行查询-->
<select id="getTeacher3" resultMap="TeacherStudent3">
SELECT * from mybatisStudy.teacher where id=#{tid}
</select>
<resultMap id="TeacherStudent3" type="Teacher">
<collection property="students" javaType="Arraylist" ofType="com.psy.pojo.Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<!--这里的id是通过resultMap中返回的Teacher对象中的id来查询的,通过column进行传递-->
<select id="getStudentByTeacherId" resultType="Student">
SELECT * FROM mybatisStudy.student WHERE tid=#{id}
</select>
2、使用结果嵌套处理
<!--按照结果进行嵌套查询-->
<select id="getTeacher2" resultMap="TeacherStudent">
SELECT s.id sid,s.`name` sname,t.`name` tname,t.id tid
FROM mybatisStudy.student s,mybatisStudy.teacher t
where s.tid=t.id and t.id=1
</select>
<resultMap id="TeacherStudent" type="com.psy.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--
javaType:指定属性的类型
集合List中的泛型信息,我们使用ofType获取
-->
<collection property="students" ofType="com.psy.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
面试高频:
- MySQL引擎
- INNODB底层原理
- 索引
- 索引优化
12、动态SQL
什么是动态SQL:动态SQL就是根据不同的条件生成不同的SQL语句
环境搭建:
//实体类
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime; //与数据库中的字段不一致-->解决办法:在config文件中开启转驼峰命名法<setting name="mapUnderscoreToCamelCase" value="true"/>
private int views;
}
//获取ID的工具类
public class IDUtils {
public static String getId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
//接口类和xml文件正常写一个增加的接口就行
//测试
@Test
public void addBlogTest() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtils.getId());
blog.setTitle("Mybatis");
blog.setAuthor("狂神说");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("Java");
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("Spring");
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("微服务");
mapper.addBlog(blog);
sqlSession.close();
}
12.1、动态SQL之IF
<select id="queryBlogIF" parameterType="map" resultType="blog">
<!--where 1=1 这样编写是不正规的 正确的写法是使用<where>标签-->
<!--where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。-->
select * from mybatisStudy.blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
<!--正规写法-->
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatisStudy.blog
<where>
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
12.2、动态SQL之常用标签
choose、when、otherwise
类似于Java中的switch case
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatisStudy.blog
<where>
<choose>
<!--只要满足一个条件就返回-->
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
trim、where、set
Set: set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<update id="updateBlog" parameterType="map">
update mybatisStudy.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="title != null">
author = #{author},
</if>
</set>
where id = #{id}
</update>
trim:定制化的覆盖前缀和后缀,where和set的定制版
12.3、动态SQL之Foreach
SQL片段
<!--将重复的代码抽取出来-->
<sql id="if-title-author">
<if test="title != null">
and title = #{title}
</if>
<if test="title != null">
and author = #{author}
</if>
</sql>
<!--在需要使用的时候,使用include进行引入-->
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatisStudy.blog
<where>
<include refid="if-title-author">
</include>
</where>
</select>
使用SQL片段的注意事项:
- 最好基于单表来定义SQL片段
- 不要存在where标签
Foreach
对集合进行遍历(尤其是在构建 IN 条件语句的时候)
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
Item(集合项)index(索引:相当于数组的下标)list(传进来的集合名)
此外,foreach还允许我们设置语句的开头、结尾和间隔符。类似trim
13、缓存
13.1、简介
网页中大部分的操作都是读操作,也就是查询。
但是每次查询都要连接数据库,连接数据库有很耗费资源。那么,我们就将一次查询的结果暂时存起来—>内存:缓存
当我们要再次查询该数据的时候,我们直接从内存里面将值拿出来即可。
1、什么是缓存(Cache)?
- 存在内存中的数据
- 用户从内存中拿数据比从磁盘上拿数据要高效的多,提高了效率,解决了高并发系统的性能问题
2、为什么使用缓存?
- 减少和数据库交互的次数,减少系统开销,提高系统效率
3、适合放到缓存中的数据?
- 经常查询且不经常发生改变的数据
13.2、Mybatis缓存
-
默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,开启一级缓存。(SqlSession级别的缓存,也称本地缓存)
- 二级缓存需要手动开启和配置,是基于namespace级别的缓存
- 通过Mybatis定义的缓存接口Cache来自定义二级缓存
-
缓存有四种清除策略:
- LRU
- FIFO
- SOFT(软引用)
- WEAK(弱引用)
一级缓存:
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
User user2 = mapper.queryUserById(1);
User user3 = mapper.queryUserById(2);
System.out.println(user);
System.out.println("user2"+user2);
System.out.println("user3"+user3);
}
// 打印出来的日志
Created connection 296347592.
==> Preparing: select * from mybatisStudy.users where id = ?
==> Parameters: 1(Integer)
<== Columns: id, username, password
<== Row: 1, 郑聪健, 92394
<== Total: 1
==> Preparing: select * from mybatisStudy.users where id = ?
==> Parameters: 2(Integer)
<== Columns: id, username, password
<== Row: 2, 张聪健, 9435114
<== Total: 1
User(id=1, username=郑聪健, password=92394)
user2User(id=1, username=郑聪健, password=92394)
user3User(id=2, username=张聪健, password=9435114)
总结:对于相同的查询语句,第一次查询会连接数据库进行查询。第一次查询完成之后,会把结果缓存到内存中。之后的查询只要是和本语句一样就会从缓存里面查询结果。这就是一级查询(SQLSession级别的缓存,一旦SQLSession关闭了,缓存就没了)。
一级缓存几种情况会导致缓存失效:
1、增删改语句会刷新缓存
2、达到了缓存清除算法的某种条件,会失效
3、缓存会不定时的刷新
4、查询不同的mapper文件
5.手动清除
二级缓存:
- 二级缓存也叫做全局缓存
- 基于namespace
- 工作机制:
- 一个会话查询一条数据,这个数据会被放在当前会话的一级缓存中
- 会话关闭后,一级缓存中的数据会被放到二级缓存中
- 新建的会话查询信息会从二级缓存中获取内容
- 不同的mapper会把数据放到自己对应的二级缓存中
<!--在配置文件中开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
<!--mapper的xml文件中开启缓存-->
<cache/>
总结:不同SQLSession使用同一个Mapper文件中的SQL查询数据的话,会使用到二级缓存。
测试时候出现的问题:我们需要将实例类序列化,否则会报错!
package com.psy.pojo;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private String id;
private String username;
private String password;
}
缓存原理:
缓存顺序:二级缓存->一级缓存->数据库查询