SpringBoot中使用redis事务

private StringRedisTemplate template;

public DemoController(StringRedisTemplate template) {

this.template = template;

}

@GetMapping(“/put”)

public String redisSet() {

int i = (int)(Math.random() * 100);

template.opsForValue().set(“key”+i, “value”+i, 300, TimeUnit.SECONDS);

return "success "+“key”+i;

}

}

启动后,我们使用RestClient发送请求http://localhost:8080/put,发送8次之后就会发现没有返回了。这个时候我们查看redis的链接数,发现已经超过8个,springboot对于jedis连接池默认的最大活跃连接数是8,所以看出来是连接池被耗光了。

127.0.0.1:6379> info clients

Clients

connected_clients:9

client_longest_output_list:0

client_biggest_input_buf:0

blocked_clients:0

127.0.0.1:6379>

还有查看程序的日志可以发现,RedisConnectionUtils只有Opening RedisConnection而没有close。

2018-08-11 11:00:48.889 [DEBUG][http-nio-8080-exec-8]😮.s.data.redis.core.RedisConnectionUtils [doGetConnection:126] Opening RedisConnection

2018-08-11 11:00:50.169 [DEBUG][http-nio-8080-exec-8]😮.s.w.s.m.m.a.RequestResponseBodyMethodProcessor [writeWithMessageConverters:249] Written [success key39] as “text/plain” using [org.springframework.http.converter.StringHttpMessageConverter@766a49c7]

2018-08-11 11:00:50.170 [DEBUG][http-nio-8080-exec-8]:org.springframework.web.servlet.DispatcherServlet [processDispatchResult:1044] Null ModelAndView returned to DispatcherServlet with name ‘dispatcherServlet’: assuming HandlerAdapter completed request handling

2018-08-11 11:00:50.170 [DEBUG][http-nio-8080-exec-8]:org.springframework.web.servlet.DispatcherServlet [processRequest:1000] Successfully completed request

2018-08-11 11:00:50.170 [DEBUG][http-nio-8080-exec-8]😮.s.boot.web.filter.OrderedRequestContextFilter [doFilterInternal:104] Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@c03b2d8

2018-08-11 11:00:53.854 [DEBUG][http-nio-8080-exec-9]😮.s.boot.web.filter.OrderedRequestContextFilter [initContextHolders:114] Bound request context to thread: org.apache.catalina.connector.RequestFacade@c03b2d8

2018-08-11 11:00:53.856 [DEBUG][http-nio-8080-exec-9]:org.springframework.web.servlet.DispatcherServlet [doService:865] DispatcherServlet with name ‘dispatcherServlet’ processing GET request for [/put]

2018-08-11 11:00:53.857 [DEBUG][http-nio-8080-exec-9]😮.s.w.s.m.m.a.RequestMappingHandlerMapping [getHandlerInternal:310] Looking up handler method for path /put

2018-08-11 11:00:53.857 [DEBUG][http-nio-8080-exec-9]😮.s.w.s.m.m.a.RequestMappingHandlerMapping [getHandlerInternal:317] Returning handler method [public java.lang.String com.github.springboot.demo.DemoController.redisSet()]

2018-08-11 11:00:53.858 [DEBUG][http-nio-8080-exec-9]😮.s.b.factory.support.DefaultListableBeanFactory [doGetBean:251] Returning cached instance of singleton bean ‘demoController’

2018-08-11 11:00:53.858 [DEBUG][http-nio-8080-exec-9]:org.springframework.web.servlet.DispatcherServlet [doDispatch:951] Last-Modified value for [/put] is: -1

2018-08-11 11:00:53.861 [DEBUG][http-nio-8080-exec-9]😮.s.data.redis.core.RedisConnectionUtils [doGetConnection:126] Opening RedisConnection

关闭template的事务支持 接下来我们修改一下RedisConfiguration的配置,不启用事务管理,

@Bean

public StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {

StringRedisTemplate template = new StringRedisTemplate();

template.setConnectionFactory(redisConnectionFactory);

// template.setEnableTransactionSupport(true); //禁用事务支持

return template;

}

重新测试一下,发现是正常的,redis的client链接数一直保持在2。程序日志里的也可以看到Redis Connection关闭的日志。

2018-08-11 15:55:19.975 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [doGetConnection:126] Opening RedisConnection

2018-08-11 15:55:20.029 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [releaseConnection:210] Closing Redis Connection

2018-08-11 15:55:20.056 [DEBUG][http-nio-8080-exec-1]😮.s.w.s.m.m.a.RequestResponseBodyMethodProcessor [writeWithMessageConverters:249] Written [success key72] as “text/plain” using [org.springframework.http.converter.StringHttpMessageConverter@51ab1ee3]

也就是说,如果我们把事务的支持打开,spring在每次操作之后是不会主动关闭连接的。我们去RedisTemplate的源码中找下原因。

public ValueOperations<K, V> opsForValue() {

if (valueOps == null) {

valueOps = new DefaultValueOperations<K, V>(this);

}

return valueOps;

}

可以发现template.opsForValue().set()操作最终是调用的DefaultValueOperations中的set()方法,继续跟进去最终调用的RedisTemplate中的execute(RedisCallback action, boolean exposeConnection, boolean pipeline)方法。

public T execute(RedisCallback action, boolean exposeConnection, boolean pipeline) {

Assert.isTrue(initialized, “template not initialized; call afterPropertiesSet() before using it”);

Assert.notNull(action, “Callback object must not be null”);

RedisConnectionFactory factory = getConnectionFactory();

RedisConnection conn = null;

try {

if (enableTransactionSupport) {

// only bind resources in case of potential transaction synchronization

conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);

} else {

conn = RedisConnectionUtils.getConnection(factory);

}

boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);

RedisConnection connToUse = preProcessConnection(conn, existingConnection);

boolean pipelineStatus = connToUse.isPipelined();

if (pipeline && !pipelineStatus) {

connToUse.openPipeline();

}

RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));

T result = action.doInRedis(connToExpose);

// close pipeline

if (pipeline && !pipelineStatus) {

connToUse.closePipeline();

}

// TODO: any other connection processing?

return postProcessResult(result, connToUse, existingConnection);

} finally {

RedisConnectionUtils.releaseConnection(conn, factory);

}

}

可以看到获取连接的操作也针对打开事务支持的template有特殊的处理逻辑。这里我们先跳过,先看看最终肯定会走到的RedisConnectionUtils.releaseConnection(conn, factory)这一步。

/**

  • Closes the given connection, created via the given factory if not managed externally (i.e. not bound to the

  • thread).

  • @param conn the Redis connection to close

  • @param factory the Redis factory that the connection was created with

*/

public static void releaseConnection(RedisConnection conn, RedisConnectionFactory factory) {

if (conn == null) {

return;

}

RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory);

if (connHolder != null && connHolder.isTransactionSyncronisationActive()) {

if (log.isDebugEnabled()) {

log.debug(“Redis Connection will be closed when transaction finished.”);

}

return;

}

// release transactional/read-only and non-transactional/non-bound connections.

// transactional connections for read-only transactions get no synchronizer registered

if (isConnectionTransactional(conn, factory)

&& TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {

unbindConnection(factory);

} else if (!isConnectionTransactional(conn, factory)) {

if (log.isDebugEnabled()) {

log.debug(“Closing Redis Connection”);

}

conn.close();

}

}

可以看到针对打开事务支持的template,只是解绑了连接,根本没有做close的操作。关于什么是解绑,其实这个方法的注释中已经说的比较清楚了,对于开启了事务的Template,由于已经绑定了线程中连接,所以这里是不会关闭的,只是做了解绑的操作。

到这里原因就很清楚了,就是只要template开启了事务支持,spring就认为只要使用这个template就会包含在事务当中,因为一个事务中的操作必须在同一个连接中完成,所以在每次get/set之后,template是不会关闭链接的,因为它不知道事务有没有结束。

使用@Transanctional注解支持Redis事务

既然RedisTemlate在setEnableTransactionSupport会造成连接不关闭,那怎么样才能正常关闭呢?我们将事务支持开关和@Transanctional结合起来用看看会怎么样。

spring中要使用@Transanctional首先要配transactionManager,但是spring没有专门针对Redis的事务管理器实现,而是所有调用RedisTemplate的方法最终都会调用到RedisConnctionUtils这个类的方法上面,在这个类里面会判断是不是进入到事务里面,也就是说Redis的事务管理的功能是由RedisConnctionUtils内部实现的。

根据官方文档,我只想用Redis事务,也必须把JDBC捎上。当然反过来讲,不依赖数据的项目确实不多,貌似这么实现影响也不大。下面我们先根据官方文档配置一下看看效果。

首先修改POM配置,添加两个依赖。如果项目里本来已经使用了数据库,那这一步就不需要了。

org.springframework.boot

spring-boot-starter-jdbc

com.h2database

h2

runtime

然后修改RedisConfiguration

@Bean

public StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {

StringRedisTemplate template = new StringRedisTemplate();

template.setConnectionFactory(redisConnectionFactory);

template.setEnableTransactionSupport(true);//打开事务支持

return template;

}

//配置事务管理器

@Bean

public PlatformTransactionManager transactionManager(DataSource dataSource) throws SQLException {

return new DataSourceTransactionManager(dataSource);

}

我们新建一个RedisService,将原来的数据操作移到service里面。同时将Service方法加上@Transactional注解。

@Service

public class RedisService {

private StringRedisTemplate template;

public RedisService(StringRedisTemplate template) {

this.template = template;

}

@Transactional

public String put() {

int i = (int)(Math.random() * 100);

template.opsForValue().set(“key”+i, “value”+i, 300, TimeUnit.SECONDS);

return "success "+“key”+i;

}

}

//-----------------------------------------------------------

//controller里面加一个新的方法,调用Service

@GetMapping(“/puttx”)

public String redisTxSet() {

return redisService.put();

}

完成这些工作之后,再往http://localhost:8080/puttx发送请求,无论点多少次,Redis的连接数始终维持在1个不变。在看程序的输出日志里面我们也发现了,事务结束后连接被正常释放。因为使用了JDBC的事务管理器,所以还顺便做了一次数据库事务的开启和提交。还有一点值得注意的是,跟数据库一样,使用注解来做事务管理,spring也会主动管理redis事务的提交和回滚,也就是在之前发送一条MULTI命令,成功后发送EXEC,失败后发送DISCARD。

2018-08-11 20:57:04.990 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [doGetConnection:126] Opening RedisConnection

2018-08-11 20:57:04.990 [DEBUG][http-nio-8080-exec-1]😮.springframework.aop.framework.JdkDynamicAopProxy [getProxy:118] Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [org.springframework.data.redis.connection.jedis.JedisConnection@20f2be3c]

2018-08-11 20:57:04.990 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [intercept:337] Invoke ‘multi’ on bound conneciton

2018-08-11 20:57:04.991 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [intercept:337] Invoke ‘isPipelined’ on bound conneciton

2018-08-11 20:57:04.991 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [intercept:337] Invoke ‘setEx’ on bound conneciton

2018-08-11 20:57:04.991 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [releaseConnection:198] Redis Connection will be closed when transaction finished.

2018-08-11 20:57:04.991 [DEBUG][http-nio-8080-exec-1]😮.s.jdbc.datasource.DataSourceTransactionManager [processCommit:759] Initiating transaction commit

2018-08-11 20:57:04.991 [DEBUG][http-nio-8080-exec-1]😮.s.jdbc.datasource.DataSourceTransactionManager [doCommit:310] Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[conn9: url=jdbc:h2:mem:testdb user=SA]]]

2018-08-11 20:57:04.992 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [intercept:337] Invoke ‘exec’ on bound conneciton

2018-08-11 20:57:04.992 [DEBUG][http-nio-8080-exec-1]😮.s.data.redis.core.RedisConnectionUtils [afterCompletion:306] Closing bound connection after transaction completed with 0
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

美团二面惜败,我的凉经复盘(附学习笔记+面试整理+进阶书籍)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

[外链图片转存中…(img-W9myZHlY-1713404508020)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值