我使用@CascadeSave将子对象保存在单独的集合中。 我的文档类是:
IF OBJECT_ID('tempdb..#teacher') IS NOT NULL DROP TABLE #teacher
IF OBJECT_ID('tempdb..#coursesCsv') IS NOT NULL DROP TABLE #coursesCsv
create table #teacher
(
TeacherID varchar(10),
FullName varchar(30)
)
insert into #teacher select '001-1225', 'Roy Brown'
insert into #teacher select '001-1230', 'Woody Boyd'
create table #coursesCsv
(
CourseName varchar(30),
TeacherID varchar(10)
)
insert into #coursesCsv select 'Math 101', '001-1225'
insert into #coursesCsv select 'Math 101 Old', '002-1225'
insert into #coursesCsv select 'History 101', '001-1230'
select t.teacherid, c.coursename from
#teacher t inner join #coursesCsv c
on t.teacherid = c.teacherid
--on right(t.teacherid,4) = right(c.teacherid,4)
我在两个朋友customFriendList中添加了一些对象。 并尝试使用以下命令更新fbUserProfile对象:
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;
}
注意:db中已存在fbUserProfile。现在我正在更新这个
错误消息:无法在没有id set
的情况下对子对象执行级联保存
如果我删除@CascadeSave。这对我来说可以。我怎么能级联设置对象。 我也在使用@CascadeSave和其他对象。它工作正常,但它们不是设置对象。
答案 0 :(得分:1)
在依赖子对象上设置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();
}
答案 1 :(得分:0)
我在其他地方找到了相同的教程:Baeldung和JavaCodeGeeks(这是我所遵循的那个)
我遇到了同样的问题,我可以解决它。可
当您尝试保留集合时会发生这种情况。集合的项目具有@Id并不重要,因为集合本身不会拥有它。我在EventListener的onBeforeConvert中编辑了代码,以检查您尝试CascadeSave的字段是否是一个集合(在我的例子中是一个List)。如果它是一个列表,你只需循环检查@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);
}
答案 2 :(得分:0)
如果您有一个列表,上述解决方案可以正常工作。但是我们可以避免为列表中的每个元素触发保存查询,因为它会降低性能。这是我从上面的代码中找到的解决方案。
@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);
}
答案 3 :(得分:0)
Okey,我扩展了类,它将检查文档是否存在(如果存在),它将更新文档,否则将插入文档:
@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()
}
}
}
}