Mybatis框架学习随笔记录

Mybatis框架学习随笔记录

使用JDBC连接数据[原始操作]

数据库驱动—->数据库连接—->创建查询(给参数赋值)—–>遍历查询结果——>关闭连接

为什么通过namespace + id 可以快速定位到sql?

SqlsessionFactoryBuilder.builde() 得到SessionFactory ——使用建造者模式创建。

resultMap 映射的时候,使用autoMapping=true 会进行自动映射
<resultMap id=”selectAll“ type=“com.example.pojo.TbUser” autoMapping=“true”>

传递参数—— JavaBean | 单一参数 需要使用@Param(“name”) String name;

useGeneratedKeys=“true” keyProperty=“id” 数据库主键是自增长。[最大序号进行加+1]


动态数据库表创建--------这个时候需要考虑使用${}  ---没有使用预编译,底层使用Statement

#{} 预编译,  会在参数上加上单引号,防止预编译,底层使用PrepareStatement 



<where></where>  去掉前and 

<set></set> 去掉最后的逗号

<trim></trim>

关联查询
保证三证表的join关联查询—关联字段需要使用索引 ;
如果超过三张表,需要考虑设计表表的时候适当的冗余字段
或者多次查询。

超过三证表的join ,会降低查询性能。

嵌套查询结果—-association | collection
List userIds
javaType —-相当于指 List类型
ofType —-相当于泛型中的String类型

lazy—-懒加载 ——一级缓存

Java动态代理
Spring事物注解实现的原理? | 为什么Mybatis可以直接使用mapper接口访问数据库?
代理模式

Mybatis会根据id标签,进行字段的合并,合理配置好ID标签可以提高处理的效率


Mybatis 一级缓存[默认是开启,如果要关闭 在select标签中设置 flushCache = "true"]
存在SqlSession的生命周期中,
同一个sqlSession中查询,Mybatis会把执行的方法和参数通过计算生成缓存的键值
将键值和查询结果存放到一个Map对象中。

如果同一个 SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,
当 Map 缓存对象中己经存在该键值时,则会返回缓存中的对象;

任何的 INSERT 、UPDATE 、 DELETE 操作都会清空一级缓存;


二级缓存:
1.存在于 SqlSessionFactory 的生命周期中,可以理解为跨sqlSession
2.缓存是以namespace为单位的,不同namespace下的操作互不影响
3.在MyBatis的核心配置文件中 cacheEnabled参数是二级缓存的全局开关,默认值是 true,
如果把这个参数设置为 false,即使有后面的二级缓存配置,也不会生效
要开启二级缓存,你需要在你的 SQL Mapper文件中添加配置
<cache eviction=“LRU" flushInterval="60000" size="512" readOnly="true"/> 
	- 映射语句文件中的所有 select 语句将会被缓存
	- 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存
	- 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回
	- 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新
	- 缓存会存储列表集合或对象(无论查询方法返回什么)的 512个引用
	- 缓存会被视为是 read/write(可读/可写)的缓存
使用二级缓存容易出现脏读,建议避免使用二级缓存,在业务层使用可控制的缓存代替更好

除了全局配置中开启二级缓存之外,还需要在mapper.xml中使用<cache></cache> 开启namespace 的二级缓存。就可以使用二级缓存
<cache-ref namespace=""/> 两个命名空间共享二级缓存。

一级缓存是sqlSession独享(线程独有的),不会出现脏读。

Mybatis源码分析 https://github.com/MyBatis/MyBatis-3 | DOC : https://mybatis.org/mybatis-3/zh/index.html

源码学习方法论:梳理流程 – 体系[梳理自己理解的骨架]

在工程目录下执行 mvn clean install -Dmaven.test.skip=true,将当前工程安装到本地仓库

接口层:sqlSession

核心处理层:配置解析 | 参数映射 | SQL解析 | SQL执行 | 结果映射 | 插件

基础支撑层:数据源 | 事物管理 | 缓存 | Binding模块 | 反射 | 类型转换 | 日志模块 | 资源加载 | 解析器

从架构可以看出使用了外观设计模式 [平时我们开发controller | service /serviceImpl | dao ]—–迪米特法则(最少知识原则)
DispathServlet —使用了外观模式。

代码和系统维护性更高 | 提高团队协作开发效率和职责分离| 系统的伸缩性

————————–七大设计原则
最少知识原则—-和其他类尽量不要进行耦合依赖,对其他对象保持最少的了解
依赖倒置原则
接口隔离原则
里氏替换原则————–引用基类地方必须能够透明的使用其子类的对象
单一职责原则
开闭原则
组合聚合原则

使用了的设计模式:

外观模式

装饰者模式

代理模式

适配器模式

日志模块
适配器设计模式 | 代理模式设计模式

问题一:
MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同,而MyBatis统一提供了trace、debug、warn、error四个级别;
解决方式:使用适配器模式
角色:
Target:目标角色,期待得到的接口.
Adaptee:适配者角色,被适配的接口.
Adapter:适配器角色,将源接口转换成目标接口

适用场景:当调用双方都不太容易修改的时候,为了复用现有组件可以使用适配器模式;在系统中接入第三方组件的时候经常被使用到;
银联支付 | 支付宝支付 | 微信支付 对接我们自己的支付接口 —-经典使用方式。

//mybatis提供的日志接口(我们自己的接口)
public interface Log {

boolean isDebugEnabled();

boolean isTraceEnabled();

void error(String s, Throwable e);

void error(String s);

void debug(String s);

void trace(String s);

void warn(String s);

}

/**

  • 实现 我们自己写的标准接口。
  • jdk日志适配器
    我们自己的接口 需要适配其他接口。让两个接口能够适配。
    */
    public class Jdk14LoggingImpl implements Log {

    //真正提供日志能力的jdk日志—适配的接口
    private final Logger log;

public Jdk14LoggingImpl(String clazz) {

log = Logger.getLogger(clazz);
}

@Override
public boolean isDebugEnabled() {

return log.isLoggable(Level.FINE);
}

@Override
public boolean isTraceEnabled() {

return log.isLoggable(Level.FINER);
}

@Override
public void error(String s, Throwable e) {

log.log(Level.SEVERE, s, e);
}

@Override
public void error(String s) {

log.log(Level.SEVERE, s);
}

@Override
public void debug(String s) {

log.log(Level.FINE, s);
}

@Override
public void trace(String s) {

log.log(Level.FINER, s);
}

@Override
public void warn(String s) {

log.log(Level.WARNING, s);
}

}

适配模式遵循了:开闭原则 依赖倒置原则(程序要依赖于抽象接口,不要依赖于具体实现) | 单一职责原则

适配器模式——–合理运用继承、实现方式让两个不兼容的接口进行兼容。
类级别的适配器 | 接口级别的适配器。

问题二:
自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;

MyBatis提供的LogFactory

public final class LogFactory {

public static final String MARKER = “MYBATIS”;
/**

  • 被选定的第三方日志组件适配器的构造方法
    */
    private static Constructor<? extends Log> logConstructor;

/**

  • 自动扫描日志实现
  • 并且第三方日志插件加载优先级
  • slf4j–>commonsLoggin–>Log4j2–>Log4j–>JdkLog
    */
    static {

    // :: jdk8 方法的引用
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
    }

private LogFactory() {

// disable construction
}

public static Log getLog(Class<?> clazz) {

return getLog(clazz.getName());
}

public static Log getLog(String logger) {

try {

return logConstructor.newInstance(logger);
} catch (Throwable t) {

throw new LogException(“Error creating logger for logger ” + logger + “. Cause: ” + t, t);
}
}

public static synchronized void useCustomLogging(Class<? extends Log> clazz) {

setImplementation(clazz);
}

public static synchronized void useSlf4jLogging() {

setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}

public static synchronized void useCommonsLogging() {

setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
}

public static synchronized void useLog4JLogging() {

setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}

public static synchronized void useLog4J2Logging() {

setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}

public static synchronized void useJdkLogging() {

setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}

public static synchronized void useStdOutLogging() {

setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
}

public static synchronized void useNoLogging() {

setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
}

private static void tryImplementation(Runnable runnable) {

//当构造方法不为空才执行方法
if (logConstructor == null) {

try {

runnable.run();
} catch (Throwable t) {

// ignore
}
}
}
//通过指定的log类来初始化构造方法
private static void setImplementation(Class<? extends Log> implClass) {

try {

Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {

log.debug(“Logging initialized using ‘” + implClass + “’ adapter.”);
}
logConstructor = candidate;
} catch (Throwable t) {

throw new LogException(“Error setting Log implementation. Cause: ” + t, t);
}
}

}

问题三:日志的使用要优雅的嵌入到主体功能中
代理模式设计模式

=====================>代理设计模式(静态代理和动态代理[jdk动态代理(实现接口方式)和cglib代理(通过继承方式)])
代理模式:间接访问目标对象 ,防止直接访问目标对象给系统带来不必要复杂性。
通过代理对象对原有的业务增强。[proxy—-AOP:前置通知|后置通知 | 环绕通知 等]
JDK (实现接口方式) | cglib代理 (通过继承方式)

代理模式使用的场景:
1.各大数码专营店,代理厂商进行销售对应的产品,代理商持有真正的授权代理书
2.客户端不想直接访问实际对象,或者访问实际的对象存在困难,通过一个代理对象来完成间接的访问
3.想在访问一个类时做一些控制,或者增强功能[比如方法的增强,除了代理模式—还可以使用装饰者模式对方法的增强]

Spring事物注解原理? | 为什么Mybatis 通过mapper接口访问数据库?
静态代理:
角色:

实现公共接口(抽象接口[接口或者抽象类]) | 代理对象 包含了真实对象[组合/聚合],从而可以随意的操作真实对象的方法。| realProject 真实对象。好比厂商销售数码产品

代理模式:静态代理 | 动态代理

静态代理—-针对某一种业务进行代理.扩展型、维护性差

开闭原则
单一原则
最少知识原则
依赖倒置原则
迪米特原则
接口隔离原则
组合聚合原则

/IO—-流 BIO NIO/
静态代理模式代码:
public interface ManToolsFactroy{

void saleManTools(String size);
}

public class ATools implements ManToolsFactroy{

public void saleManTools(String size){
    System.out.println("你要购买的尺寸为:"+size);
}

}

public class MyProxy implements ManToolsFactroy{

private ATools aTools;

public MyProxy(ATools aTools){
    this.aTools = aTools;
}


public void saleManTools(String size){
   System.out.println("购买前进行商品调研.....")
   aTools.salManTools(size);
   System.out.println("谢谢购买")
}

}

public class Client {

public static void main(String[] args){

ATools aTools = new ATools();
MyProxy myProxy = new MyProxy(aTools);
myProxy.saleManTools(“XL尺寸”)

}

}

违背了开闭原则,维护性差 ; 业务单一

==============>动态代理模式:

代理类进行抽象扩展,

Proxy 调度者 针对不同业务需求调度不同代理实例来处理需求

InvocationHandler [接口 行为规范的制定者] 具体处理者

单一职责原则。

类加载器 | 接口 | this

jdk动态代理 Proxy

动态代理类.

//公共接口
public interface Subject {

void doSomething();
}

//实现公共接口—-被代理的类
public class RealSubject implements Subject {

@Override
public void doSomething() {

System.out.println(“RealSubject do something”);
}
}

//动态代理类实现InvocationHandler接口.
public class JDKDynamicProxy implements InvocationHandler {

private Object target;

public JDKDynamicProxy(Object target) {
    this.target = target;
}

/**
 * 获取被代理接口实例对象
 * @param <T>
 * @return
 */
public <T> T getProxy() {
    return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("Do something before");
    Object result = method.invoke(target, args);
    System.out.println("Do something after");
    return result;
}

}

// jdk动态代理测试
Subject subject = new JDKDynamicProxy(new RealSubject()).getProxy();
subject.doSomething();

参考资料:https://www.cnblogs.com/zuidongfeng/p/8735241.html

class对象存放到方法区中。实例对象存放到堆里面的。
对象不可达,没有被引用的时候会被垃圾回收器进行回收.

JVM | 性能调优

动态代理本质—在内存中直接生成了jdk字节码

池化技术
PoolDataSoruce —–watie | notify 并发技术小知识点.
分析整合设计能力—–设计模式

代理模式和适配器模式的区别
代理模式: 代理类和被代理类都需要实现相同的接口。代理类需要组合被代理类
适配器模式只需要适配器类去实现需要目标接口,让外部需要适配的接口和目标适配的接口进行适配.

问题四:在MyBatis中那些地方需要打印日志?

  • 在创建prepareStatement时,打印执行的SQL语句;
  • 访问数据库时,打印参数的类型和值
  • 查询出结构后,打印结果数据条数

ConnectionLogger:负责打印连接信息和SQL语句,并创建PreparedStatementLogger;
PreparedStatementLogger:负责打印参数信息,并创建ResultSetLogger
ResultSetLogge:r负责打印数据结果信息;

使用jdk动态代理 . 实现InvocationHandler 接口 Proxy.newInstance 创建对象

在哪里进行触发 ? execute组件中进行触发的。

数据源模块
池化技术
javax.sql.DataSource接口
MyBatis不但要能集成第三方的数据源组件,自身也提供了数据源的实现
工厂方法  Factory Method
简单工厂 Simple Facotry  ----- 对象的创建  和对象的使用 进行分离[解耦]
SimpleFactory 违背了单一职责原则 和 开闭原则
参与角色:
1.产品接口Product
2.具体产品类ConcreateProduct
3.工厂接口Factory
4.具体工厂接口 ConcreateFactroy
请详细描述从数据库连接池中获取一个连接资源的过程?数据结构和算法。
MyBatis --PoolDataSource
PooledDataSource:一个简单,同步的、线程安全的数据库连接池
PooledConnection:使用动态代理封装了真正的数据库连接对象;
PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别 管理空闲状态的连接资源和活跃状态的连接资源,数据库俩接放入到了一个ArrayList集合中来进行管理.
数据接口使用两个ArrayList  一个保存闲置的连接 | 一个是活跃连接  | 其他计数器。
比如:PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别
protected PooledDataSource dataSource;
//空闲的连接池资源集合
protected final List<PooledConnection> idleConnections = new ArrayList<>();
//活跃的连接池资源集合
protected final List<PooledConnection> activeConnections = new ArrayList<>();
//请求的次数
protected long requestCount = 0;
//累计的获得连接的时间
protected long accumulatedRequestTime = 0;
//累计的使用连接的时间。从连接取出到归还,算一次使用的时间;
protected long accumulatedCheckoutTime = 0;
//使用连接超时的次数
protected long claimedOverdueConnectionCount = 0;
//累计超时时间
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
//累计等待时间
protected long accumulatedWaitTime = 0;
//等待次数 
protected long hadToWaitCount = 0;
//无效的连接次数 
protected long badConnectionCount = 0;
算法:
/**
*
*回收连接资源
*/
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {//回收连接必须是同步的 --- 线程安全问题
state.activeConnections.remove(conn);//从活跃连接池中删除此连接
if (conn.isValid()) {
//判断闲置连接池资源是否已经达到上限
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
//没有达到上限,进行回收
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
//如果还有事务没有提交,进行回滚操作
conn.getRealConnection().rollback();
}
//基于该连接,创建一个新的连接资源,并刷新连接状态
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
//老连接失效
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
//唤醒其他被阻塞的线程
state.notifyAll();
} else {//如果闲置连接池已经达到上限了,将连接真实关闭
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//关闭真的数据库连接
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
//将连接对象设置为无效
conn.invalidate();
}
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
//从连接池获取资源
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();//记录尝试获取连接的起始时间戳
int localBadConnectionCount = 0;//初始化获取到无效连接的次数
while (conn == null) {
synchronized (state) {//获取连接必须是同步的
if (!state.idleConnections.isEmpty()) {//检测是否有空闲连接
// Pool has available connection
//有空闲连接直接使用
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {// 没有空闲连接
if (state.activeConnections.size() < poolMaximumActiveConnections) {//判断活跃连接池中的数量是否大于最大连接数
// 没有则可创建新的连接
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {// 如果已经等于最大连接数,则不能创建新连接
//获取最早创建的连接
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {//检测是否已经以及超过最长使用时间
// 如果超时,对超时连接的信息进行统计
state.claimedOverdueConnectionCount++;//超时连接次数+1
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;//累计超时时间增加
state.accumulatedCheckoutTime += longestCheckoutTime;//累计的使用连接的时间增加
state.activeConnections.remove(oldestActiveConnection);//从活跃队列中删除
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {//如果超时连接未提交,则手动回滚
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {//发生异常仅仅记录日志
/*
Just log a message for debug and continue to execute the following
statement like nothing happend.
Wrap the bad connection with a new PooledConnection, this will help
to not intterupt current executing thread and give current thread a
chance to join the next competion for another valid/good database
connection. At the end of this loop, bad {@link @conn} will be set as null.
*/
log.debug("Bad connection. Could not roll back");
}  
}
//在连接池中创建新的连接,注意对于数据库来说,并没有创建新连接;
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
//让老连接失效
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// 无空闲连接,最早创建的连接没有失效,无法创建新连接,只能阻塞
try {
if (!countedWait) {
state.hadToWaitCount++;//连接池累计等待次数加1
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);//阻塞等待指定时间
state.accumulatedWaitTime += System.currentTimeMillis() - wt;//累计等待时间增加
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {//获取连接成功的,要测试连接是否有效,同时更新统计数据
// ping to server and check the connection is valid or not
if (conn.isValid()) {//检测连接是否有效
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();//如果遗留历史的事务,回滚
}
//连接池相关统计信息更新
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {//如果连接无效
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;//累计的获取无效连接次数+1
localBadConnectionCount++;//当前获取无效连接次数+1
conn = null;
//拿到无效连接,但如果没有超过重试的次数,允许再次尝试获取连接,否则抛出异常
if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
}
return conn;
}
并发编程  线程等待  |  唤醒线程

Mybatis的一级缓存。生命周期在同一个sqlSession ,基于Map来实现。

怎么样优雅的为核心功能添加多种附加能力?
使用动态代理或继承的办法扩展多种附加功能?

这些方式是静态的,用户不能控制增加行为的方式和时机。另外,新功能的存在多种组合,使用继承可能导致大量子类存在;

优化思路:装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。
使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀

======>装饰者模式 对类进行增强。

对真实的对象进行包装。 在包装类中依赖需要被包装的类或者对象。[通过组合 | 聚合]

多种组合形式对目标对象功能进行增强。这个时候需要采用装饰模式进行增强。

代理模式 对增强的功能只有一两个的时候可以使用代理模式

public interface Cache | public class PerpetualCache implements Cache

decorator: 比如: public class BlockingCache implements Cache [其实可以不用去实现要被装饰的对象]


缓存雪崩 | 缓存穿透 | 缓存击穿

缓存雪崩—->阻塞式缓存[性能会下降—–锁粗粒度—-细粒度锁 按照key blockingCache 细粒度锁,请求作为key, 锁作为value 存放在ConcurrentHashMap]

Mybatis的缓存功能使用HashMap实现会不会出现并发安全的问题?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/100721.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号