前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis框架学习随笔记录

Mybatis框架学习随笔记录

作者头像
全栈程序员站长
发布2022-06-30 11:21:52
1960
发布2022-06-30 11:21:52
举报
文章被收录于专栏:全栈程序员必看

使用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]

代码语言:javascript
复制
动态数据库表创建--------这个时候需要考虑使用${}  ---没有使用预编译,底层使用Statement

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



<where></where>  去掉前and 

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

<trim></trim>

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

代码语言:javascript
复制
超过三证表的join ,会降低查询性能。

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

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

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

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

代码语言:javascript
复制
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{

代码语言:javascript
复制
public void saleManTools(String size){
    System.out.println("你要购买的尺寸为:"+size);
}

}

public class MyProxy implements ManToolsFactroy{

代码语言:javascript
复制
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尺寸”)

代码语言:javascript
复制
}

}

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

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

代理类进行抽象扩展,

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 {

代码语言:javascript
复制
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组件中进行触发的。

代码语言:javascript
复制
数据源模块

池化技术

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实现会不会出现并发安全的问题?

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年7月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 缓存雪崩 | 缓存穿透 | 缓存击穿
相关产品与服务
Prowork 团队协同
ProWork 团队协同(以下简称 ProWork )是便捷高效的协同平台,为团队中的不同角色提供支持。团队成员可以通过日历、清单来规划每⽇的工作,同时管理者也可以通过统计报表随时掌握团队状况。ProWork 摒弃了僵化的流程,通过灵活轻量的任务管理体系,满足不同团队的实际情况,目前 ProWork 所有功能均可免费使用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档