Java spring data mongodb无法在未设置id的子对象上执行级联保存

Java spring data mongodb无法在未设置id的子对象上执行级联保存,java,mongodb,spring-data-mongodb,Java,Mongodb,Spring Data Mongodb,我正在使用将子对象保存在单独的集合中。 我的文档类是: public class FbUserProfile{ @Id private long id; @DBRef(lazy=true) @CascadeSave() private Set<FacebookFriend> friends; @DBRef(lazy=true) @CascadeSave() private Set<FacebookFriend

我正在使用将子对象保存在单独的集合中。 我的文档类是:

public class FbUserProfile{

    @Id
    private long id;

    @DBRef(lazy=true)
    @CascadeSave()
    private Set<FacebookFriend> friends;

    @DBRef(lazy=true)
    @CascadeSave()
    private Set<FacebookFriendList> customFriendList;
}

public class FacebookFriend{
    @Id
    private long id;
    private String name;
}

public class FacebookFriendList{

    @Id
    private long id;
    private String name;
    private String list_type;
}
注意:数据库中已存在fbUserProfile。现在我正在更新这个

错误消息:无法在未设置id的子对象上执行级联保存

如果我删除@CascadeSave。这对我来说很好。如何级联设置对象。
我还将@CascadeSave用于其他对象。工作正常,但它们不是set对象。

在依赖子对象上设置ID的最佳方法是通过扩展AbstractMongoEventListener类编写侦听器类,并重写onConvert()方法

公共类CustomMongoEventListener扩展
抽象MongoEventListener{
@自动连线
私有MongoOperations MongoOperations;
@凌驾
转换前公共void(最终对象实体){
if(entity.id==null | | entity.id.isEmpty()){
entity.id=generateGuid();//生成随机序列id
}
公共静态字符串generateGuid(){
SecureRandom Randogen=新的SecureRandom();
byte[]byteArray=新字节[16];
随机生成下个字节(byteArray);
返回新的Base32().encodeToString(byteArray).substring(0,26);
}
}
最后在配置文件中注册自定义侦听器。对于注释方法,请使用以下代码进行注册:
@豆子
public CustomMongoEventListener cascadingMongoEventListener(){
返回新的CustomMongoEventListener();
}

我在其他地方找到了相同的教程:'s和(这是我遵循的教程)

我也遇到过同样的问题,我可以解决它

当您尝试持久化集合时会发生这种情况。集合的项是否具有@Id并不重要,因为集合本身不会具有@Id。我在EventListener的onBeforeConvert中编辑了代码,以检查您尝试级联保存的字段是否为集合(在我的示例中为列表)。如果它是一个列表,您只需在其中循环检查每个项目的@Id并保存它们

如果它不是一个集合,您仍然必须像以前一样持久化它们

@Override
public void onBeforeConvert(Object source) {
    ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {

        @Override
        public void doWith(Field field)
                throws IllegalArgumentException, IllegalAccessException {
            ReflectionUtils.makeAccessible(field);

            if (field.isAnnotationPresent(DBRef.class) && field.isAnnotationPresent(CascadeSave.class)){
                final Object fieldValue = field.get(source);

                if(fieldValue instanceof List<?>){
                    for (Object item : (List<?>)fieldValue){
                        checkNSave(item);
                    }
                }else{
                    checkNSave(fieldValue);
                }
            }
        }
    });
}

private void checkNSave(Object fieldValue){
    DbRefFieldCallback callback = new DbRefFieldCallback();
    ReflectionUtils.doWithFields(fieldValue.getClass(), callback);

    if (!callback.isIdFound()){
        throw new MappingException("Oops, something went wrong. Child doesn't have @Id?");
    }

    mongoOperations.save(fieldValue);
}
@覆盖
转换前的公共void(对象源){
ReflectionUtils.doWithFields(source.getClass(),new ReflectionUtils.FieldCallback()){
@凌驾
公共无效doWith(字段)
抛出IllegalArgumentException,IllegalAccessException{
ReflectionUtils.MakeAccessable(字段);
if(field.isAnnotationPresent(DBRef.class)和&field.isAnnotationPresent(CascadeSave.class)){
最终对象fieldValue=field.get(源);
if(字段值instanceof List){
对于(对象项:(列表)字段值){
检查保存(项目);
}
}否则{
checkNSave(fieldValue);
}
}
}
});
}
私有void checkNSave(对象字段值){
DbRefFieldCallback callback=新的DbRefFieldCallback();
ReflectionUtils.doWithFields(fieldValue.getClass(),回调);
如果(!callback.isIdFound()){
抛出新的MappingException(“哎呀,出了点问题。孩子没有@Id?”);
}
mongoOperations.save(字段值);
}

如果您有列表,上述解决方案可以很好地工作。但我们可以避免对列表中的每个元素触发保存查询,因为这会降低性能。以下是我从上述代码中找到的解决方案

 @Override
  public void onBeforeConvert(BeforeConvertEvent<Object> event) {
      Object source = event.getSource(); 
      ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {

          @Override
          public void doWith(Field field)
                  throws IllegalArgumentException, IllegalAccessException {
              ReflectionUtils.makeAccessible(field);

              if (field.isAnnotationPresent(DBRef.class) && field.isAnnotationPresent(CascadeSave.class)){
                  final Object fieldValue = field.get(source);

                  if(fieldValue instanceof List<?>){
                      for (Object item : (List<?>)fieldValue){
                          checkNAdd(item);
                      }
                  }else{
                      checkNAdd(fieldValue);
                  }
                  mongoOperations.insertAll(documents);
              }
          }
      });
  }

  private void checkNAdd(Object fieldValue){
      DbRefFieldCallback callback = new DbRefFieldCallback();
      ReflectionUtils.doWithFields(fieldValue.getClass(), callback);

      if (!callback.isIdFound()){
          throw new MappingException("Oops, something went wrong. Child doesn't have @Id?");
      }

      documents.add(fieldValue);
  }
@覆盖
公共无效onBeforeConvert(BeforeConvertEvent事件){
Object source=event.getSource();
ReflectionUtils.doWithFields(source.getClass(),new ReflectionUtils.FieldCallback()){
@凌驾
公共无效doWith(字段)
抛出IllegalArgumentException,IllegalAccessException{
ReflectionUtils.MakeAccessable(字段);
if(field.isAnnotationPresent(DBRef.class)和&field.isAnnotationPresent(CascadeSave.class)){
最终对象fieldValue=field.get(源);
if(字段值instanceof List){
对于(对象项:(列表)字段值){
检查NADD(项目);
}
}否则{
检查NADD(字段值);
}
mongoOperations.insertAll(文档);
}
}
});
}
私有void checkNAdd(对象字段值){
DbRefFieldCallback callback=新的DbRefFieldCallback();
ReflectionUtils.doWithFields(fieldValue.getClass(),回调);
如果(!callback.isIdFound()){
抛出新的MappingException(“哎呀,出了点问题。孩子没有@Id?”);
}
文件。添加(字段值);
}

好的,我扩展类,它将检查文档是否存在如果存在,它将更新文档,否则它将插入文档:

@Component
class GenericCascadeMongo(
        private val mongoTemplate: MongoTemplate
) : AbstractMongoEventListener<Any>() {

    override fun onBeforeConvert(event: BeforeConvertEvent<Any?>) {
        val source = event.source
                ?: return
        ReflectionUtils.doWithFields(source.javaClass) { field ->
            ReflectionUtils.makeAccessible(field)
            if (field.isAnnotationPresent(DBRef::class.java) && field.isAnnotationPresent(CascadeSave::class.java)) {

                val fieldValue = field[source]
                        ?: return@doWithFields

                if (fieldValue is List<*>) {
                    fieldValue.filterNotNull().forEach {
                        checkAndSave(it)
                    }
                } else {
                    checkAndSave(fieldValue)
                }
            }
        }
    }

    private fun checkAndSave(fieldValue: Any) {
        try {
            val callback = DbRefFieldCallback(fieldValue)
            ReflectionUtils.doWithFields(fieldValue.javaClass, callback)
            if (!callback.isIdFound && callback.id == null) {
                mongoTemplate.insert(fieldValue)
            }

            if (callback.id != null) {
                val findById = mongoTemplate.exists(Query(Criteria.where(MConst.MONGO_ID).`is`(callback.id)), fieldValue.javaClass)
                if (findById) {
                    mongoTemplate.save(fieldValue)
                } else {
                    mongoTemplate.insert(fieldValue)
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private class DbRefFieldCallback(val fieldValue: Any?) : FieldCallback {
        var isIdFound = false
            private set
        var id: String? = null
            private set

        @Throws(IllegalArgumentException::class, IllegalAccessException::class)
        override fun doWith(field: Field) {
            ReflectionUtils.makeAccessible(field)
            if (field.isAnnotationPresent(Id::class.java)) {
                isIdFound = true
                id = ReflectionUtils.getField(field, fieldValue)?.toString()
            }
        }
    }
}
@组件
类GenericMongo(
私有val mongoTemplate:mongoTemplate
):AbstractMongoEventListener(){
重写onBeforeConvert(事件:BeforeConvertEvent){
val source=event.source
?:返回
ReflectionUtils.doWithFields(source.javaClass){field->
ReflectionUtils.MakeAccessable(字段)
if(field.isAnnotationPresent(DBRef::class.java)和&field.isAnnotationPresent(CascadeSave::class.java)){
val fieldValue=字段[源]
?: return@doWithFields
如果(字段值为列表){
fieldValue.filterNotNull().forEach{
检查并保存(it)
}
}否则{
检查并保存(字段值)
}
}
}
}
私人乐趣检查和保存(字段值:任意){
试一试{
val callback=DbRefFieldCallback(fieldValue)
ReflectionUtils.doWithFields(fieldValue.jav
 @Override
  public void onBeforeConvert(BeforeConvertEvent<Object> event) {
      Object source = event.getSource(); 
      ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {

          @Override
          public void doWith(Field field)
                  throws IllegalArgumentException, IllegalAccessException {
              ReflectionUtils.makeAccessible(field);

              if (field.isAnnotationPresent(DBRef.class) && field.isAnnotationPresent(CascadeSave.class)){
                  final Object fieldValue = field.get(source);

                  if(fieldValue instanceof List<?>){
                      for (Object item : (List<?>)fieldValue){
                          checkNAdd(item);
                      }
                  }else{
                      checkNAdd(fieldValue);
                  }
                  mongoOperations.insertAll(documents);
              }
          }
      });
  }

  private void checkNAdd(Object fieldValue){
      DbRefFieldCallback callback = new DbRefFieldCallback();
      ReflectionUtils.doWithFields(fieldValue.getClass(), callback);

      if (!callback.isIdFound()){
          throw new MappingException("Oops, something went wrong. Child doesn't have @Id?");
      }

      documents.add(fieldValue);
  }
@Component
class GenericCascadeMongo(
        private val mongoTemplate: MongoTemplate
) : AbstractMongoEventListener<Any>() {

    override fun onBeforeConvert(event: BeforeConvertEvent<Any?>) {
        val source = event.source
                ?: return
        ReflectionUtils.doWithFields(source.javaClass) { field ->
            ReflectionUtils.makeAccessible(field)
            if (field.isAnnotationPresent(DBRef::class.java) && field.isAnnotationPresent(CascadeSave::class.java)) {

                val fieldValue = field[source]
                        ?: return@doWithFields

                if (fieldValue is List<*>) {
                    fieldValue.filterNotNull().forEach {
                        checkAndSave(it)
                    }
                } else {
                    checkAndSave(fieldValue)
                }
            }
        }
    }

    private fun checkAndSave(fieldValue: Any) {
        try {
            val callback = DbRefFieldCallback(fieldValue)
            ReflectionUtils.doWithFields(fieldValue.javaClass, callback)
            if (!callback.isIdFound && callback.id == null) {
                mongoTemplate.insert(fieldValue)
            }

            if (callback.id != null) {
                val findById = mongoTemplate.exists(Query(Criteria.where(MConst.MONGO_ID).`is`(callback.id)), fieldValue.javaClass)
                if (findById) {
                    mongoTemplate.save(fieldValue)
                } else {
                    mongoTemplate.insert(fieldValue)
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private class DbRefFieldCallback(val fieldValue: Any?) : FieldCallback {
        var isIdFound = false
            private set
        var id: String? = null
            private set

        @Throws(IllegalArgumentException::class, IllegalAccessException::class)
        override fun doWith(field: Field) {
            ReflectionUtils.makeAccessible(field)
            if (field.isAnnotationPresent(Id::class.java)) {
                isIdFound = true
                id = ReflectionUtils.getField(field, fieldValue)?.toString()
            }
        }
    }
}