在Spring MVC中处理域对象

最近,我惊讶于一个代码库在其所有域实体中具有公共默认构造函数(即零参数构造函数),并且所有字段具有getter和setter。 当我深入研究时,我发现域实体之所以如此,主要是因为该团队认为Web / MVC框架需要它。 我认为这是消除一些误解的好机会。

具体来说,我们将研究以下情况:

  1. 生成的ID字段没有设置器(即,生成的ID字段具有吸气剂但没有设置器)
  2. 没有默认的构造函数(例如,没有公共的零参数构造函数)
  3. 具有子实体的域实体(例如,子实体未显示为可修改列表)

绑定Web请求参数

首先,一些细节和背景。 让我们基于特定的Web / MVC框架-Spring MVC。 使用Spring MVC时,其数据绑定按名称绑定请求参数。 让我们举个例子。

@Controller
@RequestMapping("/accounts")
... class ... {...@PostMappingpublic ... save(@ModelAttribute Account account, ...) {...}...
}

给定上面的控制器映射到“ / accounts”,一个Account实例可以从哪里来?

根据文档 ,Spring MVC将使用以下选项获取实例:

  • 从模型(如果已通过Model添加(例如通过同一控制器中的@ModelAttribute方法 )。
  • 通过@SessionAttributes在HTTP会话中。
  • 来自通过Converter的URI路径变量。
  • 从默认构造函数的调用开始。
  • (仅适用于Kotlin)通过调用具有与Servlet请求参数匹配的参数的“主要构造函数”; 参数名称是通过JavaBeans @ConstructorProperties或字节码中运行时保留的参数名称确定的。

假设没有在会话中添加Account对象,并且没有@ModelAttribute方法 ,Spring MVC最终将使用其默认构造函数实例化一个实例,并按name绑定Web请求参数。 例如,请求包含“ id”和“ name”参数。 Spring MVC将尝试通过分别调用“ setId”和“ setName”方法将它们绑定到“ id”和“ name” bean属性。 这遵循JavaBean约定。

生成ID字段的无设置方法

让我们从简单的事情开始。 假设我们有一个Account域实体。 它具有由持久性存储生成的ID字段,并且仅提供getter方法(但不提供setter方法)。

@Entity
... class Account {@Id @GeneratedValue(...) private Long id;...public Account() { ... }public Long getId() { return id; }// but no setId() method
}

那么,我们如何让Spring MVC将请求参数绑定到Account域实体? 我们是否必须为生成的字段和只读字段提供公共设置方法?

在我们HTML表单中,我们不会将“ id”作为请求参数。 我们将其放置为路径变量。

我们使用@ModelAttribute方法。 在请求处理方法之前调用它。 它支持与常规请求处理方法几乎相同的参数。 在我们的例子中,我们使用它来检索具有给定唯一标识符的Account域实体,并将其用于进一步的绑定。 我们的控制器看起来像这样。

@Controller
@RequestMapping("/accounts")
... class ... {...@ModelAttributepublic Account populateModel(HttpMethod httpMethod,@PathVariable(required=false) Long id) {if (id != null) {return accountRepository.findById(id).orElseThrow(...);}if (httpMethod == HttpMethod.POST) {return new Account();}return null;}@PutMapping("/{id}")public ... update(...,@ModelAttribute @Valid Account account, ...) {...accountRepository.save(account);return ...;}@PostMappingpublic ... save(@ModelAttribute @Valid Account account, ...) {...accountRepository.save(account);return ...;}...
}

更新现有帐户时,请求将是对“ / accounts / {id}” URI的PUT 。 在这种情况下,我们的控制器需要检索具有给定唯一标识符的域实体,并向Spring MVC提供相同的域对象以进行进一步绑定(如果有)。 “ id”字段将不需要设置方法。

添加或保存新帐户时,请求将是“ / accounts”的POST 。 在这种情况下,我们的控制器需要使用一些请求参数创建一个新的域实体,并向Spring MVC提供相同的域对象以进行进一步绑定(如果有)。 对于新的域实体,“ id”字段保留为null 。 基础的持久性基础结构将在存储时生成一个值。 尽管如此,“ id”字段仍不需要设置方法。

在这两种情况下,@ @ModelAttribute方法populateModel均在映射的请求处理方法之前被调用。 因此,我们需要在populateModel使用参数来确定在哪种情况下使用它。

域对象中没有默认构造函数

假设我们的Account域实体没有提供默认构造函数(即,没有零参数构造函数)。

... class Account {public Account(String name) {...}...// no public default constructor// (i.e. no public zero-arguments constructor)
}

那么,我们如何让Spring MVC将请求参数绑定到Account域实体? 它不提供默认的构造函数。

我们可以使用@ModelAttribute方法。 在这种情况下,我们要创建一个带有请求参数的Account域实体,并将其用于进一步的绑定。 我们的控制器看起来像这样。

@Controller
@RequestMapping("/accounts")
... class ... {...@ModelAttributepublic Account populateModel(HttpMethod httpMethod,@PathVariable(required=false) Long id,@RequestParam(required=false) String name) {if (id != null) {return accountRepository.findById(id).orElseThrow(...);}if (httpMethod == HttpMethod.POST) {return new Account(name);}return null;}@PutMapping("/{id}")public ... update(...,@ModelAttribute @Valid Account account, ...) {...accountRepository.save(account);return ...;}@PostMappingpublic ... save(@ModelAttribute @Valid Account account, ...) {...accountRepository.save(account);return ...;}...
}

具有子实体的域实体

现在,让我们看一下具有子实体的域实体。 这样的东西。

... class Order {private Map<..., OrderItem> items;public Order() {...}public void addItem(int quantity, ...) {...}...public Collection<CartItem> getItems() {return Collections.unmodifiableCollection(items.values());}
}... class OrderItem {private int quantity;// no public default constructor...
}

请注意,订单中的项目不会显示为可修改列表。 Spring MVC支持索引属性,并将它们绑定到数组,列表或其他自然排序的集合。 但是,在这种情况下, getItems方法将返回无法修改的集合。 这意味着当对象尝试向其添加/删除项目时,将引发异常。 那么,如何让Spring MVC将请求参数绑定到Order域实体? 我们是否被迫将订单项公开为可变列表?

并不是的。 我们必须避免用表示层关注点来稀释域模型(例如Spring MVC)。 相反,我们使表示层成为域模型的客户端。 为了处理这种情况,我们创建了另一个符合Spring MVC的类型,并使我们的域实体与表示层无关。

... class OrderForm {public static OrderForm fromDomainEntity(Order order) {...}...// public default constructor// (i.e. public zero-arguments constructor)private List<OrderFormItem> items;public List<OrderFormItem> getItems() { return items; }public void setItems(List<OrderFormItem> items) { this.items = items; }public Order toDomainEntity() {...}
}... class OrderFormItem {...private int quantity;// public default constructor// (i.e. public zero-arguments constructor)// public getters and setters
}

请注意,完全可以创建一个了解域实体的表示层类型。 但是让域实体知道表示层对象并不是全部。 更具体地说,表示层OrderForm知道Order域实体。 但是Order不了解表示层OrderForm

这是我们的控制器的外观。

@Controller
@RequestMapping("/orders")
... class ... {...@ModelAttributepublic OrderForm populateModel(HttpMethod httpMethod,@PathVariable(required=false) Long id,@RequestParam(required=false) String name) {if (id != null) {return OrderForm.fromDomainEntity(orderRepository.findById(id).orElseThrow(...));}if (httpMethod == HttpMethod.POST) {return new OrderForm(); // new Order()}return null;}@PutMapping("/{id}")public ... update(...,@ModelAttribute @Valid OrderForm orderForm, ...) {...orderRepository.save(orderForm.toDomainEntity());return ...;}@PostMappingpublic ... save(@ModelAttribute @Valid OrderForm orderForm, ...) {...orderRepository.save(orderForm.toDomainEntity());return ...;}...
}

总结思想

正如我在之前的文章中提到的,可以让您的域对象看起来像具有公共默认零参数构造函数,getter和setter的JavaBean。 但是,如果域逻辑开始变得复杂,并且要求某些域对象失去其JavaBean风格(例如,不再有公共的零参数构造函数,没有更多的setter),则不必担心。 定义新的JavaBean类型以满足与表示相关的问题。 不要稀释域逻辑。

目前为止就这样了。 我希望这有帮助。

再次感谢Juno帮助我提供样品。 相关代码段可以在GitHub上找到 。

翻译自: https://www.javacodegeeks.com/2018/06/domain-objects-spring-mvc.html

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

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

相关文章

【有限域元素加法和乘法】有限域元素加法和乘法的原理及MATLAB实现

关注公号【逆向通信猿】更精彩!!! 有限域 仍然以 r = 4 r=4 r=4,

使用storm 实时计算_使用Storm进行可扩展的实时状态更新

使用storm 实时计算在本文中&#xff0c;我将说明如何借助Storm框架以可扩展且无锁定的方式在数据库中维护实时事件驱动流程的当前状态。 Storm是基于事件的数据处理引擎。 它的模型依赖于基本原语&#xff0c;例如事件转换&#xff0c;过滤&#xff0c;聚合……&#xff0c;我…

【MFC系列-第22天】GDI算法实战——过渡色

关注公号【逆向通信猿】更精彩&#xff01;&#xff01;&#xff01; 第22天 GDI算法实战 CDC(HDC)绘图类&#xff1a; 五大GDI对象类&#xff1a;CPen&#xff0c;CBrush&#xff0c;CFont&#xff0c;CBitmap&#xff0c;CRgn 22.1 走马灯 设置定时器 SetTimer(1, 20, N…

无服务器:不费吹灰之力!

几年前&#xff0c; 集装箱横扫开发人员&#xff0c;而开发人员的土地就像6级飓风一样 。 码头工人 Rkt 。 其他 。 Docker Swarm 。 K8s 。 OpenShift 。 现在&#xff0c;我们实际上处于震中&#xff0c;但是当我们瞥见地平线时&#xff0c;我们看到另一个人来了&#x…

【MFC系列-第23天】CMemoryDC的封装过程

CDC(HDC)绘图类&#xff1a; 五大GDI对象类&#xff1a;CPen&#xff0c;CBrush&#xff0c;CFont&#xff0c;CBitmap&#xff0c;CRgn 23.1 LoadImage API HANDLE LoadImage(HINSTANCE hinst,LPCTSTR lpszName, UINT uType, int cxDesired,int cyDesired,UINT fuLoad );uT…

JDK 8与JDK 10:三元/拆箱的区别

最近的Nicolai Parlog &#xff08; nipafx &#xff09; 鸣叫引起了我的注意&#xff0c;因为它引用了关于JDK 8和JDK 10之间行为更改的有趣StackOverflow讨论 &#xff0c;并询问“为什么&#xff1f;” SerCe 在StackOverflow线程上引用的问题最终归结为在JDK 8和JDK 10之间…

【多元域乘法】多项式乘法电路原理及MATLAB详解

关注公号【逆向通信猿】更精彩!!! 关于二元域上的两个元素的乘法、多项式除法,在之前的博客 【有限域除法】二元多项式除法电路原理及MATLAB详解 子程序:sub_poly_div.m 【有限域元素加法和乘法】有限域元素加法和乘法的原理及MATLAB实现 子程序:sub_gf_add.m、sub_gf_…

my CSAPP Attack lab堆栈详解

关注公号【逆向通信猿】更精彩!!! 这个实验时学习了简书上的一篇文章后,自己根据课程例子进行的一次小测试,phase 4和5的堆栈图解还没有画,等后续有时间会进行补充。 本人转载的简书原文: https://blog.csdn.net/wlwdecs_dn/article/details/121249364#comments_19237…

Spring MVC教程

1.简介 作为企业Java开发人员&#xff0c;这项工作的主要重点之一是开发Web应用程序。 对于Web应用程序&#xff0c;后果还包括许多挑战。 具体来说&#xff0c;其中一些是状态管理&#xff0c;工作流和验证。 HTTP协议的无状态性质只会使事情变得更加复杂。 Spring的Web框架旨…

【MFC系列-第24天】梯形分页和蝴蝶QQ宠物的实现

CDC(HDC)绘图类&#xff1a; 五大GDI对象类&#xff1a;CPen&#xff0c;CBrush&#xff0c;CFont&#xff0c;CBitmap&#xff0c;CRgn 24.1 梯形分页的双缓冲改进和尺寸自适应 24.2 蝴蝶跟随鼠标点击运动 class CHitFlyDlg : public CDialogEx {CMemoryDC m_dc;//缓冲enu…

ubuntu22.04 下载路径

ftp下载路径 csdn下载 ubuntu22.04下载路径ubuntu-22.04-desktop-amd64.7z.001资源-CSDN文库 ubuntu22.04下载路径ubuntu-22.04-desktop-amd64.7z.002资源-CSDN文库 【免费】ubuntu-22.04-desktop-amd64.7z.003资源-CSDN文库 【免费】ubuntu-22.04-desktop-amd64.7z.004资源-…

camel seda 协议_探索Apache Camel Core – Seda组件

camel seda 协议Apache Camel中的seda组件与我在先前的博客中介绍的direct组件非常相似&#xff0c;但是以异步的方式。 为此&#xff0c;它使用java.util.concurrent.BlockingQueue作为默认实现来使消息排队并与主Route线程断开连接&#xff0c;然后在单独的线程中处理消息。 …

【MFC系列-第25、26天】绘图软件

25.1 绘图软件的绘制原理 纯虚函数&#xff1a;抽象函数&#xff0c;强制在派生类中进行实现&#xff1b; 虚函数&#xff1a;有函数体&#xff0c;可在基类也可在派生类中实现。 基类CLayer class CLayer {//抽象类 public:CLayer();~CLayer();virtual void OnDraw(CDC* pDC…

Java 10:“ var”关键字

Java 10使用关键字var引入了局部变量类型推断 。 这意味着无需编写&#xff1a; Map<Department, List<Employee>> map new HashMap<>(); // ... for (Entry<Department, List<Employee>> dept : map.entrySet()) {List<Employee> emplo…

【MFC系列-第32天】控件自绘技术

32.1 对话框背景设置 方法一 BOOL CClDlg::OnEraseBkgnd(CDC* pDC) {CRect rect;GetClientRect(rect);pDC->FillSolidRect(rect, RGB(200, 255, 255));return TRUE; }方法二&#xff1a;WM_CTRLCOLOR消息 按类型按句柄按控件ID HBRUSH CMFCApplication1Dlg::OnCtlColor(…

Spring Reactor教程

在RESTful服务的世界中&#xff0c;实际上实际上是在幕后进行许多工作&#xff0c;我们通常必须在应用程序中进行很多处理&#xff0c;而实际上并不会影响需要发送给真实用户的响应。 可以被动地做出这些业务决策&#xff0c;以便它们对与应用程序交互的用户没有任何影响。 Spr…

MFC多线程处理界面假死之红外图像数据获取和excel写入

在MFC主界面某个Button Click事件中起一个线程去做处理一些事情,在起的线程运行完毕后,接着跑Click起线程后的代码,已达到按顺序执行,保证时许正确的目的。 问题 通常处理一个线程等待用 WaitForSingleObject,这个放在主界面线程成中会造成主界面“卡死”,其原因是它将…

api签名_使用签名保护基于HTTP的API

api签名我在EMC上的一个平台上可以构建SaaS解决方案。 与越来越多的其他应用程序一样&#xff0c;该平台具有基于RESTful HTTP的API。 使用JAX-RS之类的开发框架&#xff0c;构建这样的API相对容易。 但是&#xff0c; 正确构建它们并不容易。 建立基于HTTP的API的问题 问…

【多元域除法】多项式除法电路原理及MATLAB详解

关注公号【逆向通信猿】更精彩!!! 关于二元域上的两个元素的加法和乘法、多项式除法,在之前的博客 【有限域除法】二元多项式除法电路原理及MATLAB详解 子程序:sub_poly_div.m 【有限域元素加法和乘法】有限域元素加法和乘法的原理及MATLAB实现 子程序:sub_gf_add.m、s…

用Rocker制作模板

在本文中&#xff0c;我们将快速介绍Rocker &#xff0c;这是一个静态类型化的快速Java 8模板引擎。 必需的依赖项 要开始使用Rocker&#xff0c;我们需要在项目中添加以下依赖项&#xff1a; <dependency><groupId>com.fizzed</groupId><artifactId>…