简体   繁体   English

spring data mongodb无法对没有设置id的子对象执行级联保存

[英]spring data mongodb Cannot perform cascade save on child object without id set

I am using @CascadeSave to save child object in separate collection.我正在使用@CascadeSave将子对象保存在单独的集合中。 My Document classes are :我的文档类是:

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;
}

I add some object in both friends,customFriendList.我在两个朋友中添加了一些对象,customFriendList。 and try to update fbUserProfile object using:并尝试使用以下方法更新 fbUserProfile 对象:

mongoTemplate.save(fbUserProfile);

note: fbUserProfile already exists in db.注意: db 中已存在 fbUserProfile。 Now I am updating this现在我正在更新这个

Error Message: Cannot perform cascade save on child object without id set错误消息:无法在未设置 id 的子对象上执行级联保存

If I remove @CascadeSave.如果我删除@CascadeSave。 It works fine for me.这对我来说可以。 How I can Cascade set objects.我如何级联设置对象。 I am also using @CascadeSave with other objects.我还将@CascadeSave 与其他对象一起使用。 Its working fine but they are not set object.它工作正常,但它们不是设置对象。

The best way to set an ID on the dependent child object is to write a listener class by extending AbstractMongoEventListener class and override the onConvert() method.在依赖子对象上设置 ID 的最佳方法是通过扩展 AbstractMongoEventListener 类并覆盖 onConvert() 方法来编写侦听器类。

public class CustomMongoEventListener extends
        AbstractMongoEventListener<Object> {

    @Autowired
    private MongoOperations mongoOperations;

    @Override
    public void onBeforeConvert(final Object entity) {

    if (entity.id == null || entity.id.isEmpty()) {
      entity.id =   generateGuid();   //generate random sequence ID
    }

public static String generateGuid() {
        SecureRandom randomGen = new SecureRandom();
        byte[] byteArray = new byte[16];
        randomGen.nextBytes(byteArray);
        return new Base32().encodeToString(byteArray).substring(0,26);
    }

}

Finally register your custom listener in `your configuration file. For annotation approach use the following code to register :
@Bean
    public CustomMongoEventListener cascadingMongoEventListener() {
        return new CustomMongoEventListener();
    }

I found the same tutorials somewhere else: Baeldung 's and JavaCodeGeeks (this is the one i've followed)我在其他地方找到了相同的教程: BaeldungJavaCodeGeeks (这是我遵循的那个)

I've had that same problem, and I could solve it.我遇到了同样的问题,我可以解决它。

It happens when you try to persist a collection.当您尝试保留集合时会发生这种情况。 It doesn't matter that the collection's items have the @Id, because the collection itself won't have it.集合的项目是否具有 @Id 并不重要,因为集合本身不会有它。 I edited the code in the EventListener's onBeforeConvert to check if the field you're trying to CascadeSave is a collection (in my case a List).我编辑了 EventListener 的 onBeforeConvert 中的代码,以检查您尝试 CascadeSave 的字段是否是一个集合(在我的情况下是一个列表)。 If it's a list, you just cycle through it checking each individual item for @Id and saving them.如果它是一个列表,您只需循环检查@Id 的每个单独项目并保存它们。

If it's not a collection you still have to persist them the same way you did before如果它不是一个集合,你仍然必须像以前一样坚持它们

@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);
}

The above solution works fine incase if you have a list.如果您有列表,上述解决方案可以正常工作。 But we can avoid firing a save query for each element from the list, as it reduces the performance.但是我们可以避免为列表中的每个元素触发保存查询,因为它会降低性能。 Here is the solution I have found out of the above code.这是我从上面的代码中找到的解决方案。

 @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);
  }

Okey I extend the class and it will check if the document is exist if it exist it will update the document else it insert the document:好吧,我扩展了类,它会检查文档是否存在(如果存在)它将更新文档,否则它会插入文档:

@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()
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM