項目已引入其他 Spring 依賴,為避免版本衝突,排除傳遞性依賴:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.2.RELEASE</version>
<exclusions>
<exclusion><artifactId>spring-context</artifactId><groupId>org.springframework</groupId></exclusion>
<exclusion><artifactId>spring-aop</artifactId><groupId>org.springframework</groupId></exclusion>
<exclusion><artifactId>spring-context-support</artifactId><groupId>org.springframework</groupId></exclusion>
<exclusion><artifactId>spring-tx</artifactId><groupId>org.springframework</groupId></exclusion>
<exclusion><artifactId>spring-core</artifactId><groupId>org.springframework</groupId></exclusion>
<exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion>
</exclusions>
</dependency>
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxActive" value="${redis.maxActive}" />
<property name="maxWait" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:hostName="${redis.url}" p:port="${redis.port}"
p:password="${redis.password}" p:pool-config-ref="poolConfig"/>
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/> <bean id="jsonSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
Key/HashKey 使用 String 序列化,Value/HashValue 使用 JSON 序列化,兼容可讀性與複雜對象存儲。
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:valueSerializer-ref="jsonSerializer"
p:hashValueSerializer-ref="jsonSerializer"/>
<bean id="updateAvailableSavingsCard" class="org.springframework.data.redis.core.script.DefaultRedisScript">
<property name="location" value="classpath:META-INF/lua/updateAvailableSavingsCard.lua"/>
<property name="resultType" value="org.meibaobao.ecos.basecomponent.common.Result"/>
</bean>
<bean id="initAvailableSavingsCard" class="org.springframework.data.redis.core.script.DefaultRedisScript">
<property name="location" value="classpath:META-INF/lua/initAvailableSavingsCard.lua"/>
<property name="resultType" value="java.lang.Boolean"/>
</bean>
-- updateAvailableSavingsCard.lua
redis.call('INCRBY', KEYS[1], ARGV[1])
local current = redis.call('GET', KEYS[1])
local result = {}
result["@class"] = "org.meibaobao.ecos.basecomponent.common.Result"
if tonumber(current) < 0 then
redis.call('DECRBY', KEYS[1], ARGV[1])
current = redis.call('GET', KEYS[1])
result["success"] = false
result["data"] = current
local encodestr = cjson.encode(result)
print(encodestr)
return encodestr
end
result["success"] = true
result["data"] = current
local encodestr = cjson.encode(result)
print(encodestr)
return encodestr
腳本邏輯:庫存增減後檢查是否小於 0,若是則回滾並返回失敗,否則返回成功。整個過程在 Redis 服務端原子執行,避免併發競態。
#redis redis.url=10.72.82.124 redis.port=6379 redis.password= redis.maxIdle=200 redis.maxActive=1024 redis.maxWait=1000 redis.testOnBorrow=true
@Service
public class StoredCardInventoryServiceImpl {
@Resource(name = "updateAvailableSavingsCard")
private RedisScript<Result> updateAvailableSavingsCard;
@Resource(name = "initAvailableSavingsCard")
private RedisScript<Boolean> initAvailableSavingsCard;
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
public static final String PREFIX = "availableStotredCard_";
public Result availableStotredCardInventory(String schemeNo, int num) {
return redisTemplate.execute(updateAvailableSavingsCard,
Collections.singletonList(PREFIX + schemeNo), num);
}
public Boolean initAvailableStotredCardInventory(String schemeNo, int num) {
return redisTemplate.execute(initAvailableSavingsCard,
Collections.singletonList(PREFIX + schemeNo), num);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:development/xml/spring/applicationContext.xml"})
public class StotredCardInventoryServiceImplTest {
@Resource private StoredCardInventoryServiceImpl service;
@Autowired private RedisTemplate<String, Object> redisTemplate;
@Test
public void test() throws InterruptedException {
System.out.println(service.initAvailableStotredCardInventory("test1111112", -10));
System.out.println(service.initAvailableStotredCardInventory("test1111112", -10));
redisTemplate.opsForValue().set("test99999", "123asd");
System.out.println(redisTemplate.opsForValue().get("test99999"));
// Hash 操作驗證
String key = "hashtest11";
redisTemplate.opsForHash().put(key, "name1", "11");
redisTemplate.opsForHash().put(key, "name2", "22");
redisTemplate.opsForHash().put(key, "name3", "33");
System.out.println(redisTemplate.opsForHash().get(key, "name2"));
redisTemplate.opsForHash().delete(key, "name2");
System.out.println(redisTemplate.opsForHash().get(key, "name2"));
System.out.println(redisTemplate.opsForHash().get(key, "name1"));
Map<Object, Object> opsMap = redisTemplate.opsForHash().entries(key);
if (opsMap != null) {
opsMap.forEach((k, v) -> System.out.println(k + "," + v));
}
}
}
通過 Spring Data Redis 的 redisTemplate 統一序列化策略,結合 DefaultRedisScript 載入 Lua 腳本,可實現複雜業務邏輯的原子化執行,有效解決高併發下的數據一致性問題。實戰中靈活運用 Hash、String 等數據結構,配合管道與事務,可進一步提升 Redis 操作效能。