Redis Data Serialization and DTO Transformation Strategies

Storing Session Data as Hashes

When handling user authentication, it is common to store user session objects in Redis. Since StringRedisTemplate requires serialized values, we must convert our Data Transfer Objects (DTOs) into a format compatible with Redis storage structures, such as a Hash, while ensuring all field values are strings.

// Conversion utility: Transform entity to DTO
ProfileDTO userProfile = BeanUtil.copyProperties(entityUser, ProfileDTO.class);

// Convert DTO to Map for Redis Hash storage, ensuring all values are Strings
Map<String, String> sessionData = BeanUtil.beanToMap(userProfile, new HashMap<>(),
    CopyOptions.create()
        .setIgnoreNullValue(true)
        .setFieldValueEditor((fieldName, fieldValue) -> String.valueOf(fieldValue))
);

// Persist to Redis as a Hash
stringRedisTemplate.opsForHash().putAll(SESSION_KEY_PREFIX + authToken, sessionData);

Caching Database Entities as JSON

For simple object caching, a common approach involves serializing the database entity directly into a JSON string. This allows the entire object graph to be stored in a single Redis key-value pair.

// Serialize entity to JSON string and cache
stringRedisTemplate.opsForValue().set(cacheKey, JsonUtil.toJsonStr(shopEntity));

Retrieving and Ordering Data from Sorted Sets

This example demonstrates retrieving top-ranked items (e.g., "Top 5 Likes") from a Redis Sorted Set (ZSet). The challenge is to query the database using these IDs while preserving the order defined by the Redis score. Standard SQL IN clauses do not guarantee order, so we use the SQL FIELD function via MyBatis-Plus's last() method.

public Result queryTopLikers(Integer blogId) {
    String cacheKey = BLOG_LIKES_KEY + blogId;
    
    // Retrieve top 5 IDs from the sorted set (highest scores)
    Set<String> topIds = stringRedisTemplate.opsForZSet().reverseRange(cacheKey, 0, 4);
    
    if (topIds == null || topIds.isEmpty()) {
        return Result.ok(Collections.emptyList());
    }
    
    // Parse strings to Long
    List<Long> idList = topIds.stream().map(Long::valueOf).collect(Collectors.toList());
    
    // Create comma-separated string for SQL FIELD function
    String joinedIds = StrUtil.join(",", idList);
    
    // Query database, preserving the order of the input IDs
    List<UserDTO> userDTOs = userService.query()
            .in("id", idList)
            // Inject custom SQL to sort results based on the order in joinedIds
            .last("ORDER BY FIELD(id, " + joinedIds + ")") 
            .list()
            .stream()
            // Map Entity to DTO
            .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
            .collect(Collectors.toList());
            
    return Result.ok(userDTOs);
}

Understanding MyBatis-Plus last() and SQL FIELD

The last() method in MyBatis-Plus is a powerful tool that appends raw SQL fragments to the end of the generated query. It is useful for scenarios like custom pagination, dynamic ordering, or specific database functions not covered by the standard wrapper.

In the code above, FIELD(id, ...) is used to sort the query results. The FIELD function returns the index position of the first argument within the subsequent list of arguments. This ensures the database returns records in the exact order provided by the application logic derived from Redis.

Security Note: When using last(), ensure all inputs are sanitized to prevent SQL injection attacks.

Data Transformation with Java Streams

The code examples leverage Java Streams for efficient data processing. The Stream API allows for declarative manipulation of collections, such as filtering, mapping, and collecting.

In the context of the "Top Likers" example, the .map() operation is critical. It tranfsorms the stream of database entities into a stream of DTOs. Additionally, Collectors.toList() aggregates the stream elements back into a List.

Consider the following examples of Stream mapping operations:

// 1. Transforming Strings
List<String> names = Arrays.asList("alice", "bob", "charlie");
List<String> formattedNames = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
// Result: [ALICE, BOB, CHARLIE]

// 2. Transforming Types
List<Integer> values = Arrays.asList(10, 20, 30);
List<String> descriptions = values.stream()
    .map(v -> "Value: " + v)
    .collect(Collectors.toList());
// Result: [Value: 10, Value: 20, Value: 30]

// 3. Mathematical Operations
int[] numbers = {1, 2, 3, 4};
List<Integer> squares = Arrays.stream(numbers)
    .map(n -> n * n)
    .boxed()
    .collect(Collectors.toList());
// Result: [1, 4, 9, 16]

Tags: Redis mybatis-plus Java Streams DTO Mapping SQL Optimization

Posted on Fri, 07 Aug 2026 16:35:34 +0000 by tam2000k2