Feature Enrichment with Spring Data Redis and Lettuce
Lettuce by far is the best Redis client out there for Java. In the java world, Annotations and Mappings are pretty popular. I build services using these concepts before however they were always under the service boundaries, never as shared libraries in order to avoid coupling. So today I record a video showing how we can use this feature and also extend the functionality to add custom enriched behavior. We will be using Spring Boot 2, Spring Data Redis, and Lettuce as redis Client. We also will need to have redis running either as standalone / cluster or running on docker. We will create custom annotations in order to describe the behavior we want. I need to say that some people love and other people hate annotations, I have mixed feelings. Often for those that don't like it - check this out. Besides that, let's take a look. So Let's get started.
Video
Code
Model
Our model is pretty simple. Simple Person POJO with Getters/Setters, toString, equals/hashCode all generated by Idea. You can see there is a custom annotation called UpperCase. The idea is to add custom features enrichment and make sure some fields are stored in the Upper case.
Annotation
Now let's look at the annotation.
Custom Repository
Here is where the magic happens. I wrapped all CurdRepository operations and I'm delegating to the proper CrudRepository implementation at the end of the day. However I', also I have a reflection code that adds the feature enrichment I want based on the existence of the UpperCase annotation at the Entity fields.
The complete code is here.
Cheers,
Diego Pacheco
Video
Code
Model
Our model is pretty simple. Simple Person POJO with Getters/Setters, toString, equals/hashCode all generated by Idea. You can see there is a custom annotation called UpperCase. The idea is to add custom features enrichment and make sure some fields are stored in the Upper case.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@RedisHash | |
public class Person { | |
private String id; | |
@UpperCase | |
private String name; | |
@UpperCase | |
private String mail; | |
public Person() {} | |
public Person(String name, String id, String mail) { | |
this.name = name; | |
this.id = id; | |
this.mail = mail; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public String getId() { | |
return id; | |
} | |
public void setId(String id) { | |
this.id = id; | |
} | |
public String getMail() { | |
return mail; | |
} | |
public void setMail(String mail) { | |
this.mail = mail; | |
} | |
@Override | |
public String toString() { | |
return "Person{" + | |
"name='" + name + '\'' + | |
", id='" + id + '\'' + | |
", mail='" + mail + '\'' + | |
'}'; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Person person = (Person) o; | |
return Objects.equals(id, person.id); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(id); | |
} | |
} |
Annotation
Now let's look at the annotation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Documented | |
@Retention(value = RetentionPolicy.RUNTIME) | |
public @interface UpperCase {} |
Custom Repository
Here is where the magic happens. I wrapped all CurdRepository operations and I'm delegating to the proper CrudRepository implementation at the end of the day. However I', also I have a reflection code that adds the feature enrichment I want based on the existence of the UpperCase annotation at the Entity fields.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Repository | |
public class RedisPersonRepository<T> { | |
@Autowired | |
private CrudRepository<T, String> repo; | |
public T save(T entity) { | |
try{ | |
for(Field f : entity.getClass().getDeclaredFields()){ | |
if (f.isAnnotationPresent(UpperCase.class)){ | |
Method getter = entity.getClass().getMethod("get" + toCamelCase(f.getName())); | |
Method setter = entity.getClass().getMethod("set" + toCamelCase(f.getName()), f.getType() ); | |
Object value = getter.invoke(entity); | |
if (null!=value) | |
setter.invoke(entity,value.toString().toUpperCase()); | |
} | |
} | |
return repo.save(entity); | |
}catch (Exception e){ | |
throw new RuntimeException(e); | |
} | |
} | |
private String toCamelCase(String name){ | |
return (name.charAt(0) + "").toUpperCase() + name.substring(1); | |
} | |
public Iterable<T> saveAll(Iterable<T> entities) { | |
return repo.saveAll(entities); | |
} | |
public Optional<T> findById(String s) { | |
return repo.findById(s); | |
} | |
public boolean existsById(String s) { | |
return repo.existsById(s); | |
} | |
public Iterable<T> findAll() { | |
return repo.findAll(); | |
} | |
public Iterable<T> findAllById(Iterable<String> strings) { | |
return repo.findAllById(strings); | |
} | |
public long count() { | |
return repo.count(); | |
} | |
public void deleteById(String s) { | |
repo.deleteById(s); | |
} | |
public void delete(T entity) { | |
repo.delete(entity); | |
} | |
public void deleteAll(Iterable<? extends T> entities) { | |
repo.deleteAll(entities); | |
} | |
public void deleteAll() { | |
repo.deleteAll(); | |
} | |
} |
The complete code is here.
Cheers,
Diego Pacheco