Spring Data MongoDB级联保存在DBRef对象上

默认情况下, Spring Data MongoDB不支持对带有@DBRef注释的引用对象的级联操作,如引用所述 :

映射框架不处理级联保存 。 如果更改了Person对象引用的Account对象,则必须单独 保存 Account对象。 在Person对象上调用save 不会自动将Account对象保存在属性帐户中。

这很成问题,因为要实现保存子对象,您需要覆盖父存储库中的save方法或创建其他“服务”? 这里介绍了类似的方法。

在本文中,我将向您展示如何使用AbstractMongoEventListener的通用实现针对所有文档实现此目标。

@CascadeSave批注

由于我们无法通过添加级联属性来更改@DBRef批注,因此可以创建新的批注@CascadeSave,该批注将用于标记保存父对象时应保存哪些字段。

package pl.maciejwalkowiak.springdata.mongodb;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface CascadeSave {}

CascadingMongoEventListener

下一部分是实现此批注的处理程序。 我们将使用强大的Spring Application Event机制 。 特别是,我们将扩展AbstractMongoEventListener以捕获已保存的对象,然后再将其转换为Mongo的DBObject 。

它是如何工作的? 调用对象MongoTemplate #save方法时,在实际保存对象之前,会将其从MongoDB api转换为DBObject。 下面实现的CascadingMongoEventListener提供了在对象转换之前捕获对象的钩子,并且:

  • 仔细检查其所有字段,以检查是否同时有@DBRef和@CascadeSave注释的字段。
  • 找到字段时,它将检查是否存在@Id批注
  • 子对象正在保存
package pl.maciejwalkowiak.springdata.mongodb;import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;import java.lang.reflect.Field;public class CascadingMongoEventListener extends AbstractMongoEventListener {@Autowiredprivate MongoOperations mongoOperations;@Overridepublic void onBeforeConvert(final Object source) {ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {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);DbRefFieldCallback callback = new DbRefFieldCallback();ReflectionUtils.doWithFields(fieldValue.getClass(), callback);if (!callback.isIdFound()) {throw new MappingException("Cannot perform cascade save on child object without id set");}mongoOperations.save(fieldValue);}}});}private static class DbRefFieldCallback implements ReflectionUtils.FieldCallback {private boolean idFound;public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {ReflectionUtils.makeAccessible(field);if (field.isAnnotationPresent(Id.class)) {idFound = true;}}public boolean isIdFound() {return idFound;}}
}

映射要求

如您所见,为了使工作正常,您需要遵循一些规则:

  • 父类的子级属性必须使用@DBRef和@CascadeSave进行映射
  • 子类需要具有以@Id注释的属性,如果应该自动生成该ID,则应按ObjectId的类型

用法

为了在项目中使用级联保存,您只需要在Spring Context中注册CascadingMongoEventListener:

<bean class="pl.maciejwalkowiak.springdata.mongodb.CascadingMongoEventListener" />

让我们测试一下

为了显示一个示例,我制作了两个文档类:

@Document
public class User {@Idprivate ObjectId id;private String name;@DBRef@CascadeSaveprivate Address address;public User(String name) {this.name = name;}// ... getters, setters, equals hashcode
}
@Document
public class Address {@Idprivate ObjectId id;private String city;public Address(String city) {this.city = city;}// ... getters, setters, equals hashcode
}

在测试中,有一个创建了地址的用户,然后保存了该用户。 测试将仅涵盖积极的情况,并且仅用于表明它确实有效( applcationContext-tests.xml仅包含默认的Spring Data MongoDB Bean和已注册的CascadingMongoEventListener):

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applcationContext-tests.xml"})
public class CascadingMongoEventListenerTest {@Autowiredprivate MongoOperations mongoOperations;/*** Clean collections before tests are executed*/@Beforepublic void cleanCollections() {mongoOperations.dropCollection(User.class);mongoOperations.dropCollection(Address.class);}@Testpublic void testCascadeSave() {// givenUser user = new User("John Smith");user.setAddress(new Address("London"));// whenmongoOperations.save(user);// thenList<User> users = mongoOperations.findAll(User.class);assertThat(users).hasSize(1).containsOnly(user);User savedUser = users.get(0);assertThat(savedUser.getAddress()).isNotNull().isEqualTo(user.getAddress());List<Address> addresses = mongoOperations.findAll(Address.class);assertThat(addresses).hasSize(1).containsOnly(user.getAddress());}
}

我们也可以在Mongo控制台中进行检查:

> db.user.find()
{ "_id" : ObjectId("4f9d1bab1a8854250a5bf13e"), "_class" : "pl.maciejwalkowiak.springdata.mongodb.domain.User", "name" : "John Smith", "address" : { "$ref" : "address", "$id" : ObjectId("4f9d1ba41a8854250a5bf13d") } }
> db.address.find()
{ "_id" : ObjectId("4f9d1ba41a8854250a5bf13d"), "_class" : "pl.maciejwalkowiak.springdata.mongodb.domain.Address", "city" : "London" }

摘要

通过这种简单的解决方案,我们最终可以通过一个方法调用保存子对象,而无需为每个文档类实现任何特殊的功能。

我相信,将来作为Spring Data MongoDB版本的一部分,我们会在级联删除中找到该功能。 这里介绍的解决方案有效,但:

  • 它需要使用其他注释
  • 使用反射API遍历字段,这不是最快的方法(但可以根据需要随意实现缓存)

如果这可以是Spring Data MongoDB的一部分,而不是附加的注释,则@DBRef可以具有附加的cascade属性。 除了反射之外,我们可以将MongoMappingContext和MongoPersistentEntity一起使用。 我已经开始准备带有这些更改的请求请求。 我们将看看它是否将被Spring Source团队接受。

参考: Spring Data MongoDB级联保存在我们的JCG合作伙伴 Maciej Walkowiak的“ 软件开发之旅”博客上的DBRef对象上。

翻译自: https://www.javacodegeeks.com/2013/11/spring-data-mongodb-cascade-save-on-dbref-objects.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/365482.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BZOJ4061/Gym100624F CERC2012 Farm and Factory 最短路、切比雪夫距离

传送门——BZOJCH 传送门——Vjudge 设\(f_i\)表示\(i\)到\(1\)号点的最短距离&#xff0c;\(g_i\)表示\(i\)到\(2\)号点的最短距离&#xff0c;\(s_i\)表示\(n1\)号点到\(i\)号点的最短距离&#xff0c;\(As_1,Bs_2\) 根据最短路三角形不等式&#xff0c;\(|f_i - A| \leq s_i…

scrapy安装_爬虫框架Scrapy简介与安装

Scrapy 框架Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架&#xff0c;用途非常广泛。框架的力量&#xff0c;用户只需要定制开发几个模块就可以轻松的实现一个爬虫&#xff0c;用来抓取网页内容以及各种图片&#xff0c;非常之方便。Scrapy 使…

前端面试题(附上自己的回答)

一些开放性题目 1.自我介绍&#xff1a;除了基本个人信息以外&#xff0c;面试官更想听的是你与众不同的地方和你的优势。 2.项目介绍&#xff1f; 3.如何看待前端开发&#xff1f; 4.平时是如何学习前端开发的&#xff1f; 5.未来三到五年的规划是怎样的&#xff1f; position…

汇编语言的基础知识

汇编语言是在硬件上工作的编程语言&#xff0c;我们需要了解硬件系统的结构&#xff0c;才能有效的用汇编语言对其进行编程。 一&#xff1a;汇编语言的组成 1&#xff09;汇编指令&#xff1a;机器码的助记符&#xff0c;有对应的机器码。 2&#xff09;伪指令&#xff1a;没有…

亚马逊Simple Worklfow服务的骆驼演示

在上一篇文章中&#xff0c;我解释了为什么AWS SWF服务很好&#xff0c;并宣布了新的Camel SWF组件。 现在&#xff0c;组件文档已准备就绪&#xff0c; 这是一个简单的完全可用的演示。 它包含三个独立的独立骆驼路线&#xff1a; 工作流生产者允许我们与工作流进行交互。 它…

CODEVS 1205 单词反转

嗯.... 这道题看起来挺像一个字符串的题&#xff0c;但其实却错了&#xff0c;它实质上却用了一个栈进行了一个模拟&#xff08;当然还有一种鬼畜的做法&#xff0c;下面也会介绍到..... 首先先看题&#xff1a; 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 青铜 Bronze 题…

python 定义函数为什么有个长线_关于格式化:如何在Python中打破这条长线?

如何设置这样的长行格式&#xff1f;我想让它的宽度不超过80个字符&#xff1a;logger.info("Skipping {0} because its thumbnail was already in our system as {1}.".format(line[indexes[url]], video.title))这是我最好的选择吗&#xff1f;url "Skipping …

NodeJS中resolve添加地址无效

今天一个朋友在群里问了这样一个问题&#xff0c;他使用url.resolve()添加地址无效&#xff0c;我看了一下&#xff0c;发现是他没有注意细节&#xff0c; resolve可以在二级目录下增加&#xff0c;他使用的时候只是一级目录&#xff0c;所以添加会有问题。他使用的是如下这种…

centos配置jdk的环境变量

1、首先呢&#xff0c;centos下的JDK环境配置分两种情况&#xff0c;一直是root用户级别的jdk配置&#xff0c;另一种是其他用户组级别的配置。这里讲解的是root用户级别的配置。 我们已经下载解压好了jdk的目录。如下 2、编辑环境变量的配置文件&#xff1a; vi /etc/profile…

Python之字符串转换为日期、结合时区的日期操作

一、字符串转换为日期 方法一 s 2019-01-20 print(datetime.strptime(s, %Y-%m-%d)) # 2019-01-20 00:00:00 方法二 def parse_ymd(s):year_s, mon_s, day_s s.split(-)return datetime(int(year_s), int(mon_s), int(day_s)) s 2019-01-20 res parse_ymd(s) print(res) …

项目学生:带有Jersey的Web服务客户端

这是Project Student的一部分。 其他职位包括带有Jersey的Webservice Client &#xff0c; 业务层和带有Spring Data的持久性 。 RESTful Web应用程序洋葱的第一层是Web服务客户端。 它可以用来模仿包含AJAX内容的网页&#xff0c;也可以被webapp的编程用户用来模仿。 注意&am…

华为摄像机搜索软件_别人的终点华为的起点!用普惠AI守护城市安全

看点&#xff1a;华为好望的求索启示录&#xff01;如何让老百姓用上实惠的AI&#xff1f;你也许不知道&#xff0c;高空抛物已是城市生活中的一大难以治理的安全隐患。一个小小的烟头、水果从高处扔下来&#xff0c;就可能引起严重火灾、人员伤亡、财物破坏&#xff0c;事发后…

在WebGL场景中进行棋盘操作的实验

这篇文章讨论如何在基于Babylon.js的WebGL场景中&#xff0c;建立棋盘状的地块和多个可选择的棋子对象&#xff0c;在点选棋子时显示棋子的移动范围&#xff0c;并且在点击移动范围内的空白地块时向目标地块移动棋子。在这一过程中要考虑不同棋子的移动力和影响范围不同&#x…

em算法python代码_EM算法的python实现的方法步骤

导读热词前言&#xff1a;前一篇文章大概说了EM算法的整个理解以及一些相关的公式神马的&#xff0c;那些数学公式啥的看完真的是忘完了&#xff0c;那就来用代码记忆记忆吧&#xff01;接下来将会对python版本的EM算法进行一些分析。EM的python实现和解析引入问题(双硬币问题)…

第一阶段·Linux运维基础-第2章·Linux系统目录结构介绍

01 变量与PS1 02 添加用户 03 关闭SELinux 04 关闭iptables 05 显示中文乱码排查过程 06 总结 07 目录结构课程内容 08 Linux目录结构特点 09 Linux核心目录简介 10 Linux目录文件之配置文件 11 Linux核心目录文件之DNS及屌丝逃离洗浴中心之路 12 Linux核心目录文件…

使用junit-drools进行JBoss Drools单元测试

最近&#xff0c;我一直在大量使用JBoss Drools进行项目。 我不是Drools专家-我也不太相信这个框架&#xff0c;或者可能不是只相信该项目中的特定用例-我发现很难为基于Drools的业务规则编写简单&#xff0c;可维护的单元测试 。 这就是junit-drools诞生的方式-简单的帮助程序…

scrapy 采集网页出现丢失url的问题

url_list ["http://www.icoat.cc/news/list_18_3.html", "http://www.icoat.cc/news/list_18.html", "http://www.icoat.cc/news/list_18_2.html", ] for ls in url_list:   yield scrapy.Request(urlls, headersheader, callbackself.parseL…

java中重新加载指定文件_java-更改后重新加载属性文件

我将属性文件加载到一个类中,然后在整个应用程序中使用该类来获取它们.public class PropertiesUtil extends PropertyPlaceholderConfigurer {private static Map properties new HashMap();Overrideprotected void loadProperties(final Properties props) throws IOExcepti…

plsql 为空显示 0 的函数_不加班只加薪!从0到1教你制作出入库进销存表格

出入库表应用十分广泛&#xff0c;是每个公司都用到的表格&#xff0c;下面我们来看看怎么从一张空白表一步一步实现《出入库表》的制作&#xff0c;目的是做到只需要记录出库入库流水&#xff0c;自动对库存及累计出入库数量进行计算、实时统计。出入库表构成做一个出入库表&a…

eShopOnContainers学习系列(一):Swagger的使用

最近在看eShopOnContainer项目&#xff0c;抽取一下其中的基础知识点&#xff0c;做个记录&#xff0c;有兴趣的可以看下。 新建一个.net core API项目&#xff0c;添加Nuget包 Swashbuckle.AspNetCore.SwaggerGen、Swashbuckle.AspNetCore.SwaggerUI&#xff1a; 然后在启动文…