I am trying to configure TTL on RedisHash. I want to set same expiry to all the keys.
1st: I tried by adding annotation #RedisHash(value="MyHash",timeToLive=60) on the entity class.
2nd: Add a new field as expiration with #TimetoLive along with #RedisHash(value="MyHash",timeToLive=60)
#RedisHash(value = "MyHash", timeToLive = 60L)
public class MyHash {
.../attributes with few indexes
#TimeToLive
private Long expiration;
}
3rd: Added #EnableRedisRepositories with KeyspaceConfiguration
#EnableRedisRepositories(basePackageClasses = MyHash.class, keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class RedisConfig {
//LettuceConnectionFactory
//RedisTemplate
}
public class MyKeyspaceConfiguration extends KeyspaceConfiguration {
#Override
public boolean hasSettingsFor(Class<?> type) {
return true;
}
#Override
public KeyspaceSettings getKeyspaceSettings(Class<?> type) {
KeyspaceSettings keyspaceSettings = new KeyspaceSettings(MyHash.class, "MyHashlog");
keyspaceSettings.setTimeToLive(60L);
return keyspaceSettings;
}
}
My Repository:
public interface MyHashRepository extends CrudRepository<MyHash, Long> {
List<MyHash> findByApplicationId(String applicationId) ;
}
All above approach do not set any expiry. When I check in Redis it shows -1.
TTL MyHash:applicationId:e1hd9-w6q0s-5jd3e-wi2h4
(integer) -1
Found the solution:
We need to add enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP attribute as below,
Solution 1: Using #RedisHash to set TTL
//Add annotation on config or Spring boot main class
#EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP)
#SpringBootApplication
public class RedisLogServiceApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(RedisLogServiceApplication.class, args);
}
}
#RedisHash(value = "MyHash", timeToLive = 60L)
public class MyHash {
.../attributes with few indexes
#Id
private Long id;
#Indexed
private String applicationId;
}
Solution 2: Use KeySapceConfiguration for setting TTL
//Add annotation on config or Spring boot main class
#EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP, keyspaceConfiguration = MyKeyspaceConfiguration.class)
#SpringBootApplication
public class RedisLogServiceApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(RedisLogServiceApplication.class, args);
}
}
//MyKeyspaceConfiguration.class to set TTL
public class MyKeyspaceConfiguration extends KeyspaceConfiguration {
#Override
public boolean hasSettingsFor(Class<?> type) {
return true;
}
#Override
public KeyspaceSettings getKeyspaceSettings(Class<?> type) {
KeyspaceSettings keyspaceSettings = new KeyspaceSettings(MyHash.class, "MyHashlog");
keyspaceSettings.setTimeToLive(60L);
return keyspaceSettings;
}
}
#RedisHash(value = "MyHash")
public class MyHash {
.../attributes with few indexes
#Id
private Long id;
#Indexed
private String applicationId;
}
No changes to Repository:
public interface MyHashRepository extends CrudRepository<MyHash, Long> {
List<MyHash> findByApplicationId(String applicationId) ;
}
Also Spring will create multiple keys based on the attributes that are annotated as #Indexed in your entity class. However, TTL is only applied on the primary key i.e. #Id. For example,
When I run keys command in redis-cli
>keys MyHash*
1) MyHash:id:e1hd9-w6q0s-5jd3e-wi2h4
2) MyHash:applicationId:e1hd9-w6q0s-5jd3e-wi2h4
>TTL MyHash:id:e1hd9-w6q0s-5jd3e-wi2h4
(integer) 59
>TTL MyHash:applicationId:e1hd9-w6q0s-5jd3e-wi2h4
(integer) -1
//After Expiry:
>keys MyHash*
(no keys)
>TTL MyHash:id:e1hd9-w6q0s-5jd3e-wi2h4
(integer) -2
I was able to solve this two ways:
Solution 1:
Adding the #EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600) to my configuration class
#Configuration
#EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3000)
open class RedisConfig{}
Solution 2:
Extending the RedisHttpSessionConfiguration class and calling setMaxInactiveIntervalInSeconds
#Configuration
open class RedisConfig: RedisHttpSessionConfiguration() {
#PostConstruct
open fun afterPropertiesSet() {
val ttlInSeconds = 3000
this.setMaxInactiveIntervalInSeconds(ttlInSeconds)
}
I opted for the latter so that I can eventually add the ttl as a config value.
Related
My objectmapper not working when I use spring controller & class for requestbody inheritation .
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type", visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(value = RecipeVersion.class, name = "recipe"),
#JsonSubTypes.Type(value = DietVersion.class, name = "diet"),
})
public interface DocumentVersion {
Info getInfo();
void setInfo(Info info);
}
and also
#Data
public class DietVersion implements DocumentVersion {
private LocalizedText warnings;
private List<DietDay> days = new LinkedList<>();
private Info info = new Info();
private String getType() {
return "diet";
}
}
Ok. I have BaseController for diets and recipes
abstract public class BaseController<T extends Document<V>, V extends DocumentVersion> {
abstract protected BaseService<T, V> getService();
#PostMapping("/{docId}/version/last")
#ResponseStatus(HttpStatus.NO_CONTENT)
public void saveVersion(#PathVariable("docId") String docId, #RequestBody V version, Authentication authentication) {
getService().replaceLastVersion(docId, version, authentication);
}
}
and some realizations. example for diet
#Controller
#RequestMapping("/diet")
public class DietController extends BaseController<Diet, DietVersion> {
private final DietService dietService;
#Autowired
public DietController(DietService dietService) {
this.dietService = dietService;
}
#Override
protected DietService getService() {
return dietService;
}
#Override
public void saveVersion(String docId, DietVersion version, Authentication authentication) {
super.saveVersion(docId, version, authentication);
}
}
But when I send json with info, days, type ('diet') to '/diet/1/version/last' then I see in debug mode that my DietVersion pure clear and has no any data. Why ?
How to change settings for objectmapper ?
what if you provide all this in your DietController class.
public void saveVersion(#PathVariable("docId") String docId, #RequestBody V version, Authentication authentication){
I am using Spring Boot to return data from a Repository. I would like to format my JSON so that it plays nicely with ExtJS' ajax handling. Essentially I would like to include properties to handle success/failure, count, and errorMsg along with a List of data from the repository.
I have tried by creating a ResponseDTO object that I'm returning from my Rest Controller.
#RestController
public class AdminController {
private static final Logger logger = LogManager.getLogger(AdminController.class);
#Autowired
private UserService userService;
#Autowired
private SecurityService securityService;
#Autowired
private UserValidator userValidator;
#GetMapping("/searchUsers")
public ResponseDTO searchUsers(String name, String active) {
int activeFlag;
List<User> users;
ResponseDTO resp;
if(active.equals("true")) {
activeFlag = 1;
} else activeFlag=0;
if(StringUtils.isEmpty(name)) {
users= userService.findAllUsers(activeFlag);
} else {
users= userService.findByUsernameActive(name, activeFlag);
}
return new ResponseDTO(users, true);
}
}
Here's my DTO that I use in the controller:
public class ResponseDTO {
private boolean success;
private int count = 0;
private List<?> values;
public boolean getSuccess() {
return this.success;
}
public void setState(boolean st) {
this.success=st;
}
public int getCount() {
return this.count;
}
public void setCount(int cnt) {
this.count=cnt;
}
public List<?>getValues() {
return this.values;
}
public void setValues(List<?> vals) {
this.values = vals;
}
public ResponseDTO(List<?> items, boolean state) {
this.success = state;
values = items;
this.count = items.size();
}
}
Here's what the JSON I get back looks like:
{
"ResponseDTO": {
"success":true,
"count":2,
"values":[{obj1 } , { obj2}]
}
}
what I would like to get is something more like:
{
"success" : true,
"count" : 2,
"values" [{obj1},{obj2}]
}
I'm using Spring Boot and Jackson annotations. I have used an annotation to ignore individual fields in the objects in the results array, but I can't find a way to unwrap the ResponseDTO object to not include the class name.
When you serialize ResponseDTO POJO, you should not get 'ResponseDTO' in the response by default. Because, the root wrap feature is disabled by default. See the doc here. If you have the below code, please remove it.
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
I am trying to convert / serialize the #Id field (which is not a string) of the model class but keep getting this error. The custom ID class just concatenates two values with a colon, e.g. aaaa:2345.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [MyIdClass] to type [byte[]]
This is my model class.
#RedisHash("alert")
public class MyClass implements Serializable
{
public static class MyIdClass
{
public String userId;
public Long sessionExpiry;
public MyIdClass()
{
}
public MyIdClass(String id, Long ex)
{
userId = id;
sessionExpiry = ex;
}
}
public static class MyIdClassSerializer implements RedisSerializer<MyIdClass>
{
#Nullable
#Override
public byte[] serialize(#Nullable MyIdClass uid) throws SerializationException
{
return String.format("%s:%d", uid.userId, uid.sessionExpiry).getBytes();
}
#Nullable
#Override
public MyIdClass deserialize(#Nullable byte[] bytes) throws SerializationException
{
String[] t = new String(bytes).split(":");
return new MyIdClass(t[0], Long.parseLong(t[1]));
}
}
#Component
#ReadingConverter
public static class MyIdClassReader implements Converter<byte[], MyIdClass>
{
#Nullable
#Override
public MyIdClass convert(byte[] source)
{
String[] t = new String(source).split(":");
return new MyIdClass(t[0], Long.parseLong(t[1]));
}
}
#Component
#WritingConverter
public static class MyIdClassWriter implements Converter<MyIdClass, byte[]>
{
#Nullable
#Override
public byte[] convert(MyIdClass uid)
{
return String.format("%s:%d", uid.userId, uid.sessionExpiry).getBytes();
}
}
/**
* User ID
* Key := 'alert:' userId ':' sessionExpiry
*/
#Id
public MyIdClass id;
public String value;
}
Here's how I'm configuring the RedisTemplate.
#Bean("redisTemplateActivityAlert")
public RedisTemplate<ActivityAlert.UserIdExpiry, ActivityAlert> redisTemplateActivityAlert()
{
RedisTemplate<ActivityAlert.UserIdExpiry, ActivityAlert> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new ActivityAlert.UserIdExpirySerializer());
template.setHashKeySerializer(new ActivityAlert.UserIdExpirySerializer());
return template;
}
I've read other posts about configuring ConversionService or TypeConverter but haven't gone far with them.
add this Bean to your RedisConfiguration :
#Bean
public RedisCustomConversions redisCustomConversions(MyIdClassReader myIdClassReader , MyIdClassWriter myIdClassWriter ) {
return new RedisCustomConversions(Arrays.asList(myIdClassWriter,myIdClassReader));
}
How to fetch data from corda Custom tables?
my sample code is as follows :-
Api layer -- getIous() method
{
Field attributeValue=IOUSchemaV1.PersistentIOU.class.getDeclaredField("value");
CriteriaExpression currencyIndex = Builder.equal(attributeValue, "12");
QueryCriteria.VaultCustomQueryCriteria criteria = new
QueryCriteria.VaultCustomQueryCriteria(currencyIndex);
vaultStates = services.vaultQueryByCriteria(criteria,IOUState.class);
}
In ExamplePlugin I added below code for schema registration
public class ExamplePlugin extends CordaPluginRegistry implements
WebServerPluginRegistry
{
#NotNull
#Override
public Set<MappedSchema> getRequiredSchemas()
{
Set<MappedSchema> requiredSchemas = new HashSet<>();
requiredSchemas.add(new IOUSchemaV1());
return requiredSchemas;
}
}
My Schema classes are ---
public final class IOUSchema {
}
#CordaSerializable
public class IOUSchemaV1 extends MappedSchema
{
public IOUSchemaV1() {
super(IOUSchema.class, 1, ImmutableList.of(PersistentIOU.class));
}
#Entity
#Table(name = "iou_states")
public static class PersistentIOU extends PersistentState {
#Column(name = "sender_name") private final String senderName;
#Column(name = "recipient_name") private final String recipientName;
#Column(name = "value") private final int value;
public PersistentIOU(String senderName, String recipientName, int value) {
this.senderName = senderName;
this.recipientName = recipientName;
this.value = value;
}
public String getSenderName() {
return senderName;
}
public String getRecipientName() {
return recipientName;
}
public int getValue() {
return value;
}
}
}
my state has :-
public class IOUState implements LinearState, QueryableState
{
--- some code goes here and below methods as well.---
#Override
public PersistentState generateMappedObject(MappedSchema schema) {
if (schema instanceof IOUSchemaV1) {
return new IOUSchemaV1.PersistentIOU(
this.sender.getName().toString(),
this.recipient.getName().toString(),
this.iou.getValue());
} else {
throw new IllegalArgumentException("Unrecognised schema $schema");
}
}
#Override
public Iterable<MappedSchema> supportedSchemas() {
return ImmutableList.of(new IOUSchemaV1());
}
}
But all the time i am getting below exception.
Caused by: net.corda.core.node.services.VaultQueryException:
Please register the entity 'com.example.schema.IOUSchemaV1' class in your CorDapp's CordaPluginRegistry configuration (requiredSchemas attribute)
and ensure you have declared (in supportedSchemas()) and mapped (in generateMappedObject())
the schema in the associated contract state's QueryableState interface implementation.
Can anyone please help to resolve this.
Try deleting implements WebServerPluginRegistry from your plugin declaration.
My Java Class is
public class User {
private List<UserInfo> userInfoList;
public class UserInfo {
private String id;
}
}
Let's assume it has getter, setter method.
json is
{"userInfoList" : [{"id":"a", "id":"b"}]}
I tried to deserialize it like below.
objectMapper.readValue(json, User.class);
But it throws error.
Can not construct instance of User$UserInfoList: no suitable constructor found
How to deserialize it?
I think you should make UserInfo static. Jackson cannot construct the UserInfo class.
I tried with that change and it works for me :
public class User {
private List<UserInfo> userInfoList;
public static class UserInfo {
private String id;
public UserInfo() {
super();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public List<UserInfo> getUserInfoList() {
return userInfoList;
}
public void setUserInfoList(List<UserInfo> userInfoList) {
this.userInfoList = userInfoList;
}
}
And :
public class App {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
User.UserInfo ui1 = new User.UserInfo();
ui1.setId("a");
User.UserInfo ui2 = new User.UserInfo();
ui2.setId("b");
List<User.UserInfo> userInfoList = new ArrayList<User.UserInfo>();
userInfoList.add(ui1);
userInfoList.add(ui2);
User user = new User();
user.setUserInfoList(userInfoList);
System.out.println(mapper.writeValueAsString(user));
user = mapper.readValue(mapper.writeValueAsString(user), User.class);
}
}