Skip to content

Commit ca34a10

Browse files
committed
fix: allow users to override Redis mapping context without bean conflicts (#637)
Added @ConditionalOnMissingBean to the default redisEnhancedMappingContext bean configuration, allowing users to provide their own custom mapping context with a custom keyspace resolver without encountering bean definition override issues. The fix maintains @primary on the default bean for backward compatibility while allowing users to override it by providing their own bean with the same name and @primary annotation. Example usage: ```java @bean(name = "redisEnhancedMappingContext") @primary public RedisEnhancedMappingContext customMappingContext() { RedisEnhancedMappingContext context = new RedisEnhancedMappingContext(); context.setKeySpaceResolver(type -> "tenant:prefix:" + type.getSimpleName()); return context; } ``` - Added @ConditionalOnMissingBean(name = "redisEnhancedMappingContext") - Maintained @primary for backward compatibility - Added comprehensive test demonstrating custom keyspace resolver - Verified backward compatibility with existing tests Closes #637
1 parent 7d50b9a commit ca34a10

File tree

2 files changed

+182
-1
lines changed

2 files changed

+182
-1
lines changed

redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,24 @@ public RedisModulesConfiguration() {
111111
private static final Log logger = LogFactory.getLog(RedisModulesConfiguration.class);
112112

113113
/**
114-
* Creates the primary Redis mapping context for enhanced entity mapping.
114+
* Creates the default Redis mapping context for enhanced entity mapping.
115115
* <p>
116116
* This mapping context provides metadata about Redis-mapped entities including
117117
* field information, type conversions, and persistence properties.
118+
* <p>
119+
* Users can override this by providing their own bean named "redisEnhancedMappingContext"
120+
* with @Primary annotation. The @ConditionalOnMissingBean ensures this default
121+
* bean is only created if users haven't provided their own.
118122
*
119123
* @return the enhanced mapping context instance
120124
*/
121125
@Bean(
122126
name = "redisEnhancedMappingContext"
123127
)
124128
@Primary
129+
@ConditionalOnMissingBean(
130+
name = "redisEnhancedMappingContext"
131+
)
125132
public RedisEnhancedMappingContext redisMappingContext() {
126133
return new RedisEnhancedMappingContext();
127134
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package com.redis.om.spring.issues;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.junit.jupiter.api.Assertions.*;
5+
6+
import java.util.List;
7+
import java.util.Optional;
8+
9+
import org.junit.jupiter.api.AfterEach;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
import org.springframework.beans.factory.annotation.Autowired;
13+
import org.springframework.boot.autoconfigure.SpringBootApplication;
14+
import org.springframework.boot.test.context.SpringBootTest;
15+
import org.springframework.context.annotation.Bean;
16+
import org.springframework.context.annotation.Configuration;
17+
import org.springframework.context.annotation.Primary;
18+
import org.springframework.data.geo.Point;
19+
import org.springframework.data.redis.core.RedisTemplate;
20+
import org.springframework.data.redis.core.mapping.RedisMappingContext;
21+
import org.springframework.test.annotation.DirtiesContext;
22+
import org.testcontainers.junit.jupiter.Testcontainers;
23+
24+
import com.google.gson.JsonObject;
25+
import com.redis.om.spring.AbstractBaseOMTest;
26+
import com.redis.om.spring.TestConfig;
27+
import com.redis.om.spring.annotations.EnableRedisDocumentRepositories;
28+
import com.redis.om.spring.fixtures.document.model.MyDoc;
29+
import com.redis.om.spring.fixtures.document.repository.MyDocRepository;
30+
import com.redis.om.spring.mapping.RedisEnhancedMappingContext;
31+
import com.redis.om.spring.ops.RedisModulesOperations;
32+
import com.redis.om.spring.ops.json.JSONOperations;
33+
import com.redis.om.spring.ops.search.SearchOperations;
34+
35+
/**
36+
* Test for issue #637: "Not able to override keyValueMappingContext"
37+
*
38+
* This test verifies that users can now provide their own RedisEnhancedMappingContext
39+
* bean with a custom keyspace resolver, without needing to set
40+
* spring.main.allow-bean-definition-overriding=true
41+
*
42+
* The fix removes @Primary and adds @ConditionalOnMissingBean to allow user overrides.
43+
*/
44+
@Testcontainers
45+
@DirtiesContext
46+
@SpringBootTest(classes = Issue637CustomMappingContextTest.Config.class)
47+
class Issue637CustomMappingContextTest extends AbstractBaseOMTest {
48+
49+
private static final String TENANT_PREFIX = "tenant_prod";
50+
51+
@Autowired
52+
MyDocRepository myDocRepository;
53+
54+
@Autowired
55+
RedisTemplate<String, String> template;
56+
57+
@Autowired
58+
RedisModulesOperations<String> modulesOperations;
59+
60+
@Autowired
61+
RedisMappingContext mappingContext;
62+
63+
String myDocId;
64+
65+
@BeforeEach
66+
void loadTestData() {
67+
Point point = new Point(-122.124500, 47.640160);
68+
MyDoc myDoc = MyDoc.of("issue 637 test", point, point, 1);
69+
myDoc = myDocRepository.save(myDoc);
70+
myDocId = myDoc.getId();
71+
}
72+
73+
@AfterEach
74+
void cleanUp() {
75+
myDocRepository.deleteAll();
76+
}
77+
78+
@Test
79+
void testIssue637_CustomMappingContextIsUsed() {
80+
// Verify our custom mapping context is being used
81+
assertThat(mappingContext).isInstanceOf(RedisEnhancedMappingContext.class);
82+
83+
// Verify the custom keyspace resolver is applied
84+
String keyspace = mappingContext.getKeySpaceResolver().resolveKeySpace(MyDoc.class);
85+
assertThat(keyspace).isEqualTo(TENANT_PREFIX + ":MyDoc");
86+
}
87+
88+
@Test
89+
void testIssue637_DocumentsUseCustomKeyspace() {
90+
// Verify the document was saved with custom keyspace
91+
JSONOperations<String> ops = modulesOperations.opsForJSON();
92+
93+
// The key should use our custom prefix
94+
String expectedKey = TENANT_PREFIX + ":MyDoc:" + myDocId;
95+
JsonObject rawJSON = ops.get(expectedKey, JsonObject.class);
96+
97+
assertNotNull(rawJSON, "Document should exist at custom keyspace");
98+
assertEquals(myDocId, rawJSON.get("id").getAsString());
99+
}
100+
101+
@Test
102+
void testIssue637_SearchIndexUsesCustomKeyspace() {
103+
SearchOperations<String> searchOps = modulesOperations.opsForSearch(MyDoc.class.getName() + "Idx");
104+
var info = searchOps.getInfo();
105+
106+
@SuppressWarnings("unchecked")
107+
var definition = (List<Object>) info.get("index_definition");
108+
assertNotNull(definition);
109+
110+
int prefixesIndex = definition.indexOf("prefixes");
111+
assertTrue(prefixesIndex >= 0, "Index definition should contain prefixes");
112+
113+
@SuppressWarnings("unchecked")
114+
var prefixes = (List<String>) definition.get(prefixesIndex + 1);
115+
assertNotNull(prefixes);
116+
assertEquals(1, prefixes.size());
117+
assertEquals(TENANT_PREFIX + ":MyDoc:", prefixes.get(0),
118+
"Index should use custom keyspace prefix");
119+
}
120+
121+
@Test
122+
void testIssue637_RepositoryOperationsWork() {
123+
// Test find by ID
124+
Optional<MyDoc> maybeDoc = myDocRepository.findById(myDocId);
125+
assertTrue(maybeDoc.isPresent());
126+
assertEquals("issue 637 test", maybeDoc.get().getTitle());
127+
128+
// Test update
129+
MyDoc doc = maybeDoc.get();
130+
doc.setTitle("updated title");
131+
myDocRepository.save(doc);
132+
133+
maybeDoc = myDocRepository.findById(myDocId);
134+
assertTrue(maybeDoc.isPresent());
135+
assertEquals("updated title", maybeDoc.get().getTitle());
136+
137+
// Test delete
138+
myDocRepository.deleteById(myDocId);
139+
maybeDoc = myDocRepository.findById(myDocId);
140+
assertFalse(maybeDoc.isPresent());
141+
}
142+
143+
@SpringBootApplication
144+
@Configuration
145+
@EnableRedisDocumentRepositories(
146+
basePackages = {
147+
"com.redis.om.spring.fixtures.document.model",
148+
"com.redis.om.spring.fixtures.document.repository"
149+
}
150+
)
151+
static class Config extends TestConfig {
152+
153+
/**
154+
* This demonstrates the fix for issue #637.
155+
* Users can now provide their own RedisEnhancedMappingContext bean
156+
* with a custom keyspace resolver.
157+
*
158+
* The @ConditionalOnMissingBean annotation on the default bean
159+
* ensures this user-provided bean takes precedence without
160+
* requiring spring.main.allow-bean-definition-overriding=true
161+
*/
162+
@Bean(name = "redisEnhancedMappingContext")
163+
@Primary
164+
public RedisEnhancedMappingContext customMappingContext() {
165+
RedisEnhancedMappingContext mappingContext = new RedisEnhancedMappingContext();
166+
167+
// Custom keyspace resolver for multi-tenant support
168+
mappingContext.setKeySpaceResolver(type ->
169+
TENANT_PREFIX + ":" + type.getSimpleName());
170+
171+
return mappingContext;
172+
}
173+
}
174+
}

0 commit comments

Comments
 (0)