我如何devise一个没有ORM且没有embedded式SQL的Java Web应用程序

编辑:原始标题:关于使用ORM的好处的问题。

我想使用ORM进行学习,并尝试使用nhibernate。 我正在使用教程,然后我有一个真正的项目。 我可以走“老路”或使用ORM。 我不确定我完全理解这个好处。 一方面,我可以在代码中创build我的抽象,以便可以更改我的数据库并独立于数据库。 另一方面,如果我真的改变数据库列,我必须改变我所有的代码。

为什么我不能没有我的应用程序没有ORM,改变我的数据库和改变我的代码,而不是改变我的数据库,ORM和代码? 他们的数据库结构是不是变化太大了?

我相信有这样的好处,因为ORM被这么多人使用。 我只是不确定我还没有得到它。

谢谢。

编辑:在教程中他们有许多文件,用于使ORM的工作

http://www.hibernate.org/362.html

在应用程序发生变化的情况下,只是说我有“适当”的抽象层,似乎还有很多额外的工作。 因为我是新手,看起来并不那么容易维护,再次看起来像额外的工作,而不是更less。

编辑:这是一个老问题,我不断回来。 我想看看如果正确地devise没有ORM的应用程序,没有使用embedded式SQL,也没有使用.NET LINQ-to-SQL,没有违法的例子。 我现在在Java世界,而且我迷失在如何继续。 这是一个Web应用程序。 没有spring,没有其他世俗的框架。 JSP,JSTL,EL,HTML,JavaScript,CSS,Java,Tomcat。 希望我没有留下任何东西。 是的,我知道这是一个古老的问题。 这仍然是相关的。

为什么呢,为什么这个行业如此紧贴这个概念的灾难呢? 以前的答案没有充分地解决@约翰尼的担忧。 它们都是半成品的挥手理由,可以很好地清除数据库程序员面临的具体问题。

@TXI的答案是我在提出同样的问题时得到的回答的典型例子:图层分离。 这是什么意思? 为什么我分开我的图层? 或者,更重要的是,如何创build一个与关系层不同的附加层,并且应该是来自该层的规范映射,对我有什么好处?

此外,(@ johhny尚未回答的问题)这是如何保护我们不受改变的? 如果数据库发生变化,ORM层几乎肯定会跟随。 实际上,在对象层次上不对连接表build模的缺省做法使得情况变得更糟,因为当表格不可避免地增长一些额外的列时,你不仅增加了一些额外的字段到对象模型,而且你还改变了它的拓扑和力一堆代码重写! 这是一个变革pipe理的灾难,是一个经典的例子,说明一个错误的关系观点(思考它们是否代表对象)是法院的灾难。 如果您只是将编程语言中的关系数据库直接映射到一致的概念(想到LINQ到SQL,而不是 LINQ到EF),就不会发生这种情况。

这个空间中最大的悬而未决的问题 – 房间里的大象 – 是:ORM应该解决什么问题? 而不要说“对象 – 关系阻抗不匹配”。 这只是另一个挥手的脱口秀。 解释为什么有一个阻抗不匹配,为什么它应该是数据库的语言,而不是语言去数据库。 我的解释是,大多数编程语言都习惯于expression和处理关系数据,但这是一个正在开始消失的历史现实(LINQ-to-SQL是朝这个方向迈出的第一步),而不是一个基本的原则在哪个基础上build立健全的架构。

有一个原因,ORM变得如此复杂,延迟加载,caching,一系列令人困惑的持久性和所有权语义等等。还有一个原因,就是对于所有这些额外的敲击键盘,他们仍然无法有效地解决基本诸如“哪一对成员共享多个组?”之类的问题? 关系模型是在networking和社会模型因为这些问题而屈膝屈膝的时候构思的, 这是一个新鲜的空气气息。 现在,我们似乎都渴望回到我们老旧的沙坑,我们认为我们已经发明了一些新的东西(只要我们持有鼻子)。

(我完全希望在这个答案中被广泛地低估,但是当你这样做的时候请留下评论,我不介意被告知我错了,只要我知道为什么。)

编辑:谢谢@Chris抽空评论。 它给了我一些具体的观点来解决…(请注意,虽然我经常在下面提到@Chris,但是我并不是想要专门去做他的任务,他的回答是我讨论所以我希望他不要把我的批评看作是一种个人的冒犯,他们不是那种意图,我真的很感激他的回应。

首先,让我澄清@克里斯的评论和回答中的一些错误观念。

  1. 因为所有显而易见的原因,我不主张原始SQL代码,有些不是很明显的原因(例如,SQL既不是代数也不是微积分,这使得function分解几乎不可能)。
  2. 我不主张单一的应用程序devise。 一般来说,图层是一件好事。
  3. 我不主张用许多线路噪声(如特殊字段,方法和属性)来污染对象模型。 坦率地说,这是一个稻草人,因为域/对象模型只存在于ORM领域。 现在,我知道LINQ-to-SQL的所有这些类都有很多嘈杂的东西,但它们只是幕后pipe道。 你不要编辑这个代码,而且你通常不应该去查看它。

现在有人反对反对意见:

  1. 断言应用程序可以独立于数据库而构build是毫无根据的。 总的来说,ORM只是对数据层的一个规范映射(Tables Foo和Bar成为Foo和Bar类,而FooBar成为Foo和Bar之间某种类似的事情)。 在这个映射中没有太多的摆动空间,所以对数据模型的任何改变几乎肯定会要求相应的改变对象模型。 在我看来,这是一件好事,因为从相应的数据库模型中彻底分化出来的对象只不过是所有相关人员的额外维护头痛。
  2. 一旦ORM产生数据模型独立性的错觉被抛弃,所有关于直接耦合到数据模型的弊端的抗议就变得没有意义了。 但我想追求这一点,而不是简单地解雇它。 耦合是系统devise的基本特征。 在某些时候,必须做出决定和假设。 你不能用一个“东西”表来编程所有的东西。 您必须决定您的域名包含某些特定的概念,然后创build尊重这些概念的模式和代码,将其视为一stream的公民,对其进行硬编码。 应用程序应该独立于数据库的想法是错误的。 数据库是(或者应该是)一个企业知识的最纯粹的表示(我知道情况并非总是如此,我将在稍后讨论)。 与这种表示相结合应该为弹性提供最强有力的保证,因为这种数据模型只有在业务本身经历一些内在变化时才会改变。 简而言之,耦合到精心devise的数据库模式是一件非常好的事情。
  3. 分层本身并不是目的。 这是很好的,因为它达到了一些特定的目标。 前面的观点表明,以ORM的方式在数据库和应用程序之间进行分层对于实现真正的弹性变更目标既不是有效的,也是必需的。 这是通过良好的数据库devise实现的。
  4. @Chris断言,让数据库指挥的东西阻碍OOdevise。 这已经足够了,但是如果面向对象devise是build立知识模型的最佳方法,那么这一点就很有意思。 OODBMS在市场上几乎完全失败,暗示事实并非如此。 关系模型及其谓词逻辑基础与OOdevise具有相同的expression能力,不会产生OO模型的图论复杂性。
  5. @克里斯反对关系模型,理由是它不能解决今天的问题(因此NoSQL运动)是完全不合格的。 NoSQL的意思是“没有SQL”,而不是“没有关系模型”。 不幸的是,即使NoSQL运动的支持者在这方面似乎也是相当无知的。 SQL有很深的缺陷,其中许多可以追溯到与关系模型彻底背离。 要说我们应该放弃关系模型,因为SQL糟糕是一个相当公然的情况,把婴儿扔出去洗澡。
  6. 不使用ORM不会使构build应用程序的工作量增加三倍。 这是一个荒谬的主张,甚至@Chris似乎正在对后门开放,对代码的替代方式赞不绝口。 Codegen工具,如LINQ到SQL的sqlmetal是一个完美的解决scheme,对于任何不熟悉这个教条的人来说,应用程序的数据模型绝对不同于数据库的数据模型。

我自己的ORM经验是,他们在教程中工作很好,在现实世界中造成无尽的痛苦和挫折。 使用LINQ到SQL解决了许多激发ORM的问题,我没有理由让自己陷入这种折磨。

仍然存在一个主要问题:当前的SQL数据库不能提供对物理层和逻辑层分离的有意义程度的控制。 从表到磁盘上的东西的映射大部分是固定的,完全在SQL DBMS的控制之下。 这不是关系模型计划的一部分,关系模型将两者明确区分开来,并且允许定义可以以与逻辑模型所build议的完全不同的结构存储在磁盘上的数据的一致的逻辑表示。 例如,系统(或DBA)可以自由地进行非规范化 – 出于性能原因 – 高度规范化的逻辑模型。 因为SQL引擎不允许这种关注的分离,所以通常需要非规范化或以其他方式折磨逻辑模型。 因此,逻辑模型不可能总是完全按照他们的意思,所以使用数据库作为最纯粹的知识表示的理想是无法完全实现的。 然而,在实践中,devise师总是坚持从数据库到领域模型的规范映射,因为其他任何东西都太难以维护。

在我使用NHibernate的经验中,它有点像Visual Basic: 它使得简单的问题变得非常简单,但是这也使得难以做到甚至完全不可能。

其基本思想是ORM避免了编写持久性代码。 在这样的代码中有很多重复的东西,所以让代码具有通用性,而不是特定于特定的业务层是非常诱人的,并且跨项目重用。 到现在为止还挺好。 对于简单的对象层次结构和业务需求,这实际上工作得很好。 如果你的数据库改变了,是的,你必须改变ORM映射文件,但是这通常很简单,你只需要在一个地方进行修改 – 比改变访问数据库的代码要容易得多。

问题是,随着数据库和需求变得越来越复杂,ORM越来越难以跟上。 所以ORM变得越来越复杂。 它也采取捷径,做一些效率低下的事情,因为在所有情况下,如何有效地做到这一点都不够聪明。 更重要的是,因为整个想法是透明的,所以在影响用户体验之前,你往往不能看到这些性能问题。 一个相关的问题是,错误很难find,因为你只是不知道ORM里面发生了什么,除非你debugging它。 (是的,我必须通过NHibernate代码,它不是野餐!)

所以你开始绕开ORM来直接使用SQL。 当然,你必须使代码与使用ORM的代码一起工作,这是更多的工作。 您最终编写代码来手动加载和保存一些对象,并以某种方式将其工作到ORM代码中。 最后你开始想知道ORM是否为你创造了更多的工作,而不是节省 – 更不用说性能和bug的问题了。

所以,如果你正在编写一个非常简单的应用程序,你在教程中find的那种,ORM将会做得很好。 如果比这更复杂,那么我认为这是不值得的。 当然,对于一个简单的应用程序来说,节省的绝对时间也是很小的。 我的结论是:不要打扰ORM。 ORM是通往黑暗面的path。

在阅读本文之前,我经常想知道其他人是否真的很难理解ORM工具对项目的影响。 @Evgeny,@Marcelo Cantos和@Jimmy B把这个rest一下。

简而言之,它们与ORM工具周围的大多数问题都没有关系。 只有一对,他们要么没有覆盖,要么覆盖不够。

首先,使用ORM并不意味着更less的代码。 这可能意味着更less的SQL,但它并不意味着更less的代码。 沿着这些线,工具生成的代码可能是错误的。 更糟糕的是,补偿不良生成的代码是一个沮丧的行为。

其次,ORM工具并不意味着你不必理解SQL。 换句话说,你必须知道SQL是一个有效的程序员(有像embedded的家伙那样的例外)。 从性能或资源的angular度来看,这些工具发出的查询(types和数量)都不够好。 如果你已经知道SQL,为什么要枷锁自己?

第三,ORM工具不会节省时间。 您将花费尽可能多的(如果不是更多的话)击败您select的ORM工具,以便处理体面大小的任何应用。 为了增加侮辱,当你完成后,你会发现查询的质量通常比你自己做的更差。 要点是ORM =在项目上花费的时间越多,结果越糟糕。

第四,数据库独立性通常是浪费时间。 大多数应用程序在其生命周期中将只使用一个DBMS; 无论是Oracle,SQL服务器,MySql,不pipe。 该应用程序能够利用DBMS提供的function要好得多。 作为DBMS不可知的手段,你将不得不限制自己的子查询。

第五,不是所有的东西都必须成为一个客体。 这是一个重要的事情要注意。 我们经常被要求在页面上显示一组特定的数据。 通常这意味着连接来自两个或多个表格的数据。 您的应用程序是否必须创build并实例化所有这些对象才能显示一些数据? 还是你最好只是执行一个查询,并以您select的格式将数据直接发送到屏幕/浏览器?

即使是最简单的事情,ORM也会增加很多开销。 大多数ORM将会生成多个sql查询,甚至在表上执行一个简单的UPDATE。

第六,也是我心中最重要的问题:ORM降低了你的安全性。 当然你可以使用ORM的s'procs; 但大多数人不。 几乎所有我见过的利用ORM暴露数据库的应用程序都是这样的:如果网站被破解,整个数据库都可能杀死或被窃取。 ORM工具即时生成SQL。 这意味着他们需要直接访问表格。 这意味着,如果你的应用程序被入侵,黑客将有直接的表访问。 这意味着您已经从应用程序中有效地移除了至less一层安全性。

我意识到这个问题是前一段时间发布的,但我想知道和johnny一样的东西。 我已经看到Hibernate在智能人员的几个大型项目中使用,而且在任何情况下,这都是一场无可争议的灾难。 根据我的经验,大多数企业应用程序的单一最复杂和性能影响的区域是在业务层和数据库层之间。 这是我们添加数据访问层的原因之一,试图将数据库交互封装到可pipe理的块中。

我见过的使用ORM的最大理由是它们“减less了手写代码”,并提供了一个将业务逻辑和数据访问分开的抽象层。 我断言,除非是非常简单的情况,否则这些都不是真实的。

为了使Hibernate(以及大多数其他的ORM工具)能够工作,我们要么创build一个hibernate映射文件来logging所有的数据库和对象的交互,要么使用注释来logging相同的关系。 在一个例子中,我们把我们的代码移到了一个很难testing的xmlconfiguration文件中,但是并不复杂。 另一方面,我们分布了如何在整个领域模型中与数据库进行交互的逻辑。 重点是,虽然我们写的实际“代码”较less,但将代码移到configuration文件或注释!=“较less的代码”。 它简单地将代码中的复杂性从我们可以直接控制并减轻的代码转移到一个控制更less的第三方工具。

应该将业务/域层与数据库层分开的ORM抽象层往往会有更微妙的影响来抵消这种“分离”。 你看过多less个项目,ORM层以何种方式影响对象模型和/或数据库的devise? 要使用一个可怜的物理比喻,假设你的业务层,ORM层和数据库层都有质量。 另一个层之间的ORM层的存在往往会施加一个改变和扭曲其他层的力。 您是否必须介绍一个主键对象,因为ORM层需要它而不太适合您的业务模型? 您是否必须调整数据库结构以适应特别复杂的对象图模型,因为ORM工具无法处理它呢? 极端情况下,ORM层的存在会扭曲数据库交互应该如何工作的整个视angular。 而不是处理持久化对象图的服务层,它可以转化为为每个域对象创build单独的数据访问层对象,就像它们独立生活一样。 我已经在不同程度上看到了这些情景。 也许它对ORM工具集没有经验。 也许这是一个不好的数据库结构。 我对此表示怀疑。 我所见过的一切都指出ORM解决scheme不足。

如果您接受我的观点:数据访问层是一个复杂性,极易出现性能瓶颈,为什么我应该考虑添加一个不能实现更less代码和分离目标的工具,同时又会影响我的应用程序的其他层的结构?

我对@Marcelo Cantos的答案做了彻底的回应,但是我将会总结使用ORM的主要好处。

持久性无知(PI)和域驱动devise(DDD)

ORM完全适合这两种总体devise模式。 对于正确的面向对象devise,您将使用分层和多态对象来表示数据在应用程序中的stream动方式。 有了一个好的devise,你会频繁地争取POCO对象(普通的老C / C#对象,我已经看到了这个词的其他定义),因为如果我有一个人有一个列表我应该有这个。 我不应该有一个DataTable的人和一个DataTable的地址,我总是强迫我的应用程序工作。 除此之外,我不需要将大量数据库特定逻辑与我的对象绑定,为什么我的人的FirstName字段需要像[ColumnName("First_Name")] [Length(255)] [DataType(Types.Nvarchar)]还是其他任何疯狂的属性或代码,它们定义了有多less数据库存在被迫进入我的域devise?

less写手写代码

当您删除需要为应用程序中的每个对象编写select,插入,更新和删除语句以将其保存到数据库时,写入的代码行数会大量减less。 这使得你只需要编写查询意味着什么。 另外,许多ORM(比如NHibernate)将会包含一个在SQL之上的语言,这个语言被定义为更加互动,并且可以在语法上更less。

最充分的时间是考虑一个应用程序有一个UserTable链接到每一个单一的对象,由于某种原因,你必须改变主键名或types。 在这一点上,你可能需要改变数据库中的每一个存储过程,在一个正确实现的ORM解决scheme中,你只需要改变一些configuration映射(或者可能没有),然后就完成了。

使用ORM工具的基本好处是便于在多层应用程序中将业务逻辑从数据访问中分离出来。 如果您可以成功地构build一个响应数据库更改的数据访问层(通过更改映射),那么在进行更改时就不得不使用整体代码。

分层通常是3层或n层应用程序的目标,ORM是一个很好的方法。

主要驱动程序:pipe理/维护的整体代码较less

我没有看到任何人解决的问题是ORM应该解决什么问题。 ORMs应该解决的问题是处理应用程序端代码和数据库之间的断开以及开发人员在数据库问题上花费的时间。 数据库和应用程序代码之间的断开是指必须运行应用程序才能意识到引用了无效列的代码。 ORM应该缩小这个差距,使数据库,应用程序和数据在一起移动; 我编译我知道代码不同步(只要我保持我的模型最新)。

我已经使用了3种不同的ORM(EF,LINQ to SQL和N-Hibernate),用于从小型到大型企业应用程序的各种规模的项目。 另外,对于使用ADO.NET来说,我并不陌生。 数据读取器和映射到类,数据集和各种生成工具。 我在3个不同的ORM中看到了好的和坏的实现,没有ORM方法。 我见过的大多数问题都回到了反模式,而不是理解特定ORM的局限性。 以下文章( 在N层应用程序中避免反模式 )在突出显示许多这些反模式方面做得很好。 即使是最好的软件架构师,开发人员和聪明人,在他们发现devise模式或某些devise原则的价值以解决反模式产生的问题之前,他们也参与了他们的反模式的分享。

不知道你的工具是另一个问题,因为这是性能问题的来源。显然很多人不确定dynamicSQL和存储过程在执行数据库逻辑(如CRUD操作)方面的性能差异。 那么判决是在,dynamic命令被certificate是一样有效的存储过程的同行。 两者都有优点和缺点。 关键是知道他们是什么,并select适当的情况下select。

如果我从ORM中得到的所有东西都是为了使我不需要维护存储过程,那对我来说就足够了。

然而,还有更多的事情是,一个ORM允许我们免于像人质一样的握手数据库模式强加在我们的代码的devise上。 当我可以像现实世界一样build模我的代码时,事情就简单得多了,而且我仍然可以用一个(好的)ORM来做到这一点,这有助于(和隐藏)一个潜在的难看的遗留数据库与可怕的命名约定映射到一个结构意味着一些东西,而不是在意这样的冗余代码:

 using (SqlWrapper sql = SqlWrapper.Create(WrapperConnectionType.ShopNet, "sStore_InsertUpdate_v4")) { sql.AddParameter("StoreId", StoreId); sql.AddParameter("StoreName", StoreName); sql.AddParameter("Region", Region); sql.AddParameter("PostCode", PostCode); sql.AddParameter("Tel", Tel); sql.AddParameter("Email", Email); sql.AddParameter("RegionId", RegionId); sql.AddParameter("MapImage", MapImage); sql.AddParameter("ShopImage", ShopImage); sql.AddParameter("Visible", Visible ? 'Y' : 'N'); sql.AddParameter("TazEnabled", TasEnabled ? 'Y' : 'N'); sql.AddParameter("Latitude", Latitude); sql.AddParameter("Longitude", Longitude); sql.AddParameter("StoreOpeningDate", OpeningDate); sql.AddParameter("CustomerNo", CustomerNo); sql.AddParameter("ReserveAtStoreEnabled", RasEnabled); sql.AddParameter("OpeningHoursId", OpeningHours.Id); sql.AddParameter("AddressLine1", AddressLine1); sql.AddParameter("AddressLine2", AddressLine2); sql.AddParameter("City", City); sql.AddParameter("District", District); sql.AddParameter("County", County); sql.AddParameter("CountryCode", CountryCode); sql.AddParameter("GoogleCategories", GoogleCategories); sql.AddParameter("GooglePlacesEnabled", GooglePlacesEnabled); sql.AddParameter("ManagerName", ManagerName); sql.AddParameter("MapZoomLevel", MapZoomLevel); sql.AddParameter("IpAddress", IpAddress); sql.AddParameter("FlibbleEnabled", FlibbleEnabled); sql.AddParameter("RegionalManager", RegionalManagerId); sql.AddParameter("ShutlLogin", ShutlLogin); sql.AddParameter("ShutlPassword", ShutlPassword); sql.Execute(); } 

并根除(但不要忘记)这些types的战斗:

 System.IndexOutOfRangeException at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name) 

对我来说,这个论点不一定比这个更复杂,我得到了一些好的东西,有些可怕的东西被拿走了。 Nuff说,下一个!

没有人认为ORM是一切传统的100%替代品。 它不会是编写复杂报表的好工具,因为它使用SP和视图,并使用NHibernate本地调用它们,并将它们自动映射到您的c#实体。 很好,仍然是SQL的所有function,没有蹩脚的映射代码。

走进0s&1s的黑暗中,来到有益健康的c#weltanschauung。

你得到的1个好处是你的数据层与对象层是分开的,所以当你有多个依赖于这个层的应用程序/项目时,你在ORM中改变一次,并且所有引用对象层的应用程序/项目都不必改变,如果没有它,你将不得不在所有项目中进行修改。