目录
一、缓存 Cache
缓存:存在内存中的临时数据
使用缓存可以减少对硬盘(数据库)的访问次数(IO操作),提高查询效率,减少系统开销,解决了高并发系统的性能问题。
1.MyBatis缓存分类
一级缓存:自动开启,默认开启,针对同一个SqlSessio (不同的SqlSession有不同的一级缓存)
二级缓存:手动开启,默认关闭,针对同一个SqlSessioinFactory (由这一个SqlSessionFactory) 创建的所有SqlSession可以共享二级缓存中的数据)。
缓存命中率 = 命中缓存的次数/查询的总次数
查询的顺序是:
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- 查询数据库的结果,会立刻放入一级缓存 (不会立刻放入二级缓存)
- SqlSession关闭之前,一级缓存中的数据会写入二级缓存
2.一级缓存
一级缓存失效的情况
- 不是同一个SqlSession
- 同一个SqlSession但是查询条件发生了变化
- 同一个SqlSession两次查询期间执行了任何一次增删改操作 (清空、刷新一级缓存)
- 同一个SqlSession两次查询期间手动清空了缓存
- 同一个SqlSession两次查询期间提交了事务
3.二级缓存
结论:SqlSession关闭的时候,一级缓存中的内容会被存入二级缓存
1.全局开关:默认开启
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2.局部开关:二级缓存以namespace为单位。
<mapper namespace="com.atguigu.mapper.EmployeeMapper">
<!--二级缓存的分开关,针对该命名空间下的查询操作-->
<cache/>
<select id="findById" resultType="employee">
SELECT emp_id empId ,emp_name empName,emp_salary empSalary
FROM t_emp
WHERE emp_id = #{empId}
</select>
</mapper>
3.实体类要序列化
什么时候需要序列化:在在内存中存储不需要序列化,如果内存的数据要存入硬盘,或者在网络上传输,就要序列化(其实是变成字节数组)
一级缓存肯定在内存中,二级缓存可能在内存中,但是二级缓存数据多,也可以存储到外存中,所以存在二级缓存中的数据必须序列化。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable {
private Integer empId;
private String empName;
private Double empSalary;
}
4.测试
运行之前访问一级缓存的代码,发现已经查询二级缓存,但是在二级缓存中一次也没有发现数据
一级缓存的数据,何时放入二级缓存:
SqlSession关闭的时候,一级缓存中的内容会被存入二级缓存
//二级缓存
@Test
public void testCache2(){
//创建SqlSession1
SqlSession sqlSession1 = factory.openSession();
EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
Employee emp1 = mapper1.findById(21);
System.out.println(emp1);
sqlSession1.close(); //关闭了sqlSession,存入二级缓存
//创建SqlSession2
SqlSession sqlSession2 = factory.openSession();
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
Employee emp2 = mapper2.findById(2);
System.out.println(emp2);
}
二级缓存分开关的设置
<cache type="" size="" blocking="" eviction="" flushInterval="" readOnly=""/>
- Type:指定具体的二级缓存类型。如果不指定,就使用MyBatis自带的二级缓存。
- Size:代表缓存最多可以存储多少个对象,太大容易导致内存溢出
- flushInterval:刷新(清空缓存)间隔。默认情况是不设置,也就是没有刷新间隔
- readOnly:只读。True:性能高 false,性能低,安全性高
- Eviction:缓存清除策略。比如二级缓存中最多放100个缓存内容,第101来了,怎么办?
- LRU – 最近最少使用(默认):移除最长时间不被使用的对象 (Least Recently Used )
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。(First in First out)
- Blocking 阻塞:访问某个缓存数据,在访问过程中对key(sql语句)加锁,其他的请求同一个SQL语句,要等待。保证了只有一个请求来访问缓存中的一份数据。
4.第三方自定义缓存:整合Ehcache
Ehcache是一种开源的、缓存的技术。整合Ehcache,就是使用Ehcache做MyBatis的二级缓存
①添加依赖
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
②整合EHCache
在resources下创建配置文件:ehcache.xml。文件内容:
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
③加入log4j适配器依赖
存在SLF4J时,是无法直接访问log4j的,需要加入一个适配器类:slf4j-log4j12。
因为log4j不是slf4j的直接实现
<!-- SLF4j和log4j的适配器类 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>
④mapper.xml 指明二级缓存使用Ehcache
<mapper namespace="com.atguigu.mapper.EmployeeMapper">
<!--二级缓存的分开关,针对该命名空间下的查询操作-->
<!--type:使用了ehcache二级缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
⑤测试查看结果
使用了Ehcache的效果:
Ehcache的配置项(了解)
属性名 | 是否必须 | 作用 |
---|---|---|
maxElementsInMemory | 是 | 在内存中缓存的element的最大数目 |
maxElementsOnDisk | 是 | 在磁盘上缓存的element的最大数目,若是0表示无穷大 |
eternal | 是 | 设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断 |
overflowToDisk | 是 | 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 |
timeToIdleSeconds | 否 | 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大 |
timeToLiveSeconds | 否 | 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大 |
diskSpoolBufferSizeMB | 否 | DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区 |
diskPersistent | 否 | 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。 |
diskExpiryThreadIntervalSeconds | 否 | 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作 |
memoryStoreEvictionPolicy | 否 | 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出) |
二、日志框架
① 日志框架选择
情况1:如果开发项目中已经确定采用哪种具体日志系统,直接使用即可(不引入SLF4J等抽象的日志门面)。
- log4j:日志框架的鼻祖,作者Ceki Gülcü将其捐献给Apache开源,吸引了无数的人无偿帮助测试它,扩展它,改进它,很快就成了最流行的日志工具。
- JDK logging:从JDK1.4开始,JDK中提供了java.util.logging包,也来记录日志。这可是官网日志包,而不是第三方日志包,但是已经无法撼动log4j了。
- log4j2:2015年9月,Apache软件基金业宣布,Log4j不在维护,建议所有相关项目升级到Log4j2。log4j2性能优异。但内部实现和log4j完全不同
情况2:如果开发项目中无法确定采用哪种具体的日志系统,使用SLF4J等日志门面,也要引入一种具体的日志框架。引入一个抽象的日志框架:SLF4j
- SLF4j:由log4j的作者设计完成的,简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,而是通过Facade Pattern提供一些Java logging API,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。作者创建SLF4J的目的是为了替代Jakarta Commons-Logging。
- Jakarta Commons-Logging,现在叫Apache Commons-Logging,也是一个日志门面,希望解决的问题和Slf4j类似。common-logging通过动态查找的机制,在程序运行时自动找出真正使用的日志库; slf4j在编译时静态绑定真正的Log库。
- logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架,并给出了slf4j的原生实现(即直接实现了slf4j的接口,而log4j因为出现早于slf4j,并没有直接实现,就需要一个适配器slf4j-log4j12.jar)。
② 日志框架类型
门面:
名称 | 说明 |
---|---|
JCL(Jakarta Commons Logging) | 陈旧 |
SLF4J(Simple Logging Facade for Java)★ | 适合 |
jboss-logging | 特殊专业领域使用 |
实现:★表示同一个作者
名称 | 说明 |
---|---|
log4j★ | 最初版 |
JUL(java.util.logging) | JDK自带 |
log4j2 | Apache收购log4j后全面重构,内部实现和log4j完全不同 |
logback★ | 优雅、强大 |
③ logback的使用
[1] 依赖信息
<!-- slf4j日志门面的一个具体实现:logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
[2] 配置信息
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>
如果有过个日志框架,哪个依赖在前,就启用哪个。
logback:时间在前
三、缓存源码 (装饰模式)
设计模式:装饰模式 :动态组装
蛋糕接口:(Cache接口)
蛋糕实现类:奶油蛋糕 冰激凌蛋糕(PerpetualCache EhcacheCache)
蛋糕的装饰:卡片、干果、蜡烛(FifoCache、LruCache是添加了装饰的蛋糕)
如果采用继承,就会数不清的实现类。采用装饰模式,可以减少子类的数量,是继承的一种替代方案。现场组装。
蛋糕接口:(InputStream抽象类)
蛋糕实现类:奶油蛋糕 冰激凌蛋糕(FileInputStream ByteArrayInputStream)
蛋糕的装饰:卡片、干果、蜡烛(BufferedInputStream DataInputStream)
1.Cache相关的API
Cache内部有一个Map,存储缓存的信息
所有的装饰类中必须有一个Cache的引用,说明对谁进行装饰。(类似包装流内的节点流)
PerpetualCache 是Mybatis的默认缓存,也是Cache接口的默认实现。Mybatis一级缓存和自带的二级缓存都是通过 PerpetualCache 来操作缓存数据的。
PerpetualCache 如何区分不同级别的缓存:根据调用者不同
- 一级缓存:由BaseExecutor调用PerpetualCache
- 二级缓存:由CachingExecutor调用PerpetualCache,而CachingExecutor可以看做是对BaseExecutor的装饰
2.一级Cache的源码
3.二级Cache的源码
底层使用Ehcache
底层使用自带的二级缓存
底层不使用二级缓存:会调用一级缓存
四、逆向工程(了解)
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成Java实体、\Mapper接口、Mapper配置文件
添加插件
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
2.添加、修改配置文件 generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis-example?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"
userId="root"
password="*******">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.atguigu.pojo" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Employee"/>
<table tableName="t_customer" domainObjectName="Customer"/>
<table tableName="t_order" domainObjectName="Order"/>
</context>
</generatorConfiguration>
进行测试
QBC(了解)
QBC:Query By Criteria 查询标准
QBC查询最大的特点就是将SQL语句中的WHERE子句进行了组件化的封装,让我们可以通过调用Criteria对象的方法自由的拼装查询条件。
// 1.创建EmployeeExample对象
EmployeeExample example = new EmployeeExample();
// 2.通过example对象创建Criteria对象
EmployeeExample.Criteria criteria01 = example.createCriteria();
EmployeeExample.Criteria criteria02 = example.or();
// 3.在Criteria对象中封装查询条件
criteria01
.andEmpAgeBetween(9, 99)
.andEmpNameLike("%o%")
.andEmpGenderEqualTo("male")
.andEmpSalaryGreaterThan(500.55);
criteria02
.andEmpAgeBetween(9, 99)
.andEmpNameLike("%o%")
.andEmpGenderEqualTo("male")
.andEmpSalaryGreaterThan(500.55);
SqlSession session = factory.openSession();
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
// 4.基于Criteria对象进行查询
List<Employee> employeeList = mapper.selectByExample(example);
for (Employee employee : employeeList) {
System.out.println("employee = " + employee);
}
session.close();
// 最终SQL的效果:
// WHERE ( emp_age between ? and ? and emp_name like ? and emp_gender = ? and emp_salary > ? ) or( emp_age between ? and ? and emp_name like ? and emp_gender = ? and emp_salary > ? )
五、其他
1.Mapper映射、实体类别名
- Mapper映射
注意:Mapper配置文件夹名称 需要和 mapper接口保持一致
Mapper配置文件放在Mapper接口所在的包内
- 实体类别名
<typeAliases>
<!--给一个包下所有类起别名,别名是类名首字母小写-->
<package name="com.atguigu.pojo"/>
<!--给单个实体类起别名 不推荐-->
<!--<typeAlias type="com.atguigu.pojo.Employee" alias="employee"></typeAlias>-->
</typeAliases>
2.插件机制(了解)
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。
著名的Mybatis插件包括 PageHelper(分页插件)、通用 Mapper(SQL生成插件)等。
如果想编写自己的Mybatis插件可以通过实现org.apache.ibatis.plugin.Interceptor接口来完成
但是由于插件涉及到Mybatis底层工作机制,在没有足够把握时不要轻易尝试。
3.类型处理器typeHandler(了解)
Mybatis内置类型处理器
Mybatis自定义类型处理器
1.开发自定义类型转换器:AddressTypeHandler
//自定义类型转换器
public class AddressTypeHandler extends BaseTypeHandler<Address> {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Address address, JdbcType jdbcType) throws SQLException {
}
@Override
public Address getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
String name = resultSet.getString(columnName);
System.out.println(name);
String[] adds = name.split(",");
Address address = new Address(adds[0],adds[1],adds[2]);
return address;
}
@Override
public Address getNullableResult(ResultSet resultSet, int i) throws SQLException {
return null;
}
@Override
public Address getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}
}
2.在配置文件中进行注册
mybatis-config.xml
<!--注册类型转换器-->
<typeHandlers>
<typeHandler
jdbcType="VARCHAR"
javaType="com.atguigu.pojo.Address"
handler="com.atguigu.handlers.AddressTypeHandler"/>
</typeHandlers>
3.修改实体类、数据库表,测试
六、MBatis底层对JDBC的封装
SqlSession是一个接口,这里返回的是其实现类DefaultSqlSession的一个实例。其中有一个Executor类型的成员变量。其实进行数据库操作时SqlSession就是一个门面,真正干活的却是Executor。
1.Mbatis底层四大对象:
你以为的四大对象:SqlSessionFactory,SqlSession,Mapper,
JDBC四大对象:DriverManager Connection Statement ResultSet
真正的四大对象:Executor、StatementHandler、ParameterHandler、ResultSetHandler
①Executor
执行器,由它来调度 StatementHandler。
②StatementHandler
使用数据库的 Statement 、PreparedStatement 执行操作
③ParameterHandler
处理SQL参数;比如查询的where条件、和insert、update的字段值的占位符绑定。
④ResultSetHandler(只针对查询操作)
处理结果集ResultSet的封装返回。将数据库字段的值赋给实体类对象的成员变量。
2.Mybatis底层四大步骤:
BaseExecutor 父类 | SimpleExecutor 子类 | Statement JDBC | |
查询 | query() | doQuery() | executeQuery() |
DML | update() | doUpdate() | executeUpdate() |
第一步:获取数据库连接
Connection connection = getConnection(statementLog);
第二步:获取Statement、PreparedStatement
Statement stmt = handler.prepare(connection, transaction.getTimeout());
第三步:处理参数 (SELECT和DML),给SQL语句中的占位符赋值 ParameterHanlder
handler.parameterize(stmt);
第四步:执行操作
return handler.update(stmt);//insert update delete
return handler.query(stmt, resultHandler); //select 会涉及到结果集ResultHandler
handler是StatementHandler,果真是常务副总经理
七、Mybatis大总结
一、简介
理解框架
生活案例:毛坯房;技术理解:jar+配置文件。
好处:提供开发效率 统一了开发风格。
ORM
Object-Relational Mapping 对象关系映射。
数据库表---类 数据库表字段---类的成员变量 数据库表记录--对象。
开发者对对象进行操作,结果反应到数据库表,避免了手动的转换。
和Hibernate对比
都是持久层框架,都是对JDBC进行了封装。
半自动化的MyBatis战胜了全自动化的Hibernate。
MyBatis将手写SQL语句的工作丢给开发者,可以更加精确的定义SQL,更加灵活,也便于优化性能。
符合互联网高并发、大数据、高性能、高响应的要求,使它取代Hibernate成为了Java互联网中首选的持久框架.
二、单表操作(基础)
项目构成
配置文件:数据库连接参数、别名、开启二级缓存、指定映射文件的位置.
映射文件:指定SQL语句(select、insert、update、delete)
测试类:SqlSessionFactory、SqlSession、Mapper
两种方式
无接口方式,直接调用SqlSession的方法(selectList、selectOne、update...)
Mapper接口方式:需提供接口,但是实现使用动态代理动态生成。(推荐)
数据输入:#{} ${}
数据输出:resultType resultMap
三、多表操作(重点难点)
多表连接查询
优点:一条SQL语句,速度快。
缺点:不灵活,只有立即加载没有延迟加载。
分步查询
缺点:多条SQL语句,速度慢。
优点:灵活,不仅有立即加载还有延迟加载。
Java类中添加属性
Customer:List<Order> orderList = new ArrayList()
Order:Customer customer;
XML映射文件中配置
association
collection
重点掌握一对多操作基础上能够完成一对一、多对多的操作。
四、动态SQL(开发中有应用)
使用场合:避免了多条件查询下StringBuilder和append的字符串拼接,还可以应用insert、update操作
if where trim 处理多条件查询,有几个条件就执行几个
choose/when/otherwise 只执行第一个满足条件的条件
foreach:循环
set:update
sql :提取SQL片段,避免重复修改
五、缓存
作用:减少数据库访问次数,提高查询速度
分类:一级缓存(SqlSession)、二级缓存(SqlSessionFactory)
先查询二级缓存,再查询一级缓存、最后是查询数据库
一级缓存自动开启,二级缓存需要手动开启,可以引入第三方的二级缓存,比如Ehcache
日志框架体系,建议slf4j门面+logback/log4j2具体日志框架
六、底层源码原理(面试中使用)
四大对象:Executor、StatementHandler、ParameterHandler、ResultSetHandler
四大步骤:获取数据库连接、获取Statement|PreparedStatement、处理参数 (SELECT和DML),给SQL语句中的占位符赋值 ParameterHanlder、执行操作
缓存的源码
读取属性文件