Spring mvc Spring和MongoDB:以更简单的方式保存值对象

Spring mvc Spring和MongoDB:以更简单的方式保存值对象,spring-mvc,domain-driven-design,spring-data-mongodb,lombok,value-objects,Spring Mvc,Domain Driven Design,Spring Data Mongodb,Lombok,Value Objects,对于我正在设计和开发的一个新的Spring应用程序,出于一些技术原因,我们使用MongoDB作为持久层。这是我尝试实现一些DDD原则的第一个项目,包括值对象。我试图找到保存ValueObject的最佳方法,它实际上只是一个字符串。使用Lombok的@Value,我的SpringREST控制器在RestController端愉快地将值解析为ValueObject。但是当保存值时,它会在MongoDB端以结构化的方式保存 比如说 我的VO: @Value public class PersonKey

对于我正在设计和开发的一个新的Spring应用程序,出于一些技术原因,我们使用MongoDB作为持久层。这是我尝试实现一些DDD原则的第一个项目,包括值对象。我试图找到保存ValueObject的最佳方法,它实际上只是一个字符串。使用Lombok的@Value,我的SpringREST控制器在RestController端愉快地将值解析为ValueObject。但是当保存值时,它会在MongoDB端以结构化的方式保存

比如说

我的VO:

@Value
public class PersonKey {
    private String value;
}
我将在MongoDB中存储的文档:

@Document
public class PersonDocument {
    private PersonKey personKey;
    private Name name;
    ...
}
{.. "personKey": {"value": "faeeaf2"} ...}
MongoDB中保存的内容:

@Document
public class PersonDocument {
    private PersonKey personKey;
    private Name name;
    ...
}
{.. "personKey": {"value": "faeeaf2"} ...}
我真正想要的是:

{.. "personKey": "faeeaf2" ..}

当然,使用最少的额外样板代码…-)

似乎您唯一的选择是使用
AbstractMongoEventListener
onAfterConvert
方法在转换后修改
DBObject
。不幸的是,不可能轻松更改文档中单个字段的转换。保存整个文档而不是单个字段时使用自定义转换器。您也不能使用getter方法来替换字段访问(“对象的字段用于与文档中的字段进行转换。不使用公共JavaBean属性。”from)。因此,实现您想要的目标的唯一方法是通过mongodb事件。但是,您可以在事件处理程序中使用反射来检查字段是否用
@Value
注释进行了注释,因此可以以更通用的方式对其进行转换。如果存在
@Value
注释,只需在
DBObject
中将其替换为其Value属性即可

要实现这一点,您需要扩展
AbstractMongoEventListener
。您可以通过
onBeforeSave
事件处理程序在此处看到示例:

更新:
正如@maartinus在评论中注意到的,使用反射搜索
@Value
对象将不起作用,因为它在运行时不可用(保留设置为
源代码
)。因此,您需要使用单个方法
value()
来引入自己的注释或接口(例如
ValueObject
),该方法将返回对象的值。

巧合的是,我正在处理完全相同的问题。你可以用CustomConverters来实现这一点。也许不完全像你想要的,但方式非常相似。我唯一无法克服的问题是,您不能在运行时动态选择转换器。这是由于在行号
182
上实现了
CustomConversions#registerConversion

这个例子展示了我目前正在做的事情。我使用的是
通用转换器
,但您也可以退回到常规的
转换器
。本例不检查字段注释。相反,它使用一个名为
SingleValue
的接口。表示单个值的所有my Value对象都实现此接口。因此,不需要在字段上进行注释

@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);
            }
        }
    }
}
@配置
@Slf4j
公共类MongoDbConfiguration{
@豆子
@初级的
public CustomConversions mongoCustomConversions()引发异常{
最终列表转换器列表=新的ArrayList();
添加(新的SingleValueConverter());
返回新的CustomConversions(转换器列表);
}
私有静态类SingleValueConverter实现GenericConverter{
@凌驾
公共集getConvertibleTypes(){
返回新的HashSet(Arrays.asList)(新的ConvertiblePair[]{
新的ConvertiblePair(UUIDEntityReference.class,String.class),
新的ConvertiblePair(String.class,FundingProcessId.class)
//把你所有的类型转换都放在这里(双向!)
}));
}
@凌驾
公共对象转换(对象源、类型描述符源类型、类型描述符目标类型){
试一试{
//首先检查此类型的实例是否是“SingleValue”接口的实例
if(SingleValue的源实例){
返回((SingleValue)source.getValue();
}
最终类objectType=targetType.getType();
最终构造函数=objectType.getConstructor(sourceType.getType());
返回构造函数.newInstance(源);
}捕捉(反射操作异常e){
抛出新的RuntimeException(“无法转换意外类型”+
sourceType.getObjectType().getName()+”到“+targetType.getObjectType().getName(),e);
}
}
}
}
这是我能找到的最优雅的方式。仍在寻找自动注册所有类型的方法。这可以通过注释和类路径扫描实现

请注意,此实现假设了以下几点

  • 您有一个带有getValue方法的接口
    SingleValue
  • 每个“单值”值类都有一个构造函数,其中包含一个内部类型表示的参数(例如,UUID的字符串)

  • 编辑:我发布了错误的示例代码。在
    PHP
    中更新了它

    ,我完成了一个转换器类,每次
    insert
    update
    find
    mongodb操作都会调用它。这个转换器是递归的,并且使用反射。这就是PHP,一种动态语言。我敢说Java作为一种静态语言是不同的:-)这就是为什么我没有提供答案的原因。也许这对你有帮助。可以添加自定义转换器,帮助从MongoDB层进行转换,正如在中所解释的,这可能是一个解决方案,但不够动态,不符合我的喜好检查
    @lombok.Value
    无法工作,因为它是
    @Retention(RetentionPolicy.SOURCE)