[Spring MVC6]事务管理与缓存机制

news2024/12/27 22:03:18

Spring MVC 关于Spring与MaBatis事务管理,这里的事务管理类似于数据库中的transaction,基本操作也都一样。同时介绍了MaBatis缓存模式,特别是一级缓存与二级缓存。

希望对你有所帮助!

目录

  • Spring 事务管理
  • MyBatis 事务管理
  • MyBatis 缓存模式
    • 一级缓存
    • 二级查询缓存
  • MyBatis 缓存原理

Spring 事务管理

事务管理是企业级不可少的技术,用来确保数据的完整性和一致性。事务有四大特性(ACID):原子性、一致性、隔离性、持久性。Spring在不同的事物管理API上定义一个抽象层。
Spring 既支持编程式事务管理 ( 将事务管理代码嵌入到业务方法来控制事务的提交和回滚)也支持声明式事务管理(AOP,从业务中分离出来)。大多情况下声明式比编程式更好用。
数据库访问技术有很多,例如JDBC、JPA、Hibernate、分布式事务等。Spring 不直接管理事务,而是提供了许多内置事务管理器实现,常用的有:DataSourceTransationManager ,JpaTransationManager,HibernateTransationManager。

Spring 配置关于事务配置总是三个组成:DataSource,TransationManager和代理机制。基于注解方式配置Spring声明式事务。

// 在类和方法注解表明该类或方法需要事务支持
@Transational
public AyUser update() {
	// 执行数据库操作
}

在applicationContext.xml添加事务相关的配置:
Spring提供了@EnableTransactionManagement注解在配置类上开启声明式事务的支持,会自动扫描注解@Transactional的方法和类。

<!-- 声明式事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"
                          proxy-target-class="true"/>
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

在这里插入图片描述

并发事务所导致的三个问题:
1.脏读:发生在一个事务读取了另一个事务但未提交数据,如果改写在稍后回滚了,那么第一个获取的数据无效。
2.不可重复读:一个事务执行相同的查询两次及以上都会得到不同的数据 (因为在读期间更新了数据)
3.幻读
所以以上需要进行隔离。

MyBatis 事务管理

使用Transaction接口对数据库事务进行了抽象,其定义如下:

public interface Transaction {
	// 获取数据库连接对象
	Connection getConnection() throws SQLException;
	// 提交事务
	void commit() throws SQLException;
	// 回滚事务
	void rollback() throws SQLException;
	// 关闭数据库连接
	void close() throws SQLException;
	// 获取事务超时时间
	Integer getTimeout() throws SQLException;
}

Transcation 接口有两个实现类,即JdbcTransaction和ManagedTransaction。
JdbcTransaction 依赖 JDBC onnection 控制事务的提交和回滚。

MyBatis 缓存模式

缓存在互联网非常重要,作用就是将数据保存到内存中,当用户查询数据时,优先从缓存容器中获取数据,而不是频繁从数据库查数据,从而提高查询性能。目前流行的缓存服务器有MongoDB,Redis,Ehcache等,不同缓存服务器有不同的应用场景。
MyBatis提供了一级缓存和二级缓存机制。一级缓存是SqlSession,在操作数据时,每个SqlSessioni类实体对象有一个HashMap数据结构来缓存数据。二级缓存是Mapper 级别的缓存,即多个SqlSession实例对象操作同一个Mapper配置文件SQL语句。MyBatis 默认只开启一级缓存。

一级缓存

构造SqlSession对象,不同的SqlSession互不影响。在参数和SQL完全一样的情况下,使用一个SqlSession对象调用同一个Mapper方法,往往只执行一次SQL,因为MyBatis会将数据放在缓存中,下次查询的时候,SqlSession都会取出当前缓存的数据,而不是发送SQL到数据库中。如果执行了DML操作(insert、update、delete)并提交到数据库,MyBatis会清空一级缓存,避免出现脏读。
在test包AyUserDaoTest.java

 	@Resource
    private SqlSessionFactoryBean sqlSessionFactoryBean;
    @Test
    public void testSessionCache() throws Exception {
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        AyUserDao ayUserDao1 = sqlSession.getMapper(AyUserDao.class);
        //第一次查询
        AyUser ayUser = ayUserDao1.findById("1");
        System.out.println(ayUser.getName());
        // 第二次查询
        AyUser ayUser1 = ayUserDao1.findById("1");
        System.out.println(ayUser1.getName());
        sqlSession.close();
    }

上述代码中,通过@Resource注解注入SqlSessionFactoryBean对象,在applicationContext.xml已经配置:

<!--2.数据源 druid -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

<!--3、配置SqlSessionFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"/>
        <!--扫描sql配置文件:mapper需要的xml文件-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
        
    </bean>

AyUserDao.java:

AyUser findById(String id);

AyUserMapper.xml:

<select id="findById" parameterType="String" resultType="com.ay.model.AyUser">
        select * from ay_user where id = #{id}
    </select>

执行测试用例testSessionCache():
在这里插入图片描述
由图中的控制台打印的信息可以看出,第一次和第二次查询,查询日志只输出一遍,这说明第二次查询数据不是从数据库查询的,而是从一级缓存获取的。
现在两次查询直接执行commit操作(更新,删除或插入):

@Test
    public void testSession() throws  Exception {
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        AyUserDao ayUserDao1 = sqlSession.getMapper(AyUserDao.class);
        //第一次查询
        AyUser ayUser = ayUserDao1.findById("1");
        System.out.println(ayUser.getName());
        //执行commit操作:
        AyUser ayUser1 = new AyUser();
        ayUser1.setId(1);
        ayUser1.setName("a1");
        ayUserDao1.updateUser(ayUser1);

        //第二次查询
        AyUser ayUser2 = ayUserDao1.findById("1");
        System.out.println(ayUser2.getName());
        sqlSession.close();
    }

控制台打印相关的信息:
在这里插入图片描述

生命周期
在开启一个Session会话的时候会创建新的SqlSession对象,每个对象会创建一个新的Executor对象,即:如果SqlSession调用了close方法会释放掉一级缓存对象;调用了clearCache会清空PerpetualCache对象数据但对象可用;执行一个DDL操作会清空对象数据,但该对象仍可以继续使用。

二级查询缓存

二级缓存是Mapper级别的缓存,多个SqlSession 使用一个Mapper(namespace)的SQL语句操作数据库,跨SqlSession,当某个SqlSession类实例对象执行了DML操作,Mapper会清空二级缓存,MyBatis默认不开启二级缓存。
在applicationContext.xml配置如下:
最重要的是指定MyBatis配置文件的位置

<!--3、配置SqlSessionFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"/>
        <!--扫描sql配置文件:mapper需要的xml文件-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
        <!-- mybatis配置文件的位置 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        
    </bean>

然后在resources添加mybatis-config.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 全局配置参数,需要时再设置 -->

    <settings>
        <!-- 开启二级缓存  默认是不开启的-->
        <setting name="cacheEnabled" value="true"/>
      
    </settings>
</configuration>

最后由于二级缓存是Mapper级别的,需要开启二级缓存的具体mapper.xml文件中开启二级缓存,只需要在mapper.xml添加一个cache标签即可,其属性如下:
在这里插入图片描述

开启AyUserMapper的namespace二级缓存
<cache/>

二级缓存的实例:

需要在AyUserMapper.xml加cache:

<mapper namespace="com.ay.dao.AyUserDao">
    <!-- 开启AyUserMapper的namespace下的二级缓存 -->
    <cache/>
@Test
    public void testSession() throws  Exception {
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        AyUserDao ayUserDao1 = sqlSession.getMapper(AyUserDao.class);
        //第一次查询
        AyUser ayUser = ayUserDao1.findById("1");
        System.out.println(ayUser.getName());
        //执行commit操作:
        AyUser ayUser1 = new AyUser();
        ayUser1.setId(1);
        ayUser1.setName("a1");
        ayUserDao1.updateUser(ayUser1);

        //第二次查询(命中缓存)
        AyUser ayUser2 = ayUserDao1.findById("1");
        System.out.println(ayUser2.getName());
        sqlSession.close();
    }

AyUserDao和AyUserMapper.xml 不变
运行@Test:
在这里插入图片描述
开启了二级缓存会从其获取数据,需要在select 标签设置
useCache=“false” 禁用当前的select语句使用二级缓存

<select id="findById" useCache="false" parameterType="String" resultType="com.ay.model.AyUser">
        select * from ay_user where id = #{id}
    </select>

二级缓存的特点:以namespace为单位,不同namespace下的操作互不影响;增删改查会清空namespace 下全部缓存。
不过需要注意的是,有时候不同的namespace下的SQL配置可以缓存了相同的数据,例如AyUserMapper.xml,其他XXXMapper.xml有针对用户表的单表操作也缓存了用户数据,如果在AyUserMapper.xml做了刷新操作在XXXMapper.xml缓存的数据依然有效,这样会出现脏读。
所以根据业务情况,谨慎使用二级缓存。

cache-ref共享缓存
MyBatis 并不是整个Application 只有一个Cache缓存对象,将缓存划分得更细,也就是Mapper级别,每一个Mapper都有一个Cache对象:1.为每一个Mapper分配一个Cache缓存对象 (cache) 2.多个Mapper公用一个Cache缓存对象(cache-ref)

<mapper namespace="com.ay.dao.MoodDao">
    <cache-ref namespace="com.ay.dao.UserDao"/>

MyBatis 缓存原理

一个SqlSession对象创建一个本地缓存local cache,对于每次查询,会根据查询条件去一级缓存查找,如果缓存存在数据直接读出否则从数据库读。
在这里插入图片描述
交给了Executor执行器来完成,完成对数据库的操作。MyBatis会这个SqlSession创建一个新的Executor执行器,而缓存信息被维护在这个器中,MaBatis 将缓存和对缓存的操作封装成Cache接口。
二级缓存机制关键是使用Executor对象,开启SqlSession会话时,如果用户配置了"cacheEnable = true"会加上一个装饰者:CachingExecutor。
在这里插入图片描述
装饰器模式(Decorator Pattern)在不改变一个对象本身功能的基础上给对象增加额外的功能,一种用于替代继承的技术。
Cache接口是MyBatis缓存模块中最核心的接口,定义了所有缓存的基本行为,其源码如下:

public interface Cache {
//该缓存对象的id
    String getId();
// 向缓存添加数据,一般key为CacheKey,value为查询结果
    void putObject(Object var1, Object var2);
// 根据指定的key 在缓存查找对应的结果对象
    Object getObject(Object var1);
// 删除key对应的缓存项
    Object removeObject(Object var1);
// 清空缓存
    void clear();
// 缓存项个数
    int getSize();
// 获取读写锁
    ReadWriteLock getReadWriteLock();
}

Cache实现类有很多:
在这里插入图片描述
大部分都是装饰器,只有PrepetualCache提供了Cache接口的基本实现。

public class PerpetualCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new HashMap();

    public PerpetualCache(String id) {
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public int getSize() {
        return this.cache.size();
    }

    public void putObject(Object key, Object value) {
        this.cache.put(key, value);
    }

    public Object getObject(Object key) {
        return this.cache.get(key);
    }

    public Object removeObject(Object key) {
        return this.cache.remove(key);
    }

    public void clear() {
        this.cache.clear();
    }

    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    public boolean equals(Object o) {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else if (this == o) {
            return true;
        } else if (!(o instanceof Cache)) {
            return false;
        } else {
            Cache otherCache = (Cache)o;
            return this.getId().equals(otherCache.getId());
        }
    }

    public int hashCode() {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else {
            return this.getId().hashCode();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/4898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html实现个人空间主页(附源码)

文章目录1.设计思路1.1 欢迎界面1.2 屏保界面1.3 主界面1.4 我的项目界面1.5 我的日记界面1.6 我的日记管理界面2.效果展示和代码展示2.1 动态效果图2.2 主界面代码2.3 欢迎页代码2.4 屏保代码源码下载作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weix…

JavaScript算法 — 二叉树遍历

目录1、构造二叉树2、递归遍历3、非递归遍历3.1 先序3.2 中序3.3 后序1、构造二叉树 树节点&#xff1a; // 二叉树节点的构造函数 function TreeNode(val, left, right) {this.val (valundefined ? 0 : val)this.left (leftundefined ? null : left)this.right (rightu…

给好朋友用代码画一个爱心吧

目录 效果图 html爱心 python爱心 ​编辑 代码 html python 浅浅分析一下《燃烧我&#xff0c;照亮你》剧中的爱心代码 光棍节要到了&#xff0c;不给心意的人写个爱心代码&#xff1f; 话不多说&#xff0c;上才艺&#xff0c;这里有两种爱心&#xff0c;一种是html&a…

Unity功能—— 在VS中快速访问Unity API对应文档

声明&#xff1a;本文为个人笔记&#xff0c;用于学习研究使用非商用&#xff0c;内容为个人研究及综合整理所得&#xff0c;若有违规&#xff0c;请联系&#xff0c;违规必改。 Unity功能—— 在VS中快速访问Unity API对应文档 文章目录Unity功能—— 在VS中快速访问Unity API…

光点科技数据口袋数据填报系统满足多类型企业报表需求_光点科技

在招聘过程中&#xff0c;HR对数据处理存在一定的需求&#xff0c;手动整理繁杂的数据无疑是加大招聘工作量&#xff0c;因此&#xff0c;借助数据填报系统更好地进行处理数据工作&#xff0c;不失为帮助HR减轻招聘工作量的良方。 光点数据填报系统利用传统商业报表工具进行数据…

中国热泵空调行业发展趋势及投资风险研究报告

智研瞻产业研究院专注于中国产业经济情报及研究&#xff0c;目前主要提供的产品和服务包括传统及新兴行业研究、商业计划书、可行性研究、市场调研、专题报告、定制报告等。涵盖文化体育、物流旅游、健康养老、生物医药、能源化工、装备制造、汽车电子、农林牧渔等领域&#xf…

基于springboot二手交易平台

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;非前后端分离 前端技术&#xff1a;vue.jselementUI等框架实现 服务端技术&#xff1a;springbootmybatis-pl…

Linux服务器中配置tomcat的服务,并通过端口8888访问

引言: Tomcat是常见的免费的web服务器,前端服务很多都是通过tomcat部署的&#xff01;所以多了解点肯定没坏处&#xff01; 一、配置端口 1.防火墙策略中配置8888端口&#xff0c;并允许策略&#xff1a; 二、安装tomcat包 1.新建tomcat文件夹 进入 /usr/local,并新建文件夹…

弹框确认按钮,请求两个接口跳转刷新页面,并使用async和await将异步改成同步的数据?

前景&#xff1a;公司前后端不分离项目&#xff0c;使用的框架element-ui较低版本的&#xff0c;弹框确定按钮加载动态按钮的loading。 //插件 npm i element-ui -S效果图: 代码: <template><div><el-button type"text" click"dialogVisible …

Hive——操作数据库创建修改表(DDL数据定义)

DDL操作1. 数据库操作1.1 创建&查询数据库1.2 修改&删除数据库2. 表操作2.1 创建表2.2 内部表和外部表2.2.1 管理表2.2.2外部表2.2.3管理表与外部表的互相转换2.3 修改表1. 数据库操作 1.1 创建&查询数据库 定义&#xff1a; CREATE DATABASE [IF NOT EXISTS] d…

IDEA settings设置技巧,最常用快捷键,让你的编译器用更加得心应手

导读 每次下载安装新的 IDEA 以后&#xff0c;都免不了对该软件进行一些优化配置&#xff0c;以方便我们在使用的时候更加得心应手。一份趁手的设置&#xff0c;无疑能让我们的工作效率事半功倍。 碍于每次自己重装 IDEA 都需要去网搜一大堆文章去设置 settings&#xff0c;所以…

Vue框架的学习(Vue的基础指令操作二 V-For)第二课

今天的任务理解下面的几个指令操作 重点在V-for上 V-if V-else V-show V-For 本文章的重心放在V-For 从数据到数组到对象一步一步的去查找 底层的原理 v-show和v-if的用法看起来是一致的&#xff0c;也是根据一个条件决定是否显示元素或者组件 下面是 V-if V-else V-sho…

1. 云计算简介

1.2 云计算的定义和分类 云计算的定义 美国国家标准与技术研究院&#xff08;NIST&#xff09;定义&#xff1a; 云计算是一种模型&#xff0c;它可以实现随时随地、便捷地、随需应变地从可配置计算资源共享池中获取所需的资源&#xff08;例如&#xff0c;网络、服务器、存…

【记录】终端如何 进入conda(base) 环境,如何退出 conda(base)环境,终端快速进入Jupyter notebook的方法

目录一、终端 进入 conda&#xff08;base&#xff09; 环境二、终端 退出 conda&#xff08;base&#xff09; 环境三、终端进入 Jupyter notebook 的方法一、终端 进入 conda&#xff08;base&#xff09; 环境 --->> win R : 输入cmd 回车&#xff0c;进入终端界面。…

什么是微服务?怎么测试?今天一次性讲清楚...

01、什么是微服务 Adrian Cockcroft对微服务的表述&#xff1a;loosely couped service oriented architecture with bounded context。 这里涉及两个微服务的概念&#xff1a; loosely couped&#xff1a;松耦合 松耦合可以引申出其他概念&#xff0c;如各自独立&#xff0c…

坚持软件自主可控,打造国产化公文交换系统

编者按&#xff1a;软件自主可控是什么意思&#xff1f;企业如何对自己使用的软件做到自主可控&#xff1f;本文分析了软件自主可控的概念及意义&#xff0c;并通关相关案例展示了国产化低代码平台是如何助力企业打造公文交换系统的。 软件自主可控的意义 说到软件的自主可控&…

【分享 10 个日常使用的脚本】

&#x1f935;‍♂️ 个人主页老虎也淘气 个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f44d;&#x1f3fb; 收藏…

第十节:继承【java】

目录 &#x1f4d8;1.1为什么需要继承 &#x1f4d2;1.2 继承概念 &#x1f4d7;1.3 继承的语法 &#x1f4d5;1.4 父类成员访问 &#x1f3b1;1.4.1 子类中访问父类的成员变量 &#x1f38e;1.4.2 子类中访问父类的成员方法 &#x1f4d9;1.5 super关键字 &#x1f4d…

基于Matlab利用移动目标指示雷达抑制地面杂波(附源码)

目录 一、构建雷达系统 二、定义目标 三、杂波 四、仿真接收到的脉冲和匹配滤波器 五、使用三脉冲消除器执行 六、使用交错PRF模拟接收到的脉冲 七、对交错的 PRF 执行 八、总结 九、程序 本示例显示了移动目标指示 &#xff08;MTI&#xff09; 雷达的设计&#xff0…

《Java》private、protected、public区别及解析

我们今天来给大家解析一下Java中四种修饰类型的区别。 那么有的老铁就疑惑了&#xff0c;文章名称不是说三种吗&#xff1f;其实还有一种隐藏的修饰类型&#xff08;默认类型&#xff09;英文翻译过来的话是define&#xff0c;这就是我们什么都不修饰&#xff0c;例如&#xff…