[Spring MVC6]事务管理与缓存机制
创始人
2024-01-20 18:07:57
0

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的方法和类。


在这里插入图片描述

并发事务所导致的三个问题:
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

 	@Resourceprivate SqlSessionFactoryBean sqlSessionFactoryBean;@Testpublic 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已经配置:


AyUserDao.java:

AyUser findById(String id);

AyUserMapper.xml:


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

@Testpublic 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配置文件的位置


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





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

开启AyUserMapper的namespace二级缓存

二级缓存的实例:

需要在AyUserMapper.xml加cache:


@Testpublic 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语句使用二级缓存


二级缓存的特点:以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)


MyBatis 缓存原理

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

public interface Cache {
//该缓存对象的idString 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 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();}}
}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...