MyBatis好好学四(缓存管理和事务管理)

MyBatis好好学四(缓存管理和事务管理)

大纲

  • 1、MyBatis缓存管理

  • 2、MyBatis事务管理

1、MyBatis缓存管理

         MyBatis提供了缓存机制,这可以有效地减少与数据库的交互次数,提高系统性能。

默认情况下,MyBatis会开启一级缓存(SqlSession级别的缓存)和二级缓存(全局共享的缓存)。

1、一级缓存

        SQLSession级别缓存,每次MyBatis开启一次和数据库的会话,就会创建出一个SqlSession对象表示一次数据库会话。每一个SqlSession都会创建一个一级缓存,两个不同的SqlSession执行同一个查询Sql也是查询各自的一级缓存。在同一SqlSession中执行的多次查询可以被缓存,从而提高了性能.

-- 优缺点
一级缓存是基于sqlSession级别的缓存,即再同一个sqlSession中执行多次查询可以被缓存,
从而提高性能。并且默认开启,不能被关闭,但可以通过调用clearCache()来清空缓存。
缺点:不能跨SqlSession,SqlSession关闭后,一级缓存将被清空。一级缓存是MyBatis维护的,因此其内部实现可能会影响缓存的透明性和可定制性。

-- 一级缓存的工作流程
-- 创建缓存的key
根据SQLSession执行查询Sql,Executor会根据Sql语句、查询参数等内容创建一个key值,根据key值查询一级缓存
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

-- 获取缓存
根据key值就会从Cache(PerpetualCache)中获取对应的缓存结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

// 如果命中,直接将缓存结果返回,否则,会查询数据库,并且将查询到的数据存放在缓存中,最后返回结果。


private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  //占位符
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    //查询数据库
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    //清除占位符
    localCache.removeObject(key);
  }
  //将从数据库中的数据存放在缓存中
  localCache.putObject(key, list);
  //执行Sql都是采用PreparedStatement,可以忽略下面的if语句块
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  //返回结果
  return list;
}

2、二级缓存

        MyBatis的二级缓存是指在多个SqlSession中执行相同的查询语句时,会将查询结果缓存在内存中,下次执行相同的查询语句时,直接从缓存中获取结果,而不需要再次访问数据库

        需要注意的是,二级缓存的实现需要满足以下两个条件:

  • 必须在mapper.xml文件中的select标签中添加cache标签,并指定cache的id。
  • 查询语句的返回值类型必须是可序列化的,因为缓存是存储在内存中的,需要将缓存结果序列化到磁盘上。如果返回值类型不可序列化,会在缓存时抛出异常。

二级缓存的优缺点:

  • 减少数据库的访问次数(优点)

        使用二级缓存可以减少数据库的访问次数,从而提高应用程序的响应速度。当应用程序需要重复查询相同的数据时,可以直接从缓存中获取数据,而不用再次访问数据库。

  • 提高应用程序的性能(优点)

        使用二级缓存可以将查询结果缓存到应用程序的内存中,访问内存的速度比访问数据库的速度要快得多。这样可以大大提高应用程序的性能,尤其是在高并发的情况下。

  • 降低数据库的负载(优点)

        使用二级缓存可以降低数据库的负载,减少数据库的压力

  • 提高应用程序的可扩展性(优点)

        使用二级缓存可以提高应用程序的可扩展性。当应用程序需要扩展时,可以在多台服务器之间共享缓存,从而避免了数据不一致的问题,提高了应用程序的可扩展性

  • 配置复杂,且可能存在数据不一致性问题(缺点)

        二级缓存的配置较为复杂,需要用户进行详细的配置和管理。此外,如果二级缓存中的数据与数据库中的数据不一致,可能会出现问题。因此,二级缓存的正确使用和维护是必要的。

-- 开启二级缓存
全局配置:
<setting name="cacheEnabled" value="true"/>
局部配置:mapper.xml
<!--开启二级缓存-->
<cache/> 或
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
使用:
<select id= "getUserById" paramterType="Integer" resultType="Student" useCache="true">
    select * from user where id=#{param}
</select>
注解:
在自定义Mapper接口中,可以通过@CacheNamespace注解来启用单独的二级缓存
@CacheNamespace
public interface UserMapper {
    // ...

}

2、MyBatis事务管理

        事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。事务可大可小,在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。

1、MyBatis事务管理策略

MyBatis的事务管理分为两种形式:

  • (1)使用JDBC的事务管理机制。

        这种机制就是利用java.sql.Connection对象完成对事务的提交

  • (2)使用MANAGED的事务管理机制。

        这种机制mybatis自身不会去实现事务管理,而是让程序的Web容器或者Spring容器来实现对事务的管理。

2、MyBatis中Transaction接口

        MyBatis支持的两种事务类型管理器,Transactions接口中对两种事务管理方式进行行为约束。

public interface Transaction {
  //JDBC中事务手动管理,需要依靠Connection对象,此方法可以取得Connection对象。
  Connection getConnection() throws SQLException;

  //设置在什么情况下执行commit()命令
  void commit() throws SQLException;

  //设置在什么情况下执行rollback()命令
  void rollback() throws SQLException;

  //业务完毕后,处理Connection对象,一般有两种形式,将这个Connection对象销毁或者将Connection返回数据库连接池中。
  void close() throws SQLException;

  //Connection向数据库索要一个Transaction对象时的最大等待时间。
  Integer getTimeout() throws SQLException;
}
3、Transaction的接口实现类

Transaction接口中有两个实现类:JdbcTransaction和ManagedTransaction。

(1)JdbcTransaction
        JdbcTransaction直接使用JDBC的提交和回滚事务管理机制。它依赖与从dataSource中取得的连接connection来管理transaction的作用域,connection对象的获取被延迟到调用getConnection()方法。如果autoCommit设置为on,开启状态的话,它会忽略commit和rollback。

public class JdbcTransaction implements Transaction {
    private static final Log log = LogFactory.getLog(JdbcTransaction.class);
    protected Connection connection;
    protected DataSource dataSource;
    protected TransactionIsolationLevel level;
    protected boolean autoCommit;

    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
        this.dataSource = ds;
        this.level = desiredLevel;
        this.autoCommit = desiredAutoCommit;
    }

    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    public void commit() throws SQLException {
        if (this.connection != null && !this.connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Committing JDBC Connection [" + this.connection + "]");
            }

            this.connection.commit();
        }

    }

    public void rollback() throws SQLException {
        if (this.connection != null && !this.connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Rolling back JDBC Connection [" + this.connection + "]");
            }

            this.connection.rollback();
        }

    }

    public void close() throws SQLException {
        if (this.connection != null) {
            this.resetAutoCommit();
            if (log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + this.connection + "]");
            }

            this.connection.close();
        }

    }

    protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
        try {
            if (this.connection.getAutoCommit() != desiredAutoCommit) {
                if (log.isDebugEnabled()) {
                    log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + this.connection + "]");
                }

                this.connection.setAutoCommit(desiredAutoCommit);
            }

        } catch (SQLException var3) {
            throw new TransactionException("Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: " + desiredAutoCommit + ".  Cause: " + var3, var3);
        }
    }

    protected void resetAutoCommit() {
        try {
            if (!this.connection.getAutoCommit()) {
                if (log.isDebugEnabled()) {
                    log.debug("Resetting autocommit to true on JDBC Connection [" + this.connection + "]");
                }

                this.connection.setAutoCommit(true);
            }
        } catch (SQLException var2) {
            if (log.isDebugEnabled()) {
                log.debug("Error resetting autocommit to true before closing the connection.  Cause: " + var2);
            }
        }

    }

    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }

        this.connection = this.dataSource.getConnection();
        if (this.level != null) {
            this.connection.setTransactionIsolation(this.level.getLevel());
        }

        this.setDesiredAutoCommit(this.autoCommit);
    }

    public Integer getTimeout() throws SQLException {
        return null;
    }
}

(2)ManagedTransaction

        查看这个类,可知其中的commit方法和rollback方法没有具体实现。ManagedTransaction是让容器来管理事务Transaction的整个生命周期,使用ManagedTransaction的commit和rollback功能不会对事务有任何影响,它没有具体实现,它将事务管理权交给容器来实现。

public class ManagedTransaction implements Transaction {
    private static final Log log = LogFactory.getLog(ManagedTransaction.class);
    private DataSource dataSource;
    private TransactionIsolationLevel level;
    private Connection connection;
    private final boolean closeConnection;

    public ManagedTransaction(Connection connection, boolean closeConnection) {
        this.connection = connection;
        this.closeConnection = closeConnection;
    }

    public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
        this.dataSource = ds;
        this.level = level;
        this.closeConnection = closeConnection;
    }

    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    public void commit() throws SQLException {
    }

    public void rollback() throws SQLException {
    }

    public void close() throws SQLException {
        if (this.closeConnection && this.connection != null) {
            if (log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + this.connection + "]");
            }

            this.connection.close();
        }

    }

    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }

        this.connection = this.dataSource.getConnection();
        if (this.level != null) {
            this.connection.setTransactionIsolation(this.level.getLevel());
        }

    }

    public Integer getTimeout() throws SQLException {
        return null;
    }
}
4、MyBatis事务管理流程
MyBatis的事务管理流程如下:
-- 配置事务管理器:
在MyBatis的配置文件中,配置事务管理器的类型和相关属性。
-- 获取事务:
通过事务管理器的getTransaction()方法获取事务对象。
-- 执行数据库操作:
在数据库操作前后,通过事务对象获取数据库连接,并执行相应的数据库操作。
-- 提交事务或回滚事务:
根据操作的成功与否,调用事务对象的commit()或rollback()方法。
-- 关闭事务:
在事务操作完成后,调用事务对象的close()方法关闭数据库连接。
end
  • 作者:旭仔(联系作者)
  • 发表时间:2024-03-10 20:39
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者公众号二维码(公众号二维码见右边,欢迎关注)
  • 评论