【21.9.24】Mybatis
1. 简介
1.1、Mybatis官网
-
MyBatis 是一款优秀的持久层框架
-
它支持自定义 SQL、存储过程以及高级映射
-
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集
-
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
-
MyBatis 本是apache的一个
-
2013年11月迁移到
1.3、持久层
(1).数据持久化(动作)
-
持久化就是将程序的数据在持久化状态和瞬时状态转化的过程
-
内存:数据断电即失(瞬时状态)
-
将数据存在数据库(jdbc)或io文件使得数据处于持久化状态
(2).为什么需要持久化?
因为有一些数据我们不想让它丢掉,想一直使用它们,还有就是内存太贵了,用来存储数据显得比较浪费!
(3).持久层(是名词,是一个概念)
-
完成持久化工作的代码块
-
层的界限十分明显
1.4、为什么需要Mybatis?
-
帮助程序员将数据快速存入到数据库
-
方便
-
传统的jdbc代码太复杂了,需要简化,所以出现了Mybatis框架。框架的作用是:相当于你填写个人信息时,不再是给一张白纸你填写了,而是给份表格你,这份表格的作用就起到了框架的作用,只要你往里面填信息就能达到想要的效果!
-
特点:
-
简单易学
-
灵活
-
sql和代码的分离,提高了可维护性。
-
提供映射标签,支持对象与数据库的orm(Object Relation Mapping:对象-关系映射)字段关系映射
-
提供对象关系映射标签,支持对象关系组建维护
-
提供xml标签,支持编写动态sql
-
2. 第一个Mybatis程序
2.1、搭建环境
(1).搭建数据库
//创建数据库
create database mybatis;
use mybatis;
//创建表
create table user(
id int(20) not null primary key,
name varchar(30) default null,
pwd varchar(30) default null
)engine=INNODB default charset=utf8;
//向user表中插入数据
insert into user(id,name,pwd) values(1,'狂神','123456'),(2,'张三','12345678'),(3,'李四','12345678910')
(2).新建maven项目
-
删除src(让这个工程变成一个父工程,以后不用重复导包!!)
-
编写pom.xml核心配置文件
<!--2.导入依赖-->
<!--2.1 导入mysql驱动-->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--2.2 导入mybatis包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--2.3 导入junit测试包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies> -
给父工程创建一个子工程(new一个module)
(3).编写mybatis核心配置文件
在子工程的resource文件夹里新创一个“mybatis-config.xml”核心配置文件,代码如下:
<!--3.1 configuration 核心配置文件-->
<configuration>
<!--3.2 environments复数,代表可以通过default配置id实现多套使用环境下的转换-->
<environments default="development">
<environment id="development">
<!--3.3 事务管理,默认使用jdbc的-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--3.4 和数据库相关的一些信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--useSSL=true:开启了安全连接;useUnicode=true:使用了Unicode字符-->
<!--characterEncoding=UTF-8:字符集编码使用了UTF-8-->
<!--&:表示&。只不过在xml文件中要这样进行转义-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
(4).编写mybatis的工具类
定义一个工具类,因为每用到mybatis时都要做这些准备工作,所以将它封装出来
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
//4.1 一加载类就创建sqlsessionFactory对象,这是使用mybatis的第一步
static{
try {
//4.2 先找到Mybatis的核心配置文件
String resource = "mybatis-config.xml";
//4.3 将配置文件转成输入流
InputStream inputStream =Resources.getResourceAsStream(resource);
//4.4 使用这个流结合Builder来创建sqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// org.apache.ibatis.session.SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
// 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
//4.5 获得sqlSession
SqlSession sqlSession=sqlSessionFactory.openSession();
return sqlSession;
}
}
简单理解一下SqlSessionFactoryBuilder、sqlSessionFactory、SqlSession的关系:
(5).编写代码
-
5.1 编写实体类
public class User {
private int id;
private String name;
private String pwd;
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
-
5.2 编写UserDao接口(Mapper)
public interface UserDao {
//获取所有的用户
List<User> getUserList();
} -
5.3 编写接口的实现类(xml文件)
由原来的UserDaoImpl转变为一个Mapper配置文件!!
-
测试
(1).常见报错:org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.(Mapper还没有注册!!)
解决办法:每一个Mapper.xml都需要在Mybatis核心配置文件(mybatis-config.xml)中进行注册!
<mappers>
<mapper resource="com/kuang/dao/UserMapper.xml"></mapper>
</mappers>(2).配置文件缺少报错
java.lang.ExceptionInInitializerError
Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/kuang/dao/UserMapper.xml
解决方法:在父工程和子工程的pom.xml文件(为了保险,所以都加)加上资源导出代码
<!--在build中配置resources,防止我们资源导出失败的问题-->
<build>
<resources>
<!--设置正常情况的resources目录下的properties文件-->
<resource>
<!--配置路径-->
<directory>src/main/resources</directory>
<includes>
<!--resources这个文件夹可以包含什么文件,下面配置了可以包含.properties和.xml文件-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<!-- 设置java路径的properties文件-->
<resource>
<directory>src/main/java</directory>
<includes>
<!--java这个文件夹可以包含什么文件,下面配置了可以包含.properties和.xml文件。如果不配置,那么java
文件夹下只能写java代码,如果你写了其他格式的文件,到时候项目导出时,那些文件是导不出的!!-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>(3).用junit测试
一定要关闭sqlSession!!
public class UserDaoTest {
结果:
![image-20210924180944877]()
2.2、CRDU(增删改查)
步骤:先写接口,再去到xml文件写sql语句,最后去写测试类!!
最重要一点:增删改最后必须要提交事务,否则数据库的数据不会进行更新!!
-
id表示接口里面的方法名
-
resultType表示要返回的数据类型
-
parameterType表示参数类型
-
#{id}表示接收传过来的参数id
(1).select语句
<select id="getUserById" resultType="com.kuang.pojo.User">
select * from mybatis.user where id=#{id};
</select>
(2).insert语句
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into mybatis.user values(#{id},#{name},#{pwd})
</insert>
(3).update语句
<update id="updateUser" parameterType="com.kuang.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
</update>
(4).delete语句
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
(5).万能Map
用Map来传参你不需要知道数据库表里有多少个字段,你只需传你想做修改的字段过去就行了!!
总结:
-
Map传递参数,直接在sql中传key就行了,而且它存在灵活性。parameterType="map"
-
对象传递参数,要在sql中把这个对象的所有属性都要写出来,有点局限性,当表的属性一多时就很麻烦了。parameterType="Object"
-
当只传一个基本类型参数的情况下,可以省略parameter不写
-
传多个参数一定要想起用map!
//接口代码
//用map来根据id查询用户
User getUserById2(Map<String,Object> map);
<!--xml代码-->
<!--万能的map:这里的#{userId}是定义了map里的key的值,可由你随意定制-->
<insert id="addUser2" parameterType="map">
insert into mybatis.user values(#{userId},#{userName},#{userPwd})
</insert>
//测试类代码
//万能的Map
public void addUser2(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("userId",5);
map.put("userName","李五");
map.put("userPwd","555555");
mapper.addUser2(map);
//一定要提交事务!!
sqlSession.commit();
sqlSession.close();
}
2.3、模糊查询
(1).在java代码中就传递通配符%过去给sql
List<User> userList=mapper.getUserLike("%李%");
(2).在sql拼接中使用通配符
//java代码
List<User> userList=mapper.getUserLike("李");
select * from mybatis.user where name like "%"#{value}"%"; <!--拼接死了!-->
3. 配置解析
3.1、核心配置文件
-
mybatis-config.xml
-
Mybatis的配置文件包含了会深深影响Mybatis行为的设置和属性信息
configuration 配置
properties 属性
settings 设置
typeAliases 类型命名
typeHandlers 类型处理器
objectFactory 对象工厂
plugins 插件
environments 环境
environment 环境变量
transactionManager 事务管理器--有两种:JDBC和MANAGED
dataSource 数据源--dbcp,c3p0,druid--连接数据库
mappers 映射器
3.2、环境配置(environments)
-
Mybatis可以配置适应多套环境,不过要记住,尽管可以配置多套环境,但每个SqlSessionFactory实例只能选择一种环境进行使用!!
-
Mybatis默认的事务管理器就是JDBC,连接池是POOLED
3.3、属性(properties)
我们可以通过properties属性来实现引用配置文件!!
这些属性都是可外观配置且可以动态替换的,既可以在典型的 Java 属性配置文件(db.properties)中配置, 又可以通过 properties 元素的子元素来传递。
(1).编写一个数据库配置文件
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&Unicode=true&characterEncoding=UTF-8
username=root
password=123456
(2).在核心配置文件中引入

有两种引入格式,分别如下:
用$来引入参数值!!
-
直接引入外部文件db.properties
<!--一、自闭合引入外部配置文件-->
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<!--3.3 事务管理,默认使用jdbc的-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--3.4 和数据库相关的一些信息-->
<property name="driver" value="${driver}"/>
<!--useSSL=true:开启了安全连接;useUnicode=true:使用了Unicode字符-->
<!--characterEncoding=UTF-8:字符集编码使用了UTF-8-->
<!--&:表示&。只不过在xml文件中要这样进行转义-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments> -
也可以在properties增加一些属性配置
//db.properties文件,少了两个属性
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&Unicode=true&characterEncoding=UTF-8
<!--二、-->
<properties resource="db.properties">
<property name="username" value="root"></property>
<property name="pwd" value="123456"></property>
</properties>
<environments default="development">
<environment id="development">
<!--3.3 事务管理,默认使用jdbc的-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--3.4 和数据库相关的一些信息-->
<property name="driver" value="${driver}"/>
<!--useSSL=true:开启了安全连接;useUnicode=true:使用了Unicode字符-->
<!--characterEncoding=UTF-8:字符集编码使用了UTF-8-->
<!--&:表示&。只不过在xml文件中要这样进行转义-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${pwd}"/>
</dataSource>
</environment>
</environments>
注意:如果两个文件有同一字段但值不同,如密码等,它会优先使用外部配置文件的字段其内部文件的字段将不会被使用!!
3.4、映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件!
方式一:完全限定名(推荐使用)
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中进行注册!!这个点很容易忘记的!-->
<mappers>
<mapper resource="com/kuang/dao/UserMapper.xml"></mapper>
</mappers>
方式二:使用class文件绑定注册
<mappers>
<mapper class="com.kuang.dao.UserMapper"></mapper>
</mappers>
方式三:使用扫描包进行注入绑定
<mappers>
<package name="com.kuang.dao"></package>
</mappers>
方式二和三的注意点(不注意就会报错!!):
-
接口和它的Mapper配置文件必须同名!
-
接口和它的Mapper配置文件必须在同一个包下!
3.5、生命周期和作用域

生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题!!
SqlSessionFactoryBuilder:
-
一旦创建了SqlSessoionFactory,就不再需要它了
-
作用域:方法作用域(局部变量)
SqlSessionFactory:
-
说白了就是可以想象为数据库连接池(等待别人关联,然后关闭)
-
一旦被创建,SqlSessionFactory 实例应该在你的应用程序执行期间都存在。没有任何理由丢弃它或重新创建另一个实例。
-
最简单的就是使用单例模式或者静态单例模式。
-
最佳作用域:应用作用域(全局变量)
SqlSession:
-
连接到连接池的一个请求
-
用完之后需要赶紧关闭,否则资源被占用!
-
SqlSession的实例不是线程安全的,因此是不能被共享的!
-
最佳作用域:请求或方法作用域

这里面的每个mapper就是一个具体的业务(方法)!!
4. ResultMap结果集映射
4.1、应用场景
解决(实体类)属性名和(数据库)字段名不一致的问题
-
数据库中的字段

-
新建项目改实体类中的属性(将之前的pwd改成password)---发现最后得出的结果为null;

解决方法:
-
起别名(将数据库中名字改成实体类中对应的名字)
select id,name,pwd as password from mybatis.user where id=#{id};
-
结果集映射--resultMap(代替了resultType)
<!--去掉了resultType改用resultMap-->
<!--type表示要将其结果映射成User类-->
<resultMap id="UserMap" type="com.kuang.pojo.User">
<!--column表示数据库中字段名,property表示实体类中属性名
result标签的作用是将数据库中的字段名映射成实体类中的属性名,实际上只需改有变动的就行,不用动id和name这些没变化的字段-->
<!-- <result column="id" property="id"></result>
<result column="name" property="name"></result>-->
<result column="pwd" property="password"></result>
</resultMap>
<select id="getUserById" resultMap="UserMap">
select * from mybatis.user where id=#{id};
<!-- select id,name,pwd as password from mybatis.user where id=#{id};-->
</select>
4.2、resultMap的特点
-
resultMap元素是 MyBatis 中最重要最强大的元素。 -
ResultMap 的设计思想是,对简单的语句做到零配置(属性名和字段名一致的情况下,我们不需要对他们进行配置),对于复杂一点的语句,只需要描述语句之间的关系就行了。
-
ResultMap的优秀之处——当你熟悉时,完全可以不用显式地配置它们,就是和第二点一样,做到零配置!!
5. 日志
5.1、日志工厂
作用:如果一个数据库操作,出现了异常,我们需要快速排错!日志就是最好的助手。以往我们用到的排错手段是sout(控制台输出错误)和debug,现在我们学习日志工厂来排错!
日志工厂的配置代码如下:
<!--一定要注意这些标签的位置顺序!!-->
<!--注意:name的字母一定要注意大小写;value要注意前后不要多加空格-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

相应的value值如下:
-
SLF4J
-
LOG4J 【掌握】
-
LOG4J2
-
JDK_LOGGING
-
COMMONS_LOGGING
-
STDOUT_LOGGING 【掌握】
-
NO_LOGGING
在Mybatis中具体使用哪一个日志,由设置setting标签(在核心配置文件:mybatis-config.xml文件里)来决定!
5.2、STDOUT_LOGGING
STDOUT_LOGGING :标准日志输出,就是我们什么都不用配,直接使用即可!
下面我们来看一下控制台结果输出的区别显示:
-
使用日志工厂之后控制台输出的结果显示:


-
没用日志工厂的普通输出结果显示:

5.3、LOG4J
(1). 什么是log4j?
-
Log4j是
-
我们也可以控制每一条日志的输出格式
-
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
-
这些可以通过一个
(2). 在(子工程)pom.xml文件导入log4j的包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
(3). 在resources文件夹下写配置文件(log4j.properties)
#将等级为DEBUG的日志信息输出到console和file这两个目的地。console和file的定义在下面代码。
log4j.rootLogger=DEBUG,console,file
#在控制台输出的相关设置
#输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
#以sout这样的方式去输出
log4j.appender.console.Target=System.out
#Threshold:开端,界限
log4j.appender.console.Threshold=DEBUG
#输出的布局
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#布局的格式
log4j.appender.console.layout.ConversiconPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#文件输出的相关设置
#输出到文件
log4j.appender.file=org.apache.log4j.RollingFileAppender
#文件的路径以及命名;./表示当前项目路径
log4j.appender.file.File=./log/yuzhou.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
#日志输出级别
#会输出mybatis、sql、Statement、ResultSet、PreparedStatement这四个DEBUG时的日志信息
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.Statement=DEBUG
log4j.logger.java.ResultSet=DEBUG
log4j.logger.java.PreparedStatement=DEBUG
(4). 配置log4j为日志的实现方式
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
(5). 直接测试log4j的使用


(6). 简单使用log4j
1. 在要使用log4j的类中导入“org.apache.log4j.Logger”的包,切记不要导错!

2. 在创建的日志对象中参数为当前类的class
public class UserDaoTest {
// 通过反射来获得当前类的信息创建日志对象
static Logger logger = Logger.getLogger(UserDaoTest.class);
输出结果显示:

在当前项目中会生成一个log文件(因为前面在配置文件已经设置好了)

-
明确日志常使用的级别
//分别为不同的级别
logger.info("info:进入testlog4j方法"); //正常的输出
logger.debug("debug:进入testlog4j方法"); //调试时用的
logger.error("error:进入testlog4j方法"); //错误时使用,一般在try-catch时使用
6.实现数据分页
6.1、limit sql语句在mybaits中实现分页(面向sql)
(1). 为什么要分页?
-
减少数据的处理量
(2). 使用Limit分页
语法:select * from user limit startIndex,pageSize
语句1:select * from user limit 0,2
结果显示:会查出前面两条信息显示出来!
语句2:select * from user limit 3
结果显示:页面显示3条信息,从0开始算起
语句3:select * from user limit 2,-1
结果显示:现在会报错!但是在早期时可以使用的,表示从下标为2到数据结尾的信息全部显示出来。大家了解一下即可,现在这个已经没用了
startIndex:表示数据开始的下标位置,从0开始的
pageSize:表示页面大小
(3). 使用Mybatis实现分页核心就是sql)
1.写好UserMapper接口
//分页--使用万能的Map来传参
List<User> getUserByLimit(Map<String,Integer> map);
2.再写UserMapper.xml
<!--分页实现查询-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from user limit #{startIndex},#{pageSize}
</select>
3.最后写测试
6.2、RowBounds(面向对象)----不建议使用
(1).先写接口
//分页2--使用RowBound
List<User> getUserByRowBounds(Map<String,Integer> map);
(2).写UserMapper.xml
<!--分页2:RowBounds实现查询:具体分页操作由对象来做-->
<select id="getUserByRowBounds" resultMap="UserMap">
select * from user
</select>
(3). 写测试,在对象中实现分页操作
6.3、Mybatis分页插件(了解即可)
想了解的去这个官网了解即可,步骤很简单,文档说得很明白!! 
7. 使用注解开发
注意:针对mybatis来说,注解显得不太方便,建议还是使用配置文件来实现!!

本质:运用反射机制实现
底层:动态代理
7.1、简单实现步骤
(1). 注解在接口上实现
//在注解里写sql语句,写完之后要去配置文件绑定接口
(2). 需要在核心配置文件中绑定接口
<!--绑定接口-->
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
</mappers>
(3). 测试
(4). 结果显示

在mybatis里使用注解会出现一些问题的,虽然说可以解决,但是比较费劲,所以建议在mybatis中还是使用配置文件。
7.2、mybatis详细执行过程(debug走源码)
过程如下:
-
通过Resources获取加载核心配置文件
-
实例化SqlSessionFactoryBuilder构造器
-
解释配置文件流XMLConfigBuilder(源码)
-
返回Configuration所有的配置详细(源码)
-
实例化sqlSessionFactory
-
经过事务管理器transaction(debug)
-
创建executor执行器(debug)
-
创建sqlSession
-
实现CURUD,此时有事务了就会回滚到第6步事务管理器
-
查看事务是否执行成功,不成功也会回滚到事务管理器再次执行
-
提交事务
-
关闭
7.3、注解CRUD
(1). 设置成自动提交事务
public static SqlSession getSqlSession(){
//4.5 获得sqlSession
// SqlSession sqlSession=sqlSessionFactory.openSession(); //需要我们手动提交事务(可以查看源码看这个方法的重载)
SqlSession sqlSession=sqlSessionFactory.openSession(true); //直接设置成true,可以自动提交事务
return sqlSession;
}
(2). 编写各大接口,增加注解
//方法存在多个参数,所有的基本类型或String类型的参数必须
// 加上@Param("")注解
// 下面#里的名字是要Param里的名字要一致的
(3). 测试
注意:我们必须要先将接口注册绑定到我们的核心配置文件中!!
(4). 关于@Param( )注解
-
基本类型或String类型的参数,需要加上
-
引用类型不需要加
-
如果只有一个基本类型的话,可以不加,但建议大家加上
-
我们在SQL中引用的参数名就是我们这里的@Param()中设定的属性名
(5). #{}和${}的区别(了解)

8. Lombok
注意:使用注解来写 java的实体类,虽说可以快速帮你用注解写好实体类的get,set,toString等方法,但可读性差,还是不太建议使用,了解即可!!
8.1、使用步骤
(1). 在IDEA中安装Lombok插件


下载安装完之后需要重启一下IDEA才能使用这个插件!!
(2). 在项目中导入Lombok的jar包
去maven仓库里搜索Lombok的jar依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
(3). 了解一下它常用的注解
@Getter and @Setter:生成get、set方法 @FieldNameConstants:生成字段名常量 @ToString:生成toString方法 @EqualsAndHashCode:生成equals和hashCode方法 @AllArgsConstructor:生成有参构造
@RequiredArgsConstructor :生成特定参数的有参构造
@NoArgsConstructor:生成无参构造 @Data:生成无参构造、get、set、equal、toString、hashCode等方法
(4). 在实体类上加注解即可

(5). 使用Lombok的优缺点


9. 复杂查询
9.1、多对一环境搭建
(1). 关联:多对一(多个学生对一个老师)
集合:一对多(一个老师带多个学生)

(2). 搭建环境
-
导入lombok
-
新建实体类Teacher,Student以及数据库表
Teacher类:
Student类:
@Data
public class Student {
private int id;
private String name;
//学生需要关联一个老师,意味着每个学生要有一个老师属性
private Teacher teacher;
}
数据库:
CREATE 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);
-
建立Mapper接口
-
建立Maper.xml文件
TeacherMapper.xml:
StudentMapper.xml:
-
在核心配置文件中绑定注册我们的Mapper接口或者文件
<!--绑定接口-->
<mappers>
<mapper class="com.kuang.dao.TeacherMapper"/>
<mapper class="com.kuang.dao.StudentMapper"/>
</mappers>
-
测试查询是否能够成功
(3). 按照查询嵌套处理
<!--方法一:类似子查询-按照查询嵌套处理-->
<select id="getStudent" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"></result>
<result property="name" column="name"></result>
<!--复杂的属性我们需要单独处理
对象:association
集合:collection
-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{id}
</select>
(4). 按照结果嵌套处理
<!--方法二:按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid=t.id;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
(5). 回顾一下Mysql多对一的查询方式:
-
子查询
select id name from student where id=(select语句) -
联表查询
select s.id,s.name,t.name from student s,teacher t where s.tid=t.id;
(6). 踩过的bug坑:
就是如此简单的,却困扰了我一个晚上,希望遇到下面这个错的同学就此能快速排错:


原因:这里是因为在它们的.xml文件中写了多个resultMap,并且我给它们起了一样的名字!当我改过来时再去看上面的报错图片,发现它已经说的很明白了,就是我初学Mybatis还不能太明白,但这就是一个循序渐进的学习过程嘛,我相信大家也一定对这个过程爱恨交加,但也会在这个过程学到很多东西!
修改:
下面代码是在同一个.xml文件下的
<select id="getStudent" resultMap="StudentTeacher1">
select * from student
</select>
<resultMap id="StudentTeacher1" type="Student">
<result property="id" column="id"></result>
<result property="name" column="name"></result>
<!--复杂的属性我们需要单独处理
对象:association
集合:collection
-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getStudent2" resultMap="StudentTeacher1">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid=t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
大家看到上面的两个resultMap了吗?我当时这两个resultMap的名字是一样的,后来我将他们分别改成StudentTeacher1和StudentTeacher2,错误就迎刃而解了!!
9.2、一对多环境搭建
和前面步骤基本一样
(1). 导入lombok
(2). 新建实体类Teacher,Student
Teacher类:
Student类:
(3). 按照结果嵌套处理
<!--按结果嵌套查询-->
<select id="getTeacher2" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid=t.id and t.id=#{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"></result>
<result property="name" column="tname"></result>
<!--复杂的属性,我们需要单独处理。对象:association 集合:collection-->
<!--javaType是一个指定属性的类型
oType是指定集合List中泛型约束的类型
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
<result property="tid" column="tid"></result>
</collection>
</resultMap>
(4). 按查询嵌套处理
<!--按查询嵌套处理-->
<select id="getTeacher3" resultMap="TeacherStudent2">
select * from teacher where id=#{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="
Student" select="getStudentByTeacherID" column="id"></collection>
</resultMap>
<select id="getStudentByTeacherID" resultType="Student">
select * from student where tid=#{tid}
</select>
(5). 小结
-
关联-association
-
集合-collection
-
javaType和ofType
-
javaType:用来指定实体类属性的类型
-
ofType:用来指定映射到List或集合中的pojo类型,即泛型中的约束类型
-
-
注意:
-
要保证SQL的可读性
-
注意一对多和多对一中属性名和字段名的问题
-
如果问题不好排查错误的,可以使用日志,建议使用log4j
-
-
避免慢SQL
-
Mysql引擎
-
InnoDB底层原理
-
索引
-
索引优化!
-
10. 动态SQL
什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
-
if
-
choose (when, otherwise)
-
trim (where, set)
-
foreach
10.1、搭建环境
(1). 数据库表搭建
CREATE TABLE blog(
id varchar(50) NOT NULL COMMENT '博客iD',
title varchar(100) NOT NULL COMMENT '博客标题',
author varchar(30) NOT NULL COMMENT '博客作者',
creat_time datetime NOT NULL COMMENT '创建时间',
views int(30) NOT NULL COMMENT '浏览量'
) ENGINE=INNODB DEFAULT CHARSET=utf8
(2). 写实体类
解决特殊的属性名与字段名不一致的问题:在核心配置文件中加入以下设置
<!--是否开启驼峰命名转换规则-->
<setting name="mapUnderscoreToCamelCase" value="true"/>

(3). 增加一个工具类IDutils(用来生成随机、唯一的id)
(4). 编写接口类
public interface BlogMapper {
//插入数据
int addBlog(Blog blog);
}
(5). 编写.xml文件
(5). 运行测试类插入数据
public class MyTest {
10.2、if 标签
test:表示一个判断表达式,如果成立,那么就拼接相应的sql语句到主sql语句后面!!
<!--IF查询-->
<select id="queryBlogIF" parameterType="blog" resultType="Blog">
select * from blog where 1=1
<if test="title !=null">
and title=#{title}
</if>
<if test="author !=null">
and author=#{author}
</if>
</select>
10.3、choose (when, otherwise)标签
原理:有一个when条件成立了,后面的就不会再执行了,如果所有的when都不成立,那就执行otherwise条件的sql语句
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from blog
<where>
<choose>
<!--有一个when条件成立了,后面的就不会再执行了,如果所有的when都不成立,那就执行otherwise条件的sql语句-->
<when test="title !=null">
<!--第一个可以不写and-->
title=#{title}
</when>
<when test="author !=null">
and author=#{author}
</when>
<otherwise>
and views =#{views}
</otherwise>
</choose>
</where>
</select>
10.4、trim (where, set)
(1). where标签:它的作用就是,如果条件成立,可以帮你自动加上where,and,or等关键字;如果不能成立,也可以帮你去掉这些关键字,总之不让你的sql语句因为where,and的拼接而出错!!

<!--IF查询-->
<select id="queryBlogIF" parameterType="blog" resultType="Blog">
select * from blog
<where>
<if test="title !=null">
<!--第一个可以不写and-->
title=#{title}
</if>
<if test="author !=null">
and author=#{author}
</if>
</where>
</select>
上面两个if标签都成立,我们去日志那里看一下完整的失去了语句:

(2). set标签:它的作用就是用于update更新语句,如果条件成立,那么它会帮你自动补上set,并且把最后不相关的逗号去除,拼接成完整正确的失去了语句!!

<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title!=null">
title=#{title},
</if>
<if test="author!=null">
author=#{author}
</if>
</set>
where id = #{id}
</update>
上面两个if标签都成立,我们去日志看一下sql语句究竟是如何拼接的:

(3). trim标签:它的作用就是自定义where和set标签!!


由上面例子可以知道:
1. prefix="where" 表示在符合条件的拼接语句中自动补上where,因为and,or在拼接sql语句的前面,所以是prefixOverrides,表示删掉拼接语句中不该有的and或or;
2. 而prefix=“set“表示在符合条件的拼接语句中自动补上set,”是在sql语句的后面,所以是suffixOverrides,表示删除掉拼语句中不该有的“ ,”。
3. 注意:**prefix,prefixOverrides,suffixOverrides这些的值可以自己定义的,不固定是where,set等这些,这才是trim标签的作用之处!!但一般而言,where标签和set标签就足够用了,trim标签很少用到!!**
<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>
10.5、foreach标签
作用:遍历集合输出数据!!

例子:select * from user where 1=1 and (id=1 or id=2 or id=3)
<!--用foreach改编-->
<foreach item="id" index="index" collection="ids" open="and (" close=")" separator="or">
id = #{id}
</foreach>
(1). 说明:首先我们需要先定义一个集合传参过来给collection,然后id就是这个集合里面的数值,index是下标,一般很少用。最后我们组合语句,open表示以什么开头,close表示以什么结束,separator表示以什么分割;#{id}表示获取集合里面的数据!!
(2). .xml文件代码
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
(3). 测试类代码
(4). 结果显示

10.6、SQL片段
有时候,我们可能会将一些sql功能的部分抽取出来,方便复用!!
通过定义id和refid关联起来!!
(1). 使用SQL标签抽取公共的部分
<sql id="updateSql">
<if test="title!=null">
title=#{title},
</if>
<if test="author!=null">
author=#{author}
</if>
</sql>
(2). 使用include标签引用的部分
<update id="updateBlog" parameterType="map">
update blog
<set>
<include refid="updateSql"></include>
</set>
where id = #{id}
</update>
(3). 注意事项
-
最好基于单表来定义SQL片段!(因为如果是多表的话,会降低SQL片段的复用性就没有那种效果了)
-
不要存在where标签,尽量存在if标签就好了!
11. 缓存(了解)
11.1、简介
-
什么是缓存【Cache】?
-
存在内存中的临时数据
-
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高效率,解决了高并发系统的性能问题
-
-
为什么使用缓存?
-
减少和数据库的交互次数,减少系统开销,提高系统效率
-
-
什么样的数据能使用缓存?
-
经常查询并且不经常改变的数据
-
11.2、Mybatis缓存
-
Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率
-
Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称本地缓存)
-
二级缓存需要手动开启和配置,也是基于namespace级别的缓存
-
为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以实现Cache接口来自定义二级缓存
-
读写分离,主从复制
11.3、一级缓存
-
一级缓存也叫本地缓存
-
与数据库同一次会话期间查询的数据会放在本地缓存中
-
以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
-
(2). 测试步骤:
-
开启日志
-
测试类代码
-
查看日志输出
![image-20211103091715922]()
-
结果分析
上面代码分别查询两个相同的数据,第一次查询明显可以从日志中看出,它走了数据库执行了sql语句然后查了出来,而第二次查询没有执行sql是语句,更没有走数据库,而是从一级缓存中取出数据来,因为前后查的数据都是一样的,并且它们俩在缓存中的地址是一样的,因为是同一个东西嘛,所以最后比较时为true。
(3). 缓存失效的情况

-
增删改操作可能会改变原来的数据,所以必定刷新缓存,故缓存失效
结果显示:
![image-20211103093346500]()
因为期间夹杂了增删改其中的操作,而系统不知道你有没有改变先前查出来的东西,所以它就会刷新缓存从头来过,以保证结果的正确性!!
-
手动清理缓存
结果显示:
![image-20211103093818958]()
使用了手动清理缓存sqlSession.clearCache();,故缓存失效了!!
-
不同Mapper之间获取相同的内容缓存失效,因为已经超出一级缓存有效的作用域了
-
小结
一级缓存默认时开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区段有效。一级缓存就相当于是一个Map。
11.4、二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间对应一个二级缓存
-
工作机制
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
-
如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据保存到二级缓存中
-
新的会话查询信息,就可以从二级缓存中获取内容
-
不同的mapper查出的数据会放在自己对应的缓存(map)中
-
(2). 测试步骤
-
去核心配置文件设置开启缓存

<setting name="cacheEnabled" value="true"/>
-
去当前mapper开启二级缓存
a. 直接引入<cache/>就开启了(不建议使用)
<mapper namespace="com.kuang.dao.UserMapper">
<!--开启二级缓存-->
<!--需要去实体类序列化-->
<cache/>
<select id="queryUserById" parameterType="_int" resultType="user">
select * from user where id = #{id}
</select>
<update id="updateUser" parameterType="user">
update user set name=#{name},pwd=#{pwd} where id=#{id};
</update>
</mapper>
实体类:

b. 自定义缓存开启
<mapper namespace="com.kuang.dao.UserMapper">
<!--开启二级缓存-->
<!--自定义cache-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<select id="queryUserById" parameterType="_int" resultType="user">
select * from user where id = #{id}
</select>
<update id="updateUser" parameterType="user">
update user set name=#{name},pwd=#{pwd} where id=#{id};
</update>
</mapper>

注意:上面圈出来的那个部分的意思是,使用cache需要设置读写或显示定义实体类序列化,二者必选一个。当你直接用<cache/>标签引用缓存时,这时就需要去到实体类设置序列化,否则会报错;而当你自定义cache时里面包含了readOnly这个属性,它的内部也是将这个实体类序列化了,所以就不用序列化了!!
-
测试类

分析:上面代码开启了两个SqlSession,当第一个会话执行完操作查询出来结果后,关闭会话,用第二个SqlSession查询相同内容的时候,这时不需要再执行sql语句,直接从二级缓存那里获取相同的数据就行了!!
-
小结
-
只要开启了二级缓存,在同一个Mapper下就有效
-
所有的数据都会先放在一级缓存中
-
只有当会话提交或者关闭的时候,才会提交到二级缓冲中!
-
11.5、Mybatis缓存原理

11.6、自定义缓存--ehcache(了解即可)
我们现在做缓存,都基本用到redis这种非关系型数据库来做了,很少用到这些缓存。
Ehcache是一种广泛使用的开源java分布式缓存。主要面向通用缓存
要在程序中使用ehcache,先要导包
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
<!--自定义缓存:在当前Mapper文件使用ehcache实现缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
要写一个ehcache.xml配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir"/>
<!--
Mandatory Default Cache configuration. These settings will be applied to caches
created programmtically using CacheManager.add(String cacheName)
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
了解即可!!





浙公网安备 33010602011771号