有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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

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

我在两个朋友、customFriendList中都添加了一些对象。 并尝试使用以下命令更新fbUserProfile对象:

mongoTemplate.save(fbUserProfile);

note: fbUserProfile already exists in db. Now I am updating this

Error Message: Cannot perform cascade save on child object without id set

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


共 (4) 个答案

  1. # 1 楼答案

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

    @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()
                }
            }
        }
    }
    
  2. # 2 楼答案

    在依赖子对象上设置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();
        }
    
  3. # 3 楼答案

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

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

    当您试图持久化一个集合时,就会发生这种情况。集合的项是否有@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);
    }
    
  4. # 4 楼答案

    如果您有列表,上述解决方案可以很好地工作。但是我们可以避免为列表中的每个元素触发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);
      }