Skip to content

Commit bc1bbd1

Browse files
christophstroblThomas Darimont
authored and
Thomas Darimont
committed
DATAREDIS-73 - Add support for spring managed transactions.
RedisTemplate now supports enabling of transaction support, which is disabled by default. In case of enabled transaction support the RedisConnections will be bound to the current thread during ongoing outer transactions. We call MULTI at the beginning and depending on transaction state EXEC or DISCARD at its end. To support read operations during the transaction we wrap a proxy around the bound connection piping read operations to a new (non thread bound) connection obtained by the underlying RedisConnectionFactory. Transaction support is available for jedis, lettuce and srp, while had to be skipped for jredis due to the lack of support for MULTI. Original pull request: #64.
1 parent 4e4a296 commit bc1bbd1

File tree

11 files changed

+1080
-16
lines changed

11 files changed

+1080
-16
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ dependencies {
5151
compile "org.springframework:spring-context:$springVersion"
5252
compile "org.springframework:spring-tx:$springVersion"
5353
compile("org.springframework:spring-oxm:$springVersion", optional)
54+
compile "org.springframework:spring-aop:$springVersion"
5455

5556
// Redis Drivers
5657
compile("redis.clients:jedis:$jedisVersion", optional)
Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
/*
2+
* Copyright 2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.core;
17+
18+
import java.util.Arrays;
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.HashSet;
22+
import java.util.Map;
23+
import java.util.Set;
24+
25+
import org.jredis.Redis;
26+
import org.springframework.util.StringUtils;
27+
28+
/**
29+
* @author Christoph Strobl
30+
* @author Thomas Darimont
31+
* @since 1.3
32+
* @see Redis command list:
33+
* https://github.com/antirez/redis/blob/93e7a130fc9594e41ccfc996b5eca7626ae5356a/src/redis.c#L119
34+
*/
35+
enum RedisCommand {
36+
// -- A
37+
APPEND("rw", 2, 2), //
38+
AUTH("rw", 1, 1), //
39+
// -- B
40+
BGREWRITEAOF("r", 0, 0, "bgwriteaof"), //
41+
BGSAVE("r", 0, 0), //
42+
BITCOUNT("r", 1, 3), //
43+
BITOP("rw", 3), //
44+
BITPOS("r", 2, 4), //
45+
BLPOP("rw", 2), //
46+
BRPOP("rw", 2), //
47+
BRPOPLPUSH("rw", 3), //
48+
// -- C
49+
CLIENT_KILL("rw", 1, 1), //
50+
CLIENT_LIST("r", 0, 0), //
51+
CLIENT_GETNAME("r", 0, 0), //
52+
CLIENT_PAUSE("rw", 1, 1), //
53+
CLIENT_SETNAME("w", 1, 1), //
54+
CONFIG_GET("r", 1, 1, "getconfig"), //
55+
CONFIG_REWRITE("rw", 0, 0), //
56+
CONFIG_SET("w", 2, 2, "setconfig"), //
57+
CONFIG_RESETSTAT("w", 0, 0, "resetconfigstats"), //
58+
// -- D
59+
DBSIZE("r", 0, 0), //
60+
DECR("w", 1, 1), //
61+
DECRBY("w", 2, 2), //
62+
DEL("rw", 1), //
63+
DISCARD("rw", 0, 0), //
64+
DUMP("r", 1, 1), //
65+
// -- E
66+
ECHO("r", 1, 1), //
67+
EVAL("rw", 2), //
68+
EVALSHA("rw", 2), //
69+
EXEC("rw", 0, 0), //
70+
EXISTS("r", 1, 1), //
71+
EXPIRE("rw", 2, 2), //
72+
EXPIREAT("rw", 2, 2), //
73+
// -- F
74+
FLUSHALL("w", 0, 0), //
75+
FLUSHDB("w", 0, 0), //
76+
// -- G
77+
GET("r", 1, 1), //
78+
GETBIT("r", 2, 2), //
79+
GETRANGE("r", 3, 3), //
80+
GETSET("rw", 2, 2), //
81+
// -- H
82+
HDEL("rw", 2), //
83+
HEXISTS("r", 2, 2), //
84+
HGET("r", 2, 2), //
85+
HGETALL("r", 1, 1), //
86+
HINCRBY("rw", 3, 3), //
87+
HINCBYFLOAT("rw", 3, 3), //
88+
HKEYS("r", 1), //
89+
HLEN("r", 1), //
90+
HMGET("r", 2), //
91+
HMSET("w", 3), //
92+
HSET("w", 3, 3), //
93+
HSETNX("w", 3, 3), //
94+
HVALS("r", 1, 1), //
95+
// -- I
96+
INCR("rw", 1), //
97+
INCRBYFLOAT("rw", 2, 2), //
98+
INFO("r", 0), //
99+
// -- K
100+
KEYS("r", 1), //
101+
// -- L
102+
LASTSAVE("r", 0), //
103+
LINDEX("r", 2, 2), //
104+
LINSERT("rw", 4, 4), //
105+
LLEN("r", 1, 1), //
106+
LPOP("rw", 1, 1), //
107+
LPUSH("rw", 2), //
108+
LPUSHX("rw", 2), //
109+
LRANGE("r", 3, 3), //
110+
LREM("rw", 3, 3), //
111+
LSET("w", 3, 3), //
112+
LTRIM("w", 3, 3), //
113+
// -- M
114+
MGET("r", 1), //
115+
MIGRATE("rw", 0), //
116+
MONITOR("rw", 0, 0), //
117+
MOVE("rw", 2, 2), //
118+
MSET("w", 2), //
119+
MSETNX("w", 2), //
120+
MULTI("rw", 0, 0), //
121+
// -- P
122+
PERSIST("rw", 1, 1), //
123+
PEXPIRE("rw", 2, 2), //
124+
PEXPIREAT("rw", 2, 2), //
125+
PING("r", 0, 0), //
126+
PSETEX("w", 3), //
127+
PSUBSCRIBE("r", 1), //
128+
PTTL("r", 1, 1), //
129+
// -- Q
130+
QUIT("rw", 0, 0), //
131+
// -- R
132+
RANDOMKEY("r", 0, 0), //
133+
RANAME("w", 2, 2), //
134+
RENAMENX("w", 2, 2), //
135+
RESTORE("w", 3, 3), //
136+
RPOP("rw", 1, 1), //
137+
RPOPLPUSH("rw", 2, 2), //
138+
RPUSH("rw", 2), //
139+
RPUSHX("rw", 2, 2), //
140+
// -- S
141+
SADD("rw", 2), //
142+
SAVE("rw", 0, 0), //
143+
SCARD("r", 1, 1), //
144+
SCRIPT_EXISTS("r", 1), //
145+
SCRIPT_FLUSH("rw", 0, 0), //
146+
SCRIPT_KILL("rw", 0, 0), //
147+
SCRIPT_LOAD("rw", 1, 1), //
148+
SDIFF("r", 1), //
149+
SDIFFSTORE("rw", 2), //
150+
SELECT("rw", 0, 0), //
151+
SET("w", 2), //
152+
SETBIT("rw", 3, 3), //
153+
SETEX("w", 3, 3), //
154+
SETNX("w", 2, 2), //
155+
SETRANGE("rw", 3, 3), //
156+
SHUTDOWN("rw", 0), //
157+
SINTER("r", 1), //
158+
SINTERSTORE("rw", 2), //
159+
SISMEMBER("r", 2), //
160+
SLAVEOF("w", 2), //
161+
SLOWLOG("rw", 1), //
162+
SMEMBERS("r", 1, 1), //
163+
SMOVE("rw", 3, 3), //
164+
SORT("rw", 1), //
165+
SPOP("rw", 1, 1), //
166+
SRANDMEMBER("r", 1, 1), //
167+
SREM("rw", 2), //
168+
STRLEN("r", 1, 1), //
169+
SUBSCRIBE("rw", 1), //
170+
SUNION("r", 1), //
171+
SUNIONSTORE("rw ", 2), //
172+
SYNC("rw", 0, 0), //
173+
// -- T
174+
TIME("r", 0, 0), //
175+
TTL("r", 1, 1), //
176+
TYPE("r", 1, 1), //
177+
// -- U
178+
UNSUBSCRIBE("rw", 0), //
179+
UNWATCH("rw", 0, 0), //
180+
// -- W
181+
WATCH("rw", 1), //
182+
// -- Z
183+
ZADD("rw", 3), //
184+
ZCARD("r", 1), //
185+
ZCOUNT("r", 3, 3), //
186+
ZINCRBY("rw", 3), //
187+
ZINTERSTORE("rw", 3), //
188+
ZRANGE("r", 3), //
189+
ZRANGEBYSCORE("r", 3), //
190+
ZRANK("r", 2, 2), //
191+
ZREM("rw", 2), //
192+
ZREMRANGEBYRANK("rw", 3, 3), //
193+
ZREMRANGEBYSCORE("rm", 3, 3), //
194+
ZREVRANGE("r", 3), //
195+
ZREVRANGEBYSCORE("r", 3), //
196+
ZREVRANK("r", 2, 2), //
197+
ZSCORE("r", 2, 2), //
198+
ZUNIONSTORE("rw", 3), //
199+
SCAN("r", 1), //
200+
SSCAN("r", 2), //
201+
HSCAN("r", 2), //
202+
ZSCAN("r", 2), //
203+
// -- UNKNOWN / DEFAULT
204+
UNKNOWN("rw", -1);
205+
206+
private boolean read = true;
207+
private boolean write = true;
208+
private Set<String> alias = new HashSet<String>(1);
209+
210+
private int minArgs = -1;
211+
private int maxArgs = -1;
212+
213+
private final static Map<String, RedisCommand> commandLookup;
214+
215+
static {
216+
commandLookup = buildCommandLookupTable();
217+
}
218+
219+
private static Map<String, RedisCommand> buildCommandLookupTable() {
220+
221+
RedisCommand[] cmds = RedisCommand.values();
222+
Map<String, RedisCommand> map = new HashMap<String, RedisCommand>(cmds.length, 1.0F);
223+
224+
for (RedisCommand cmd : cmds) {
225+
226+
map.put(cmd.name().toLowerCase(), cmd);
227+
228+
for (String alias : cmd.alias) {
229+
map.put(alias, cmd);
230+
}
231+
}
232+
233+
return Collections.unmodifiableMap(map);
234+
}
235+
236+
private RedisCommand(String mode, int minArgs) {
237+
this(mode, minArgs, -1);
238+
}
239+
240+
private RedisCommand(String mode, int minArgs, int maxArgs) {
241+
242+
if (StringUtils.hasText(mode)) {
243+
this.read = mode.toLowerCase().indexOf('r') > -1;
244+
this.write = mode.toLowerCase().indexOf('w') > -1;
245+
}
246+
247+
this.minArgs = minArgs;
248+
this.maxArgs = maxArgs;
249+
}
250+
251+
/**
252+
* Creates a new {@link RedisCommand}.
253+
*
254+
* @param mode
255+
* @param minArgs
256+
* @param maxArgs
257+
* @param alias
258+
*/
259+
private RedisCommand(String mode, int minArgs, int maxArgs, String... alias) {
260+
261+
this(mode, minArgs, maxArgs);
262+
263+
if (alias != null && alias.length > 0) {
264+
this.alias.addAll(Arrays.asList(alias));
265+
}
266+
}
267+
268+
/**
269+
* @return {@literal true} if the command requires arguments
270+
*/
271+
public boolean requiresArguments() {
272+
return minArgs >= 0;
273+
}
274+
275+
/**
276+
* @return {@literal true} if an exact number of arguments is expected
277+
*/
278+
public boolean requiresExactNumberOfArguments() {
279+
return maxArgs >= 0;
280+
}
281+
282+
/**
283+
* @return {@literal true} if the command triggers a read operation
284+
*/
285+
public boolean isRead() {
286+
return read;
287+
}
288+
289+
/**
290+
* @return {@literal true} if the command triggers a write operation
291+
*/
292+
public boolean isWrite() {
293+
return write;
294+
}
295+
296+
/**
297+
* @return {@literal true} if values are read but not written
298+
*/
299+
public boolean isReadonly() {
300+
return read && !write;
301+
}
302+
303+
/**
304+
* {@link String#equalsIgnoreCase(String)} compare the given string representation of {@literal command} against the
305+
* {@link #toString()} representation of the command as well as its given {@link #alias}.
306+
*
307+
* @param command
308+
* @return true if positive match.
309+
*/
310+
public boolean isRepresentedBy(String command) {
311+
312+
if (!StringUtils.hasText(command)) {
313+
return false;
314+
}
315+
316+
if (toString().equalsIgnoreCase(command)) {
317+
return true;
318+
}
319+
320+
return alias.contains(command.toLowerCase());
321+
}
322+
323+
/**
324+
* Validates given argument count against expected ones.
325+
*
326+
* @param nrArguments
327+
* @throws {@link IllegalArgumentException} in case argument count does not match expected.
328+
*/
329+
public void validateArgumentCount(int nrArguments) {
330+
331+
if (requiresArguments()) {
332+
if (requiresExactNumberOfArguments()) {
333+
if (nrArguments != maxArgs) {
334+
throw new IllegalArgumentException(String.format("%s command requires %s arguments.", this.name(),
335+
this.maxArgs));
336+
}
337+
}
338+
if (nrArguments < minArgs) {
339+
throw new IllegalArgumentException(String.format("%s command requires at least %s arguments.", this.name(),
340+
this.minArgs));
341+
}
342+
}
343+
}
344+
345+
/**
346+
* Returns the command represented by the given {@code key}. Returns {@link #UNKNOWN} if no matching command could be
347+
* found.
348+
*
349+
* @param key
350+
* @return
351+
*/
352+
public static RedisCommand failsafeCommandLookup(String key) {
353+
354+
if (!StringUtils.hasText(key)) {
355+
return RedisCommand.UNKNOWN;
356+
}
357+
358+
RedisCommand cmd = commandLookup.get(key.toLowerCase());
359+
if (cmd != null) {
360+
return cmd;
361+
}
362+
363+
return RedisCommand.UNKNOWN;
364+
}
365+
}

0 commit comments

Comments
 (0)