Spring 和 MongoDB:以更扁平的方式保存值对象
Spring and MongoDB: Saving Value Objects in a more flat way
对于我正在设计和开发的新 Spring 应用程序,出于多种技术原因,我们使用 MongoDB 作为持久层。这是我尝试实施一些 DDD 原则的第一个项目,包括值对象。我试图找到保存 ValueObject 的最佳方法,它实际上只是一个字符串。使用 Lombok 的 @Value,我的 Spring REST 控制器愉快地将值解析为 RestController 端的 ValueObject。但是当保存值时,它会以结构化的方式保存在 MongoDB 端。
例如
我的旁白:
@Value
public class PersonKey {
private String value;
}
我将存储在 MongoDB 中的文档:
@Document
public class PersonDocument {
private PersonKey personKey;
private Name name;
...
}
MongoDB 中保存的内容:
{.. "personKey": {"value": "faeeaf2"} ...}
我真正想要的是:
{.. "personKey": "faeeaf2" ..}
当然有最少的额外样板代码.. :-)
看来您唯一的选择是使用 AbstractMongoEventListener
和 onAfterConvert
方法在转换后修改 DBObject
。不幸的是,无法轻松更改文档中单个字段的转换。保存整个文档时使用自定义转换器,而不是单个字段。您也不能使用 getter 方法来替换字段访问("The fields of an object are used to convert to and from fields in the document. Public JavaBean properties are not used." 来自 http://docs.spring.io/spring-data/data-mongo/docs/1.4.2.RELEASE/reference/html/mapping-chapter.html)。因此,实现您想要的唯一方法是通过 mongodb 事件。但是,您可以在事件处理程序中使用反射来检查字段是否使用 @Value
注释进行注释,因此可以以更通用的方式转换它。如果存在 @Value
注释,只需将 DBObject
中的注释替换为它的值 属性.
为此,您需要扩展 AbstractMongoEventListener
。您可以在此处查看带有 onBeforeSave
事件处理程序的示例:
更新:
正如@maartinus 在评论中注意到的那样,使用反射来搜索 @Value
对象将不起作用,因为它在运行时不可用(保留设置为 SOURCE
)。因此,您需要使用单一方法 value()
引入您自己的注释或接口(例如 ValueObject
),这将 return 对象的值。
巧合的是,我正在处理完全相同的问题。您实际上可以使用 CustomConverters 执行此操作。也许不完全像你想要的那样,但方式非常相似。我一直无法克服的唯一问题是您不能在运行时动态选择转换器。这是由于在行号 182
上实施了 CustomConversions#registerConversion
。
这个例子展示了我目前正在做的事情。我使用的是 GenericConverter
,但您也可以回退到常规 Converters
。此示例不检查 Field 注释。相反,它使用一个名为 SingleValue
的接口。我所有代表单个值的值对象都实现了这个接口。所以不需要在 Field 上进行注释。
@Configuration
@Slf4j
public class MongoDbConfiguration {
@Bean
@Primary
public CustomConversions mongoCustomConversions() throws Exception {
final List converterList = new ArrayList<>();
converterList.add(new SingleValueConverter());
return new CustomConversions(converterList);
}
private static class SingleValueConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return new HashSet<>(Arrays.asList(new ConvertiblePair[]{
new ConvertiblePair(UUIDEntityReference.class, String.class),
new ConvertiblePair(String.class, FundingProcessId.class)
// put here all of your type conversions (two way!)
}));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
// first check if the instance of this type is an instance of our 'SingleValue' interface
if (source instanceof SingleValue) {
return ((SingleValue) source).getValue();
}
final Class<?> objectType = targetType.getType();
final Constructor<?> constructor = objectType.getConstructor(sourceType.getType());
return constructor.newInstance(source);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Could not convert unexpected type " +
sourceType.getObjectType().getName() + " to " + targetType.getObjectType().getName(), e);
}
}
}
}
这是我能找到的最优雅的方式。仍在寻找自动注册所有类型的方法。这可以通过注释和类路径扫描来实现。
请注意,此实现假设了一些事情
- 你有一个接口
SingleValue
和方法 getValue
- 你的每个 'single-valued' 值 类 都有一个带有内部类型表示的单个参数的构造函数(例如,UUID 的字符串)
编辑:我发布了错误的示例代码。已更新
对于我正在设计和开发的新 Spring 应用程序,出于多种技术原因,我们使用 MongoDB 作为持久层。这是我尝试实施一些 DDD 原则的第一个项目,包括值对象。我试图找到保存 ValueObject 的最佳方法,它实际上只是一个字符串。使用 Lombok 的 @Value,我的 Spring REST 控制器愉快地将值解析为 RestController 端的 ValueObject。但是当保存值时,它会以结构化的方式保存在 MongoDB 端。
例如
我的旁白:
@Value
public class PersonKey {
private String value;
}
我将存储在 MongoDB 中的文档:
@Document
public class PersonDocument {
private PersonKey personKey;
private Name name;
...
}
MongoDB 中保存的内容:
{.. "personKey": {"value": "faeeaf2"} ...}
我真正想要的是:
{.. "personKey": "faeeaf2" ..}
当然有最少的额外样板代码.. :-)
看来您唯一的选择是使用 AbstractMongoEventListener
和 onAfterConvert
方法在转换后修改 DBObject
。不幸的是,无法轻松更改文档中单个字段的转换。保存整个文档时使用自定义转换器,而不是单个字段。您也不能使用 getter 方法来替换字段访问("The fields of an object are used to convert to and from fields in the document. Public JavaBean properties are not used." 来自 http://docs.spring.io/spring-data/data-mongo/docs/1.4.2.RELEASE/reference/html/mapping-chapter.html)。因此,实现您想要的唯一方法是通过 mongodb 事件。但是,您可以在事件处理程序中使用反射来检查字段是否使用 @Value
注释进行注释,因此可以以更通用的方式转换它。如果存在 @Value
注释,只需将 DBObject
中的注释替换为它的值 属性.
为此,您需要扩展 AbstractMongoEventListener
。您可以在此处查看带有 onBeforeSave
事件处理程序的示例:
更新:
正如@maartinus 在评论中注意到的那样,使用反射来搜索 @Value
对象将不起作用,因为它在运行时不可用(保留设置为 SOURCE
)。因此,您需要使用单一方法 value()
引入您自己的注释或接口(例如 ValueObject
),这将 return 对象的值。
巧合的是,我正在处理完全相同的问题。您实际上可以使用 CustomConverters 执行此操作。也许不完全像你想要的那样,但方式非常相似。我一直无法克服的唯一问题是您不能在运行时动态选择转换器。这是由于在行号 182
上实施了 CustomConversions#registerConversion
。
这个例子展示了我目前正在做的事情。我使用的是 GenericConverter
,但您也可以回退到常规 Converters
。此示例不检查 Field 注释。相反,它使用一个名为 SingleValue
的接口。我所有代表单个值的值对象都实现了这个接口。所以不需要在 Field 上进行注释。
@Configuration
@Slf4j
public class MongoDbConfiguration {
@Bean
@Primary
public CustomConversions mongoCustomConversions() throws Exception {
final List converterList = new ArrayList<>();
converterList.add(new SingleValueConverter());
return new CustomConversions(converterList);
}
private static class SingleValueConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return new HashSet<>(Arrays.asList(new ConvertiblePair[]{
new ConvertiblePair(UUIDEntityReference.class, String.class),
new ConvertiblePair(String.class, FundingProcessId.class)
// put here all of your type conversions (two way!)
}));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
// first check if the instance of this type is an instance of our 'SingleValue' interface
if (source instanceof SingleValue) {
return ((SingleValue) source).getValue();
}
final Class<?> objectType = targetType.getType();
final Constructor<?> constructor = objectType.getConstructor(sourceType.getType());
return constructor.newInstance(source);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Could not convert unexpected type " +
sourceType.getObjectType().getName() + " to " + targetType.getObjectType().getName(), e);
}
}
}
}
这是我能找到的最优雅的方式。仍在寻找自动注册所有类型的方法。这可以通过注释和类路径扫描来实现。
请注意,此实现假设了一些事情
- 你有一个接口
SingleValue
和方法 getValue - 你的每个 'single-valued' 值 类 都有一个带有内部类型表示的单个参数的构造函数(例如,UUID 的字符串)
编辑:我发布了错误的示例代码。已更新