- Redis Version: 7.2.1
- Installed Modules: RediSearch v.2.4.15, RedisJSON v.99.99.99
- Number of nodes: xx.x.x.xxx:6379, xx.x.x.xxx:6380, xx.x.x.xxx:6381, xx.x.x.xxx:6382, xx.x.x.xxx:6383, xx.x.x.xxx:6384
pom.xml
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.2.3</version></dependency>
Configuration
@Beanpublic JedisCluster getRedisCluster() { Set<HostAndPort> jedisClusterNode = new HashSet<>(); String[] nodes = {"xx.x.x.xxx:6379", "xx.x.x.xxx:6380", "xx.x.x.xxx:6381", "xx.x.x.xxx:6382", "xx.x.x.xxx:6383", "xx.x.x.xxx:6384"}; for (int i = 0; i < nodes.length; i++) { String[] ipAndPort = nodes[i].split(":"); jedisClusterNode.add(new HostAndPort(ipAndPort[0], Integer.valueOf(ipAndPort[1]))); } JedisCluster jc = new JedisCluster(jedisClusterNode, "default", "admin"); logger.debug("Redis(FT) connection Successfully."); return jc;}
BookDataSearchIndex.java
@Component@Order(1)public class BookDataSearchIndex implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(BookDataSearchIndex.class); @Autowired private UnifiedJedis jedis; @Override public void run(String... args) throws Exception { try { Schema schema = new Schema() .addField(new Schema.Field(FieldName.of("$.bookId").as("BOOKID"), Schema.FieldType.TEXT, false, false)) .addField(new Schema.Field(FieldName.of("$.title").as("TITLE"), Schema.FieldType.TEXT, false, false)) .addField(new Schema.Field(FieldName.of("$.price").as("PRICE"), Schema.FieldType.NUMERIC, true, false)); IndexDefinition indexDefinition = new IndexDefinition(IndexDefinition.Type.JSON) .setPrefixes("book:"); jedis.ftCreate("bookdata-idx", IndexOptions.defaultOptions().setDefinition(indexDefinition), schema); } catch (Exception e) { logger.debug("Inside run in BookDataSearchIndex : {}", e.getMessage()); } }}
BookData.java
public class BookData { private String bookId; private String title; private Long price; // Setter & Getter}
Page.java
public class Page<T> { private List<T> data; private Integer totalPage; private Integer currentPage; private Long total; public Page(List<T> data, Integer totalPage, Integer currentPage, Long total) { super(); this.data = data; this.totalPage = totalPage; this.currentPage = currentPage; this.total = total; } // Setter & Getter}
BookDataHelper.java
import java.lang.reflect.Field;import java.util.ArrayList;import java.util.Calendar;import java.util.Date;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import java.util.stream.Collectors;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import com.google.gson.Gson;import redis.clients.jedis.UnifiedJedis;import redis.clients.jedis.search.Document;import redis.clients.jedis.search.Query;import redis.clients.jedis.search.SearchResult;public class BookDataHelper { private static final String ATTHERATE = "@"; private static final String LESS_THEN = "lt"; private static final String GREATER_THEN_EQUAL = "gte"; private static final String GREATER_THEN = "gt"; private static final String LESS_THEN_EQUAL = "lte"; private static final String BETWEEN = "between"; private static final Logger logger = LoggerFactory.getLogger(BookDataHelper.class); @Autowired private UnifiedJedis jedis; public BookData save(BookData data) { String memberKey = "book:"+ data.getBookId(); Gson gson = new Gson(); jedis.jsonSet(getKey(memberKey), gson.toJson(data)); jedis.sadd(getKey("bookdata"), getKey(memberKey)); return data; } public BookData findByKey(String index, String key, Object value, Class<BookData> dto) { Map<String, Object> fields = new HashMap<>(); fields.put(key, value); List<BookData> t = search(index, fields, dto); if (!t.isEmpty()) { return t.get(0); } return null; } public List<BookData> search(String index, Map<String, Object> fields, Class<BookData> dto) { String queryCriteria = buildQuery(dto, fields, null); return buildResponse(index, queryCriteria, dto); } public Page<BookData> search(String index, String queryCriteria, Integer offset, Integer limit, Class<BookData> dto) { Query query = null; if (queryCriteria.isEmpty()) { query = new Query(); } else { query = new Query(queryCriteria); } query.limit(offset, limit); SearchResult searchResult = jedis.ftSearch(index, query); Long total = searchResult.getTotalResults(); int totalPage = (int) Math.ceil((double) total / limit); List<BookData> orderDataList = searchResult.getDocuments().stream() .map(document -> convertDocumentToModel(document, dto)).collect(Collectors.toList()); return new Page<>(orderDataList, totalPage, offset, total); } //////////////////////////////////////////////////////////////////////////////////////////////////////// //////// PRIVATE METHODS //////// //////////////////////////////////////////////////////////////////////////////////////////////////////// private List<BookData> buildResponse(String index, String queryCriteria, Class<BookData> dto) { int offset = 0; int limit = 10; boolean done = false; List<BookData> result = new ArrayList<>(); while (!done) { Page<BookData> pageResult = search(index, queryCriteria,offset, limit, dto); result.addAll(pageResult.getData()); // Check if there are more pages of results if (pageResult.getData().isEmpty() || pageResult.getTotal() < limit) { done = true; } else { offset += limit; } } return result; } private static synchronized String buildQuery(Class<?> dto, Map<String, Object> fields, Map<String, String> operators) { StringBuilder queryBuilder = new StringBuilder(); List<String> entityNumberTypeFields = getNumberFields(dto); for (Entry<String, Object> entry : fields.entrySet()) { String fieldName = entry.getKey().trim().toUpperCase(); Object fieldValue = toValue(entry.getValue()); if (null != operators && operators.containsKey(entry.getKey().trim())) { String operator = operators.get(entry.getKey().trim()).trim(); buildOperatorsQuery(queryBuilder, operator, fieldName, fieldValue, entry.getValue()); } else { if(entityNumberTypeFields.contains(fieldName)) { queryBuilder.append(ATTHERATE).append(fieldName).append(":[").append(fieldValue).append(",") .append(fieldValue).append("]").append(" "); } else { queryBuilder.append(ATTHERATE).append(fieldName).append(":").append(fieldValue).append(" "); } } } return queryBuilder.toString(); } private static synchronized void buildOperatorsQuery(StringBuilder buildQuery, String operator, String fieldName, Object fieldValue, Object originFieldValue) { if (GREATER_THEN.equalsIgnoreCase(operator)) { buildQuery.append(ATTHERATE).append(fieldName).append(":[") .append(getIncrementalVal(originFieldValue)).append(" > ").append(Integer.MAX_VALUE) .append("]").append(" "); } else if (LESS_THEN.equalsIgnoreCase(operator)) { buildQuery.append(ATTHERATE).append(fieldName).append(":[").append(Integer.MIN_VALUE) .append(" < ").append(getDecrementalVal(originFieldValue)).append("]").append(" "); } else if (GREATER_THEN_EQUAL.equalsIgnoreCase(operator)) { buildQuery.append(ATTHERATE).append(fieldName).append(":[").append(fieldValue).append(" > ") .append(Integer.MAX_VALUE).append("]").append(" "); } else if (LESS_THEN_EQUAL.equalsIgnoreCase(operator)) { buildQuery.append(ATTHERATE).append(fieldName).append(":[").append(Integer.MIN_VALUE) .append(" < ").append(toValue(fieldValue)).append("]").append(" "); } else if (BETWEEN.equalsIgnoreCase(operator) && originFieldValue instanceof List) { List<?> range = (List<?>) originFieldValue; if (range.size() == 2) { buildQuery.append(ATTHERATE).append(fieldName).append(":[").append(toValue(range.get(0))).append(",") .append(toValue(range.get(1))).append("]").append(" "); } else { throw new IllegalArgumentException("Invalid range for 'between' operator"); } } else { buildQuery.append(ATTHERATE).append(fieldName).append(":").append(fieldValue).append(" "); } } private static String getKey(String key) { return key.replace("-", "").replace("_", ""); } public static <T> T convertDocumentToModel(Document document, Class<T> model) { Gson gson = new Gson(); String jsonDoc = document.getProperties().iterator().next().getValue().toString(); return gson.fromJson(jsonDoc, model); } public static List<String> getNumberFields(Class<?> obj) { List<String> fieldList = new ArrayList<>(); try { Class<?> clazz = obj.newInstance().getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (isNumberType(field.getType())) { field.setAccessible(true); // Make the field accessible fieldList.add(field.getName().toUpperCase()); } } } catch (InstantiationException | IllegalAccessException e) { logger.error("Error while find number fields. {}", e); } return fieldList; } private static boolean isNumberType(Class<?> fieldType) { return fieldType == int.class || fieldType == Integer.class || fieldType == long.class || fieldType == Long.class || fieldType == short.class || fieldType == Short.class || fieldType == byte.class || fieldType == Byte.class; } public static Object toValue(Object value) { try { if(value instanceof Date) { Date date = (Date) value; return date.getTime(); } else if(value instanceof String) { return value.toString().trim().replace("-", "*").replace("_", "*"); } } catch (Exception e) { logger.error("Error in toValue while parsing value. {}", e); } return value; } public static Object getIncrementalVal(Object value) { try { if(value instanceof Integer || value instanceof Long || value instanceof String) { long val = Long.parseLong((String) value.toString()); return val + 1; } else if(value instanceof Double || value instanceof Float) { double val = Double.parseDouble((String) value.toString()); return val + 0.1; } else if(value instanceof Date) { Date date = (Date) value; Calendar calendar = Calendar.getInstance(); calendar.setTime(date); // Add one day calendar.add(Calendar.DAY_OF_YEAR, 1); return calendar.getTime().getTime(); } } catch (Exception e) { logger.error("Error in getIncrementalVal while parsing value. {}", e); } return value; } public static Object getDecrementalVal(Object value) { try { if(value instanceof Integer || value instanceof Long || value instanceof String) { long val = Long.parseLong((String) value.toString()); return val - 1; } else if(value instanceof Double || value instanceof Float) { double val = Double.parseDouble((String) value.toString()); return val - 0.1; } else if(value instanceof Date) { Date date = (Date) value; Calendar calendar = Calendar.getInstance(); calendar.setTime(date); // Subtract one day calendar.add(Calendar.DAY_OF_YEAR, -1); return calendar.getTime().getTime(); } } catch (Exception e) { logger.error("Error in getDecrementalVal while parsing value. {}", e); } return value; }}
Save below data by calling BookDataHelper.save method.
bookId = "HFDP-1"title = "Head First Design Patterns"price = 200
Retrieve data by calling BookDataHelper.findByKey method.
BookDataHelper.findByKey("bookdata-idx", "bookId", "HFDP-1", BookData.class);
Unfortunately getting no data result, Also tried with CLI by below command.
FT.SEARCH bookdata-idx @BOOKID:HFDP*1