文章目录
- 1. 数据库表
- 2. 更新MySQL数据并删除Redis缓存
- 3. 查询缓存中数据
- 4. 删除数据库中数据并删除缓存
- 5. 生成redis的key
- 6. 常量类 StorageConstants
- 7. 配置类 StorageConfig
- 8. 工具类 JsonStorage
- 9. 应用测试
1. 数据库表
CREATE TABLE `t_storage` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`nameSpace` varchar(64) NOT NULL COMMENT '隔离字段',
`groupId` varchar(128) NOT NULL COMMENT '分组,比如不同app',
`dataId` varchar(64) NOT NULL COMMENT '数据存储id',
`dataValue` mediumtext DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `nameSpace` (`nameSpace`,`groupId`,`dataId`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
向表中插入数据:
INSERT INTO `t_storage`(`id`, `nameSpace`, `groupId`, `dataId`, `dataValue`) VALUES (10, '_system', 'logsetting', 'thresholdConfig', '{\"threshold\":82,\"allSize\":\"47G\",\"usedSize\":\"0\",\"remainSize\":\"47G\",\"usingRate\":0,\"diskName\":null}');
INSERT INTO `t_storage`(`id`, `nameSpace`, `groupId`, `dataId`, `dataValue`) VALUES (34, '_system', 'logsetting', 'lruConfigFilepath', '{\"dsMongo\":null,\"dsFile\":null,\"dsEs\":null}');
INSERT INTO `t_storage`(`id`, `nameSpace`, `groupId`, `dataId`, `dataValue`) VALUES (45, '_system', 'auth', 'policy', '{\"passwordPeriod\":90,\"errorTimes\":5,\"lockTime\":5,\"minLength\":8}');
可以看到dataValue的值为对象序列化后的字符串;
2. 更新MySQL数据并删除Redis缓存
先更新数据库再删除缓存
@Service
@Slf4j
public class StorageDb implements IStorage {
@Autowired
private RedisUtils redisUtils;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private StorageConfig storageConfig;
@Override
@CheckParam
public void setDataValue(String nameSpace,String groupId,String dataId,String value
) throws StorageException {
// 同一nameSpace, groupId, dataId下数据不存在则新增,否则更新
String updateSql = String.format(
"insert into %s(nameSpace,groupId,dataId,dataValue) values (?,?,?,?) "
+ " on duplicate key update dataValue=values(dataValue)",
StorageConstants.TABLE_NAME
);
// 更新数据库表
int rows = jdbcTemplate.update(updateSql, nameSpace, groupId, dataId, value);
if (rows > 0) {
// 拼接redis的key
String keyName = generateKey(nameSpace, groupId, dataId);
// 更新完数据将Redis缓存删除
redisUtils.delete(keyName);
}
}
}
3. 查询缓存中数据
先从redis缓存中查询,当缓存中查询不到时,再查询数据库,查完数据库将数据设置到redis缓存,如果查询中查询的数据为null也要缓存,防止缓存穿透
@Service
@Slf4j
public class StorageDb implements IStorage {
@Autowired
private RedisUtils redisUtils;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private StorageConfig storageConfig;
@Override
@CheckParam
public String getDataValue( String nameSpace,String groupId,String dataId) {
// 拼接redis缓存key
String keyName = generateKey(nameSpace, groupId, dataId);
// 先从缓存中查询数据
String dataValue = redisUtils.getString(keyName);
// 缓存不存在则查询数据库
if (Objects.isNull(dataValue)) {
String sql = String.format("select dataValue from %s where nameSpace=? and groupId=? and dataId=?", StorageConstants.TABLE_NAME);
try {
dataValue = jdbcTemplate.queryForObject(sql, String.class, nameSpace, groupId, dataId);
} catch (DataAccessException e) {
log.error("Failed to query storage, nameSpace = {}, groupId = {}, dataId = {}, error {}", nameSpace, groupId, dataId, e);
}
// 查询完数据将其写入redis缓存,空值也缓存,防止缓存穿透
redisUtils.setWithExpired(keyName, StringUtils.isEmpty(dataValue) ? "" : dataValue, storageConfig.getExpiredTime(), TimeUnit.SECONDS);
}
// 只返回dataValue
return dataValue;
}
}
4. 删除数据库中数据并删除缓存
当数据库中数据删除成功后,删除缓存中的数据
@Service
@Slf4j
public class StorageDb implements IStorage {
@Autowired
private RedisUtils redisUtils;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private StorageConfig storageConfig;
@Override
@CheckParam
public void deleteData(String nameSpace,String groupId,String dataId
) {
String sql = String.format("delete from %s where nameSpace=? and groupId=? and dataId=?", StorageConstants.TABLE_NAME);
int rows = jdbcTemplate.update(sql, nameSpace, groupId, dataId);
// 数据库删除成功则删掉redis缓存中的数据
if (rows > 0) {
redisUtils.delete(generateKey(nameSpace, groupId, dataId));
}
}
}
5. 生成redis的key
@Service
@Slf4j
public class StorageDb implements IStorage {
/**
* 生成rediskey
*/
private String generateKey(String... fields) {
return StorageConstants.REDIS_KEY_PREFIX
.concat(String.join(StorageConstants.SEPARATOR, fields));
}
}
6. 常量类 StorageConstants
public interface StorageConstants {
/**
* 数据表名
*/
String TABLE_NAME = "t_storage";
/**
* redis命名前缀
*/
String REDIS_KEY_PREFIX = "ngsoc:storage";
/**
* redis key分隔符
*/
String SEPARATOR = ":";
/**
* redis数据过期时间
*/
long EXPIRED_TIME = 30;
}
7. 配置类 StorageConfig
@Data
@Configuration
@ConfigurationProperties(prefix = "storage.storage")
public class StorageConfig {
/**
* Redis过期时间
*/
private Integer expiredTime;
}
storage:
storage:
expired_time: 30 #redis过期时间
batch_save_size: 5000 #批量插入大小
8. 工具类 JsonStorage
因为dataValue的值为对象序列化之后的字符串,因此需要写一个工具类,在参数为对象时将其序列化为json字符串存入数据库表,在从数据库表中取出数据时将dataValue反序列化为对象;
@Service
@Slf4j
public class JsonStorage {
private IStorage storage;
@Autowired
private void setStorage(IStorage storage) {
this.storage = storage;
}
private final ObjectMapper JSON_MAPPER = new ObjectMapper();
/**
* 通过实体对象设置Json字符串
*/
public boolean setJsonDataWithEntity(String nameSpace, String groupId, String key, Object entity) {
try {
// 将对象序列化为json字符串,并存入数据库和redis缓存中
String jsonString = JSON_MAPPER.writeValueAsString(entity);
storage.setDataValue(nameSpace, groupId, key, jsonString);
return true;
} catch (JsonProcessingException | StorageException e) {
log.error("Storage setValue failed, error: {}", e);
return false;
}
}
/**
* 获取由JsonString转化后的实体对象
*/
public <T> T getJsonDataAsEntity(String nameSpace, String groupId, String key, Class<T> classType) throws StorageException {
try {
// 获取dataValue
String jsonString = storage.getDataValue(nameSpace, groupId, key);
if (!StringUtils.isEmpty(jsonString)) {
// 不匹配字段忽略
JSON_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 将dataValue反序列化为实体对象并返回,用泛型T接收
return JSON_MAPPER.readValue(jsonString, classType);
} else {
log.error("Storage get value failed, value is empty");
throw new StorageException("storage.value.empty");
}
} catch (JsonProcessingException e) {
log.error("Storage get value failed, jsonString format error", e);
throw new StorageException("storage.getValue.format.error");
}
}
}
9. 应用测试
密码策略实体类 PasswordPolicyEntity
@ApiModel
@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class PasswordPolicyEntity {
/**
* 密码周期
*/
@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
@ApiModelProperty(value = "密码周期", required = true)
private Integer passwordPeriod = 90;
/**
* 登录密码输错次数限制
*/
@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
@ApiModelProperty(value = "登录密码输错次数限制", required = true)
private Integer errorTimes = 5;
/**
* 密码输入错误达到限制后锁定锁定时间
*/
@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
@ApiModelProperty(value = "密码输入错误达到限制后锁定锁定时间", required = true)
private Integer lockTime = 5;
/**
* 密码最小长度
*/
@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
@ApiModelProperty(value = "密码最小长度", required = true)
private Integer minLength = 8;
}
读取密码策略 StorageApplicationTest
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class StorageApplicationTest {
@Autowired
private JsonStorage jsonStorage;
private final ReentrantReadWriteLock.ReadLock readLock = new ReentrantReadWriteLock().readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock().writeLock();
@Test
public void test() throws StorageException {
PasswordPolicyEntity passwordPolicyEntity = null;
readLock.lock();
try {
passwordPolicyEntity = jsonStorage.getJsonDataAsEntity(
"_system", "auth", "policy", PasswordPolicyEntity.class);
} catch (StorageException e) {
log.error("Error read pwd policy", e);
} finally {
readLock.unlock();
}
System.out.println("passwordPolicyEntity = " + passwordPolicyEntity);
}
}