【Mybatis】Mybatis数据源与事务源码分析

1.前言

数据源是持久层框架中最核心的组件之一,在实际工作中比较常见的数据源有 C3P0、Apache Common DBCP、Proxool 等。

MyBatis 自己提供了一套数据源实现,而且还能够方便地集成第三方数据源。

2.Mybatis数据源工厂

工厂方法类图
在这里插入图片描述

MyBatis 的数据源模块也是用到了工厂方法模式,如果需要扩展新的数据源实现时,只需要添加对应的 Factory 实现类,新的数据源就可以被 MyBatis 使用。

org.apache.ibatis.datasource.DataSourceFactory

MyBatis 数据源实现中的 Factory 接口角色,UnpooledDataSourceFactoryPooledDataSourceFactory 实现了 DataSourceFactory 接口,也就是 Factory 接口实现类的角色

在这里插入图片描述

继续:核心的方法是getDataSource() 方法,该方法用来生成一个 DataSource 对象。

在 UnpooledDataSourceFactory 这个实现类的初始化过程中,会直接创建 UnpooledDataSource 对象,其中的 dataSource 字段会指向该 UnpooledDataSource 对象。接下来调用的 setProperties() 方法会根据传入的配置信息,完成对该 UnpooledDataSource 对象相关属性的设置。在通过数据源拿到数据库连接之后,还需要开启事务,才能进行数据的修改。

后续会讲解 Transaction 接口,它可以管理事务的开启、提交和回滚。

package org.apache.ibatis.datasource.unpooled;

import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

/**
 * @author Clinton Begin
 */
public class UnpooledDataSourceFactory implements DataSourceFactory {

  private static final String DRIVER_PROPERTY_PREFIX = "driver.";
  private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();

  protected DataSource dataSource;

//生成一个 DataSource 对象
  public UnpooledDataSourceFactory() {
    this.dataSource = new UnpooledDataSource();
  }

//根据传入的配置信息,完成对该 UnpooledDataSource 对象相关属性的设置。
  @Override
  public void setProperties(Properties properties) {
    Properties driverProperties = new Properties();
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    for (Object key : properties.keySet()) {
      String propertyName = (String) key;
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
        String value = properties.getProperty(propertyName);
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
      } else if (metaDataSource.hasSetter(propertyName)) {
        String value = (String) properties.get(propertyName);
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    if (driverProperties.size() > 0) {
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

//返回了上面创建的 UnpooledDataSource 对象
  @Override
  public DataSource getDataSource() {
    return dataSource;
  }

  private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
    Object convertedValue = value;
    Class<?> targetType = metaDataSource.getSetterType(propertyName);
    if (targetType == Integer.class || targetType == int.class) {
      convertedValue = Integer.valueOf(value);
    } else if (targetType == Long.class || targetType == long.class) {
      convertedValue = Long.valueOf(value);
    } else if (targetType == Boolean.class || targetType == boolean.class) {
      convertedValue = Boolean.valueOf(value);
    }
    return convertedValue;
  }

}

3.DataSource

JDK 提供的 javax.sql.DataSource 接口在 MyBatis 数据源中扮演了 Product 接口的角色。 MyBatis 提供的数据源实现有两个,一个 UnpooledDataSource 实现,另一个 PooledDataSource 实现,它们都是 Product 具体实现类的角色。

3.1 UnpooledDataSource

driverClassLoader(ClassLoader 类型):加载 Driver 驱动类的类加载器。

driverProperties(Properties 类型):数据库连接驱动的相关配置。

registeredDrivers(Map<String, Driver> 类型):缓存所有已注册的数据库连接驱动。

defaultTransactionIsolationLevel(Integer 类型):事务隔离级别。

目前基本上所有数据源实现的底层都是依赖 JDBC 操作数据库的,而使用 JDBC 的第一步就是向 DriverManager 注册 JDBC 驱动类,之后才能创建数据库连接。

DriverManager 中定义了 registeredDrivers 字段用于记录注册的 JDBC 驱动,这是一个 ConcurrentHashMap 类型的集合,是线程安全的。

MyBatis 的 UnpooledDataSource 实现中定义了如下静态代码块,从而在 UnpooledDataSource 加载时,将已在 DriverManager 中注册的 JDBC 驱动器实例复制一份到 UnpooledDataSource.registeredDrivers 集合中。

  static {
    //从DriverManager中读取JDBC驱动
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      //将DriverManager中的全部JDBC驱动记录到registeredDrivers集合
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
  }

在 getConnection() 方法中,UnpooledDataSource 会调用 doGetConnection() 方法获取数据库连接

  private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (driverProperties != null) {
      props.putAll(driverProperties);
    }
    if (username != null) {
      props.setProperty("user", username);
    }
    if (password != null) {
      props.setProperty("password", password);
    }
    return doGetConnection(props);
  }
  
  private Connection doGetConnection(Properties properties) throws SQLException {
     // 初始化数据库驱动
    //在调用的 initializeDriver() 方法中,完成了 JDBC 驱动的初始化,其中会创建配置中指定的 Driver 对象,并将其注册到 DriverManager 以及上面介绍的 UnpooledDataSource.registeredDrivers 集合中保存;
    initializeDriver();
    // 创建数据库连接
    Connection connection = DriverManager.getConnection(url, properties);
    // 配置数据库连接
    //会对数据库连接进行一系列配置,例如,数据库连接超时时长、事务是否自动提交以及使用的事务隔离级别。
    configureConnection(connection);
    return connection;
  }

3.2 PooledDataSource

1.池化技术

JDBC 连接的创建是非常耗时的,从数据库这一侧看,能够建立的连接数也是有限的,所以在绝大多数场景中,我们都需要使用数据库连接池来缓存、复用数据库连接。

使用池化技术缓存数据库连接会带来很多好处,例如:

  • 在空闲时段缓存一定数量的数据库连接备用,防止被突发流量冲垮;

  • 实现数据库连接的重用,从而提高系统的响应速度;

  • 控制数据库连接上限,防止连接过多造成数据库假死;

  • 统一管理数据库连接,避免连接泄漏。

数据库连接池在初始化时,一般会同时初始化特定数量的数据库连接,并缓存在连接池中备用。当我们需要操作数据库时,会从池中获取连接;当使用完一个连接的时候,会将其释放。在使用连接池的场景中,并不会直接将连接关闭,而是将连接返回到池中缓存,等待下次使用。

2.分析

PooledDataSource 中并没有直接维护数据库连接的集合,而是维护了一个 PooledState 类型的字段(state 字段),而这个PooledState 才是管理连接的地方。在 PooledState 中维护的数据库连接并不是真正的数据库连接(不是 java.sql.Connection 对象),而是 PooledConnection 对象。

  private final PoolState state = new PoolState(this);

PooledConnection 是 MyBatis 中定义的一个 InvocationHandler 接口实现类,其中封装了真正的 java.sql.Connection 对象以及相关的代理对象,这里的代理对象就是通过JDK 动态代理产生的。

 /**
   * Unwraps a pooled connection to get to the 'real' connection
   * 打开池连接以获取“真实”连接
   * @param conn
   *          - the pooled connection to unwrap
   * @return The 'real' connection
   */
  public static Connection unwrapConnection(Connection conn) {
    if (Proxy.isProxyClass(conn.getClass())) {
      InvocationHandler handler = Proxy.getInvocationHandler(conn);
      if (handler instanceof PooledConnection) {
        return ((PooledConnection) handler).getRealConnection();
      }
    }
    return conn;
  }

3.PooledConnection 中的核心字段:

 
dataSource(UnpooledDataSource 类型):记录当前 PooledConnection 对象归属的 UnpooledDataSource 对象。也就是说,当前的 PooledConnection 是由该 UnpooledDataSource 对象创建的;

realConnection(Connection 类型):当前 PooledConnection 底层的真正数据库连接对象。

proxyConnection(Connection 类型):指向了 realConnection 数据库连接的代理对象。

checkoutTimestamp(long 类型):使用方从连接池中获取连接的时间戳。

createdTimestamp(long 类型):连接创建的时间戳。

lastUsedTimestamp(long 类型):连接最后一次被使用的时间戳。

connectionTypeCode(int 类型):数据库连接的标识。该标识是由数据库 URL、username 和 password 三部分组合计算出来的 hash 值,主要用于连接对象确认归属的连接池。

valid(boolean 类型):用于标识 PooledConnection 对象是否有效。该字段的主要目的是防止使用方将连接归还给连接池之后,依然保留该 PooledConnection 对象的引用并继续通过该 PooledConnection 对象操作数据库。

4.PooledConnection :

PooledConnection构造方法会初始化上述字段,这里尤其关注 proxyConnection 这个 Connection 代理对象的初始化,使用的是 JDK 动态代理的方式实现的,其中传入的 InvocationHandler 实现正是 PooledConnection 自身。

PooledConnection.invoke() 方法中只对 close() 方法进行了拦截

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (CLOSE.equals(methodName)) {
        // 如果调用close()方法,并没有直接关闭底层连接,而是将其归还给关联的连接池
        dataSource.pushConnection(this);
        return null;
    }
    if (!Object.class.equals(method.getDeclaringClass())) {
        // 只要不是Object的方法,都需要检测当前PooledConnection是否可用
        checkConnection();
    }
    // 调用realConnection的对应方法
    return method.invoke(realConnection, args);
}

5.PoolState

PoolState这个类,它负责管理连接池中所有 PooledConnection 对象的状态,维护了两个 ArrayList 集合按照 PooledConnection 对象的状态分类存储,其中 idleConnections 集合用来存储空闲状态的 PooledConnection 对象,activeConnections 集合用来存储活跃状态的 PooledConnection 对象。

public class PoolState {

  protected PooledDataSource dataSource;

  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
....

定义的统计字段:

requestCount:请求数据库连接的次数。

accumulatedRequestTime:获取连接的累积耗时。

accumulatedCheckoutTime:所有连接的 checkoutTime 累加。PooledConnection 中有一个 checkoutTime 属性,
                         表示的是使用方从连接池中取出连接到归还连接的总时长,也就是连接被使用的时长。

claimedOverdueConnectionCount:当连接长时间未归还给连接池时,会被认为该连接超时,该字段记录了超时的连接个数。

accumulatedCheckoutTimeOfOverdueConnections:记录了累积超时时间。

accumulatedWaitTime:当连接池全部连接已经被占用之后,新的请求会阻塞等待,该字段就记录了累积的阻塞等待总时间。

hadToWaitCount:记录了阻塞等待总次数。

badConnectionCount:无效的连接数。

3.2.1 PooledDataSource 实现分析

首先是 getConnection() 方法,其中先是依赖 popConnection() 方法获取 PooledConnection 对象,然后从 PooledConnection 中获取数据库连接的代理对象

 public Connection getConnection(String username, String password) throws SQLException {
    return popConnection(username, password).getProxyConnection();
  }

这里调用的 popConnection() 方法是从连接池中获取数据库连接的核心,具体步骤:

  1. 检测当前连接池中是否有空闲的有效连接,如果有,则直接返回连接;如果没有,则继续执行下一步。
  2. 检查连接池当前的活跃连接数是否已经达到上限值,如果未达到,则尝试创建一个新的数据库连接,并在创建成功之后,返回新建的连接;如果已达到最大上限,则往下执行。
  3. 检查活跃连接中是否有连接超时,如果有,则将超时的连接从活跃连接集合中移除,并重复步骤 2;如果没有,则执行下一步。
  4. 当前请求数据库连接的线程阻塞等待,并定期执行前面三步检测相应的分支是否可能获取连接。

1.popConnection获取连接源码:

org.apache.ibatis.datasource.pooled.PooledDataSource#popConnection
  
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) { // 加锁同步
            // 步骤1:检测空闲连接集合
            if (!state.idleConnections.isEmpty()) { 
                // 获取空闲连接
                conn = state.idleConnections.remove(0);
            } else { // 没有空闲连接
                // 步骤2:活跃连接数没有到上限值,则创建新连接
                if (state.activeConnections.size() < poolMaximumActiveConnections) {
                    // 创建新数据库连接,并封装成PooledConnection对象
                    conn = new PooledConnection(dataSource.getConnection(), this);
                } else {// 活跃连接数已到上限值,则无法创建新连接
                    // 步骤3:检测超时连接
                    // 获取最早的活跃连接
                    PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                    // 检测该连接是否超时
                    if (longestCheckoutTime > poolMaximumCheckoutTime) {
                        // 对超时连接的信息进行统计
                        state.claimedOverdueConnectionCount++;
                        state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                        state.accumulatedCheckoutTime += longestCheckoutTime;
                        // 将超时连接移出activeConnections集合
                        state.activeConnections.remove(oldestActiveConnection);
                        // 如果超时连接上有未提交的事务,则自动回滚
                        if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                            try {
                                oldestActiveConnection.getRealConnection().rollback();
                            } catch (SQLException e) {
                            }
                        }
                        // 创建新PooledConnection对象,但是真正的数据库连接
                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                        conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
                        // 将超时PooledConnection设置为无效
                        oldestActiveConnection.invalidate();
                    } else {
                        // 步骤4:无空闲连接、无法创建新连接且无超时连接,则只能阻塞等待
                        if (!countedWait) { // 统计阻塞等待次数
                            state.hadToWaitCount++;
                            countedWait = true;
                        }
                        long wt = System.currentTimeMillis();
                        state.wait(poolTimeToWait);// 阻塞等待
                        // 统计累积的等待时间
                        state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                    }
                }
            }
            if (conn != null) { // 对连接进行统计
                if (conn.isValid()) { // 检测PooledConnection是否有效
                    // 配置PooledConnection的相关属性,设置connectionTypeCode、checkoutTimestamp、lastUsedTimestamp字段的值
                    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 { 
                    ... ...// 统计失败的情况
                }
            }
        }
    }
    return conn;
}

2.pushConnection() 释放连接

当调用 proxyConnection 对象的 close() 方法时,连接并没有真正关闭,而是通过 PooledDataSource.pushConnection() 方法将 PooledConnection 归还给了关联的 PooledDataSource。

  1. 从活跃连接集合(即前面提到的 activeConnections 集合)中删除传入的 PooledConnection 对象。
  2. 检测该 PooledConnection 对象是否可用。如果连接已不可用,则递增 badConnectionCount 字段进行统计,之后,直接丢弃 PooledConnection 对象即可。如果连接依旧可用,则执行下一步。
  3. 检测当前 PooledDataSource 连接池中的空闲连接是否已经达到上限值。如果达到上限值,则 PooledConnection 无法放回到池中,正常关闭其底层的数据库连接即可。如果未达到上限值,则继续执行下一步。
  4. 将底层连接重新封装成 PooledConnection 对象,并添加到空闲连接集合( idleConnections 集合),然后唤醒所有阻塞等待空闲连接的线程。

pushConnection() 方法的核心实现

org.apache.ibatis.datasource.pooled.PooledDataSource#pushConnection
  
protected void pushConnection(PooledConnection conn) throws SQLException {
    synchronized (state) {
        state.activeConnections.remove(conn); // 步骤1:从活跃连接集合中删除该连接
        if (conn.isValid()) {// 步骤2:检测该 PooledConnection 对象是否可用
            // 步骤3:检测当前PooledDataSource连接池中的空闲连接是否已经达到上限值
            if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
                // 累计增加 accumulatedCheckoutTime(被使用的时长)
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                if (!conn.getRealConnection().getAutoCommit()) {
                    // 回滚未提交的事务
                    conn.getRealConnection().rollback();
                }
                // 步骤4:将底层连接重新封装成PooledConnection对象,
                // 并添加到空闲连接集合( idleConnections 集合)存储空闲状态的 PooledConnection 对象
                PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
                state.idleConnections.add(newConn);
                // 设置新PooledConnection对象的创建时间戳和最后使用时间戳
                newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
                conn.invalidate(); // 丢弃旧PooledConnection对象
                // 唤醒所有阻塞等待空闲连接的线程
                state.notifyAll();
            } else {
                // 当前PooledDataSource连接池中的空闲连接已经达到上限值
                // 当前数据库连接无法放回到池中

                // 累计增加 accumulatedCheckoutTime(被使用的时长)
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                if (!conn.getRealConnection().getAutoCommit()) {
                    // 回滚未提交的事务
                    conn.getRealConnection().rollback();
                }
                // 关闭真正的数据库连接
                conn.getRealConnection().close();
                // 将PooledConnection对象设置为无效
                conn.invalidate(); 
            }
        } else {
            // 统计无效PooledConnection对象个数
            state.badConnectionCount++; 
        }
    }
}

conn.isValid()

valid 字段值为 true;
realConnection 字段值不为空;
执行 PooledDataSource.pingConnection() 方法,返回值为 true。
只有这三个条件都成立,才认为这个 PooledConnection 对象可用.
public boolean isValid() {
    return valid && realConnection != null && dataSource.pingConnection(this);
}

dataSource.pingConnection(this);

PooledDataSource.pingConnection() 方法会尝试请求数据库,并执行一条测试 SQL 语句,检测是否真的能够访问到数据库

protected boolean pingConnection(PooledConnection conn) {
    boolean result = true; // 记录此次ping操作是否成功完成
    try {
        // 检测底层数据库连接是否已经关闭
        result = !conn.getRealConnection().isClosed();
    } catch (SQLException e) {
        result = false;
    }
    // 如果底层与数据库的网络连接没断开,则需要检测poolPingEnabled 字段的配置,决定
    // 是否能执行ping操作。另外,ping操作不能频繁执行,只有超过一定时长
    // (超过 poolPingConnectionsNotUsedFor 指定的时长)未使用的连接,才需要ping操作来检测数据库连接是否正常
    if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
            && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
        try {
            // 执行poolPingQuery字段中记录的测试SQL语句
            Connection realConn = conn.getRealConnection();
            try (Statement statement = realConn.createStatement()) {
                statement.executeQuery(poolPingQuery).close();
            }
            if (!realConn.getAutoCommit()) {
                realConn.rollback();
            }
            result = true; // 不抛异常,即为成功
        } catch (Exception e) {
            conn.getRealConnection().close();
            result = false;  // 抛异常,即为失败
        }
    }
    return result;
}

4.事务接口

当我们从数据源中得到一个可用的数据库连接之后,就可以开启一个数据库事务了,事务成功开启之后,我们才能修改数据库中的数据。在修改完成之后,我们需要提交事务,完成整个事务内的全部修改操作,如果修改过程中出现异常,我们也可以回滚事务,放弃整个事务中的全部修改操作。

MyBatis 专门抽象出来一个 Transaction 接口

org.apache.ibatis.transaction.Transaction

Transaction 接口是 MyBatis 中对数据库事务的抽象,其中定义了提交事务、回滚事务,以及获取事务底层数据库连接的方法。

JdbcTransaction、ManagedTransaction 是 MyBatis 自带的两个 Transaction 接口实现,这里也使用到了工厂方法模式

TransactionFactory 是用于创建 Transaction 的工厂接口,其中最核心的方法是 newTransaction() 方法,它会根据数据库连接或数据源创建 Transaction 对象。

JdbcTransactionFactory 和 ManagedTransactionFactory 是 TransactionFactory 的两个实现类,分别用来创建 JdbcTransaction 对象和 ManagedTransaction 对象。

4.1 JdbcTransaction 的实现

其中维护了事务关联的数据库连接以及数据源对象,同时还记录了事务自身的属性,例如,事务隔离级别和是否自动提交。

在构造函数中,JdbcTransaction 并没有立即初始化数据库连接(也就是 connection 字段),connection 字段会被延迟初始化,具体的初始化时机是在调用 getConnection() 方法时,通过dataSource.getConnection() 方法完成初始化。

org.apache.ibatis.transaction.jdbc.JdbcTransaction#openConnection

在这里插入图片描述

在日常使用数据库事务的时候,我们最常用的操作就是提交和回滚事务,Transaction 接口将这两个操作抽象为 commit() 方法和 rollback() 方法。在 commit() 方法和 rollback() 方法中,JdbcTransaction 都是通过 java.sql.Connection 的同名方法实现事务的提交和回滚的。

在这里插入图片描述

5.扩展

5.1 工厂方法模式

工厂方法模式中定义了 Factory 这个工厂接口,如下图所示,其中定义了 createProduct() 方法创建右侧继承树中的对象,不同的工厂接口实现类会创建右侧继承树中不同 Product 实现类(例如 ProductImpl 1 和 ProductImpl 2)。

在这里插入图片描述

我们可以看到工厂方法模式由四个核心角色构成。

  • Factory 接口:工厂方法模式的核心接口之一。使用方会依赖 Factory 接口创建 Product 对象实例。

  • Factory 实现类(图中的 FactoryImpl 1 和 FactoryImpl 2):用于创建 Product 对象。不同的 Factory 实现会根据需求创建不同的 Product 实现类。

  • Product 接口:用于定义业务类的核心功能。Factory 接口创建出来的所有对象都需要实现 Product 接口。使用方依赖 Product 接口编写其他业务实现,所以使用方关心的是 Product 接口这个抽象,而不是其中的具体实现逻辑。

  • Product 实现类(图中的 ProductImpl 1 和 ProductImpl 2):实现了 Product 接口中定义的方法,完成了具体的业务逻辑。

举个例子:

这里假设一个场景:目前我们要做一个注册中心模块,已经有了 ZookeeperImpl 和 EtcdImpl 两个业务实现类,分别支持了与 ZooKeeper 交互和与 etcd 交互,此时来了个新需求,需要支持与 Consul 交互。该怎么解决这个需求呢?那就是使用工厂方法模式,我们只需要添加新的 ConsulFactory 实现类和 ConsulImpl 实现类即可完成扩展。

工厂方法的好处:

工厂方法模式最终也是符合“开放-封闭”原则的,可以通过添加新的 Factory 接口实现和 Product 接口实现来扩展整个体系的功能。另外,工厂方法模式对使用方暴露的是 Factory 和 Product 这两个抽象的接口,而不是具体的实现,也就帮助使用方面向接口编程。

5.2 CachingExecutor与TransactionalCacheManager和TransactionalCache类之间的关系及其各自的作用是什么?

  1. CachingExecutor 是 MyBatis 中的一个执行器实现,它主要负责提供缓存机制。CachingExecutor 的核心功能是提高系统性能,通过缓存 SQL 语句的执行结果来减少数据库操作的次数。如果在缓存中找到了相同的 SQL 语句及其参数,则直接从缓存中获取结果,无需重新执行数据库操作。
  2. TransactionalCacheManager(简称 tcm)是 CachingExecutor 的一个成员变量,专门用于管理二级缓存数据。它是一个事务性缓存管理器,确保了缓存中的数据与数据库中的数据保持一致。TransactionalCacheManager 的工作原理是在事务提交后才将缓存数据真正写入到数据库,从而防止了脏读现象。
  3. TransactionalCache 是一个实现了 Cache 接口的类,它被 TransactionalCacheManager 用作缓存装饰器。在 MyBatis 中,TransactionalCache 实现了事务性缓存的功能,即只有在事务提交之后,对缓存的修改才会生效。如果事务回滚或不提交,则不会对缓存产生影响。

CachingExecutor 提供了缓存机制以提高性能,而 TransactionalCacheManager 作为其内部组件,负责管理这些缓存数据,并确保它们与数据库保持同步。TransactionalCache 作为装饰器,进一步增强了缓存的事务性特征,使得缓存操作仅在事务成功提交时才生效。

org.apache.ibatis.executor.CachingExecutor

// 真正用来操作数据库操作的 Executor对象
private final Executor delegate;

5.3 SqlSessionHolder扩展

SqlSessionTemplate正是利用了SqlSessionHolder,保证了数据库操作过程中的线程安全和事务完整性。

SqlSessionTemplate应用了代理的方式,所以每次操作数据库时,都会进入切面通过调用getSqlSession方法获取真正操作数据库的sqlSession,此时就依赖到了SqlSessionHolder

org.mybatis.spring.SqlSessionUtils
  
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    // 根据sqlSessionFactory,从TransactionSynchronizationManager定义的资源map中获取当前线程对应的SqlSessionHolder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
	  // 从SqlSessionHolder中提取符合要求的SqlSession对象
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    // 如果无法通过SqlSessionHolder获取到sqlSession,则说明当前线程没有未完成的事务
    // 那就利用sessionFactory新创建一个sqlSession
    session = sessionFactory.openSession(executorType);

    // 新创建一个SqlSessionHolder,并且将刚创建的sqlSession与其绑定
    // 然后将SqlSessionHolder对象注册到TransactionSynchronizationManager中的resources
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

 
//在当前存在事务的前提下,如果sqlSessionHolder中不存在符合要求的SqlSession,则新建一个SqlSession并将其赋给sqlSessionHolder,最终利用 TransactionSynchronizationManager将sqlSessionHolder与sessionFactory绑定。
  private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    //判断当前是否存在事务
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
       // 利用新建的sqlSession创建一个SqlSessionHolder
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        // 将sessionFactory和SqlSessionHolder作为key和value
        // 绑定到TransactionSynchronizationManager中的resources(map)属性中
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager
            .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          LOGGER.debug(() -> "SqlSession [" + session
              + "] was not registered for synchronization because DataSource is not transactional");
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      LOGGER.debug(() -> "SqlSession [" + session
          + "] was not registered for synchronization because synchronization is not active");
    }

  }

 public static void bindResource(Object key, Object value) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Assert.notNull(value, "Value must not be null");
		Map<Object, Object> map = resources.get();
	// 获取当前线程对应的map,如果map为空,则新建一个map对象
		if (map == null) {
			map = new HashMap<>();
			resources.set(map);
		}
   	// 将sessionFactory和SqlSessionHolder作为key和value进行绑定,存入map
		// 这样在下一次想要获取sqlSession的时候
		// 就可以通过TransactionSynchronizationManager.getResource方法获取到当前事务正在使用的sqlSession了
		Object oldValue = map.put(actualKey, value);
		// Transparently suppress a ResourceHolder that was marked as void...
		if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
			oldValue = null;
		}
		if (oldValue != null) {
			throw new IllegalStateException(
					"Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
		}
	}


public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
	
	  // 根据sqlSessionFactory,从TransactionSynchronizationManager定义的资源map中
    // 获取当前线程对应的SqlSessionHolder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    // 如果存在holder,说明当前仍存在事务操作,那么执行released方法
    // released方法会将referenceCount--(引用计数减一)
    if ((holder != null) && (holder.getSqlSession() == session)) {
      LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
      holder.released();
    } else {  // 如果不存在holder,说明当前的数据操作不存在事务,那么直接将sqlSession回收至连接池即可
      LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
      session.close();
    }
  }
  
public void released() {
		// 每当一个sqlSession执行完毕,holder持有引用的次数减一
		this.referenceCount--;
	}

5.4 SqlSessionTemplate 类扩展

org.mybatis.spring.SqlSessionTemplate

SqlSessionTemplate是Spring框架中提供的一个模板类,用于简化数据库操作。它基于MyBatis的SqlSession,提供了一些常用的方法,使得开发者可以更加方便地执行CRUD(创建、读取、更新、删除)操作。

5.5 SqlSessionInterceptor内部私有类

org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //通过SqlSessionUtils.getSqlSession()获得真实处理CRUD的持久层,默认为DefaultSqlSession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
          //执行CRUD操作
        Object result = method.invoke(sqlSession, args);
        //非事务处理的数据操作需要强制commit 本次是有事务处理的
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
         ......
        throw unwrapped;
      } finally {
        //关闭sqlSession
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
介绍 本资是一个基于SSM(Spring + SpringMVC + MyBatis)框架开发的远程家庭健康监测管理系统。该系统旨在帮助用户实现家庭健康数据的远程监测与管理,通过整合现代信息技术与医疗健康领域,为家庭成员提供便捷、高效的健康管理服务。 本系统涵盖了用户管理、健康数据采集、健康数据分析、健康报告生成等多个功能模块。用户可以通过系统录入家庭成员的基本信息,并实时上传健康数据,如血压、血糖、心率等。系统会根据上传的数据进行智能分析,生成个性化的健康报告,为用户提供科学的健康建议。 SSM框架的采用使得本系统具有高度的可维护性和可扩展性。Spring框架提供了强大的依赖注入和事务管理功能,使得系统的各个组件能够松耦合地协同工作;SpringMVC框架则负责处理前端的请求和响应,实现了视图与控制器的分离;MyBatis框架则简化了数据库的操作,提高了系统的开发效率。 本资不仅包含了完整的毕设代码,还附带了详细的说明文档。代码结构清晰,注释详尽,方便开发者快速理解并上手。说明文档则对系统的功能、设计思路、技术实现等方面进行了详细的介绍,为开发者提供了全面的参考。 此外,本系统具有良好的二次开发定制性。开发者可以根据实际需求对系统进行功能扩展或优化,以满足不同用户群体的健康监测管理需求。无论是作为毕业设计项目还是作为实际应用的健康管理系统,本资都提供了一个优秀的起点和坚实的基础。
介绍 “基于SSM框架开发的数据学院教务管理系统”是一个专为数据学院量身定制的教务管理解决方案。该系统采用SSM(Spring + SpringMVC + MyBatis)框架进行开发,结合了前端展示、后端逻辑处理以及数据库存储等多个层面,实现了教务管理的高效化、信息化和自动化。 该教务管理系统功能丰富,涵盖了课程管理、成绩管理、教师管理、学生管理、考试管理等多个核心模块。通过该系统,学院可以方便地录入课程信息、学生成绩,管理教师资,组织考试等。同时,系统还提供了丰富的数据分析和报表功能,帮助学院更好地掌握教学情况,为决策提供有力支持。 在技术上,SSM框架的选用保证了系统的稳定性和扩展性。Spring作为轻量级的Java开发框架,为系统提供了强大的业务逻辑处理能力;SpringMVC则负责处理前端请求和响应,实现了前后端的分离和松耦合;MyBatis作为优秀的持久层框架,简化了数据库操作,提高了开发效率。 此外,该项目不仅提供了完整的代码,还附带了详细的说明文档。这些文档对项目的整体架构、模块功能、数据库设计等进行了深入的解析,为二次开发和定制提供了有力的支持。无论是想要进一步扩展系统功能,还是根据实际需求进行定制开发,都可以轻松上手。 总之,“基于SSM框架开发的数据学院教务管理系统”是一个功能强大、技术先进的教务管理解决方案,适用于各类数据学院的教务管理工作。通过该系统,学院可以更加高效地管理教务事务,提升教学质量和管理水平。
介绍 “Java毕业设计-基于ssm框架开发的党务政务服务热线平台设计与实现”这一项目,是一款集高效性、实用性、稳定性于一体的综合性服务平台。该平台以ssm(Spring、SpringMVC、MyBatis)框架为基础,充分利用了Java语言的强大功能,旨在为党务政务工作提供一套完善的热线服务解决方案。 平台的设计紧密结合了党务政务工作的实际需求,不仅提供了基本的热线接入、咨询解答、事务处理等功能,还融入了智能化的信息管理和数据分析模块,使得平台能够实现对各类服务请求的实时跟踪、监控和统计分析,大大提高了服务效率和质量。 在实现方面,项目注重代码的可读性和可维护性,采用了模块化、分层化的设计思想,使得整个系统结构清晰、易于扩展。同时,平台还提供了丰富的接口和配置选项,方便用户根据实际需求进行二次开发和定制。 此外,项目还附带了详细的说明文档和完整的代码,为开发者提供了深入学习和研究的宝贵资。无论是对于Java初学者还是具有一定经验的开发者来说,这一项目都是一个不可多得的实践和学习机会。 综上所述,“Java毕业设计-基于ssm框架开发的党务政务服务热线平台设计与实现”项目不仅具有较高的实用价值,同时也具有一定的研究意义。通过学习和使用这一项目,开发者不仅能够提升自己的技能水平,还能够为党务政务工作贡献自己的力量。 请注意,在获取和使用该资时,请确保遵守相关法律法规,尊重原作者的劳动成果。同时,由于网络环境复杂多变,请务必谨慎甄别资的可靠性,以免遭受不必要的损失。须谨慎甄别。
介绍 本资包提供的是一个基于SSM(Spring+SpringMVC+MyBatis)框架开发的JavaWeb家居商城系统的设计与实现,并附带了完整的毕业论文和代码。该项目是一个典型的JavaWeb企业级应用开发案例,不仅展示了SSM框架在实际项目中的应用,还充分体现了MVC设计模式的优势。 家居商城系统涵盖了用户注册登录、商品浏览、购物车管理、订单生成与查询、支付结算等功能模块,满足了用户在线购物的基本需求。系统前端采用JSP技术构建动态页面,界面美观、交互友好;后端则依托SSM框架实现业务逻辑的处理与数据的持久化。 SSM框架的采用使得系统的开发过程更加规范、高效。Spring框架负责整个应用的依赖注入和事务管理,降低了模块间的耦合度;SpringMVC框架则负责请求的转发和响应的渲染,实现了视图与控制器的分离;MyBatis框架则简化了数据库操作,提高了开发效率。 该项目的代码结构清晰、逻辑严谨,注释详尽,对于初学者来说具有很好的学习参考价值。毕业论文则从需求分析、系统设计、实现过程到测试部署等方面进行了全面的阐述,对于了解整个开发流程、掌握SSM框架的应用技巧以及撰写相关论文都具有很好的指导意义。 此外,由于该项目具有良好的可扩展性和可维护性,因此可以基于现有代码进行二次开发定制,以满足不同的业务需求。无论是想要提升个人技能、完成课程作业,还是进行企业级应用的开发,本资包都是一个不可多得的好选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞四海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值