ele*_*ype 37 java sql repository-pattern mybatis jooq
我正在开发一个新的Java Web应用程序,我正在探索新的方法(对我来说是新的!)来保存数据.我主要有JPA和Hibernate的经验,但除了简单的情况,我认为这种完整的ORM会变得非常复杂.另外,我不喜欢和他们一起工作.我正在寻找一种新的解决方案,可能更接近SQL.
我正在调查的解决方案:
但与Hibernate相比,我有两个使用案例,我担心这些解决方案.我想知道这些用例的推荐模式是什么.
Person实体.
Person有一个相关的Address实体.
Address有一个相关的City实体.
City实体有一个name属性.从人员实体开始,访问城市名称的完整路径是:
person.address.city.name
Run Code Online (Sandbox Code Playgroud)
现在,假设我PersonService使用以下方法从a加载Person实体:
public Person findPersonById(long id)
{
// ...
}
Run Code Online (Sandbox Code Playgroud)
使用Hibernate,Person可以根据需要延迟加载与之关联的实体,因此可以访问person.address.city.name并确保我可以访问此属性(只要该链中的所有实体都不可为空).
但是使用我正在研究的3种解决方案中的任何一种,它都会更复杂.有了这些解决方案,有哪些推荐的模式来处理这个用例?在前面,我看到3种可能的模式:
可以通过所使用的SQL查询急切地加载所有必需的关联子孙实体.
但是我在这个解决方案中看到的问题是,可能还有一些其他代码需要从实体访问其他实体/属性路径Person.例如,可能需要访问一些代码person.job.salary.currency.如果我想重用findPersonById()我已经拥有的方法,那么SQL查询将需要加载更多信息!不仅是相关address->city实体,还包括相关job->salary实体.
现在如果还有10个其他地方需要从人员实体开始访问其他信息呢?我是否应该急切地加载所有可能需要的信息?或者可能有12种不同的服务方法来加载一个人实体?:
findPersonById_simple(long id)
findPersonById_withAdressCity(long id)
findPersonById_withJob(long id)
findPersonById_withAdressCityAndJob(long id)
...
Run Code Online (Sandbox Code Playgroud)
但是每当我使用一个Person实体时,我就必须知道装载它的是什么以及什么没有...它可能非常麻烦,对吧?
在实体的getAddress()getter方法中Person,是否可以检查地址是否已经加载,如果没有,则懒得加载它?这是现实生活中常用的模式吗?
是否有其他模式可用于确保我可以从加载的模型访问我需要的实体/属性?
我希望能够Person使用这个PersonService方法保存实体:
public void savePerson(Person person)
{
// ...
}
Run Code Online (Sandbox Code Playgroud)
如果我有一个Person实体并且我改变person.address.city.name了其他的东西,我怎样才能确保在City保存时保留实体修改Person?使用Hibernate,可以很容易地将保存操作级联到关联的实体.我正在调查的解决方案怎么样?
我是否应该使用某种脏标志来了解在保存此人时还必须保存哪些相关实体?
是否有任何其他已知模式可用于处理此用例?
更新:有一个讨论有关在JOOQ论坛这个问题.
Luk*_*der 29
你的许多问题的答案很简单.你有三个选择.
使用您提到的三个以SQL为中心的工具之一(MyBatis,jOOQ,DbUtils).这意味着您应该停止考虑OO域模型和对象关系映射(即实体和延迟加载).SQL是关于关系数据的,而RBDMS非常擅长计算"急切获取"多个连接结果的执行计划.通常,甚至不需要过早的缓存,如果你确实需要缓存偶尔的数据元素,你仍然可以使用像EhCache这样的东西
不要使用任何以SQL为中心的工具并坚持使用Hibernate/JPA.因为即使你说你不喜欢Hibernate,你也在"思考Hibernate".Hibernate非常擅长将对象图持久化到数据库中.这些工具都不能像Hibernate一样被迫工作,因为他们的使命是别的.他们的任务是运行SQL.
采用完全不同的方式,选择不使用关系数据模型.其他数据模型(例如图表)可能更适合您.我把它作为第三种选择,因为你可能实际上没有那种选择,而且我对替代模型没有太多的个人经验.
请注意,您的问题并非专门针对jOOQ.尽管如此,使用jOOQ,您可以通过外部工具(如ModelMapper)将平面查询结果(从连接表源生成)的映射外部化到对象图.关于ModelMapper用户组的这种集成,有一个有趣的持续线索.
(免责声明:我为jOOQ背后的公司工作)
leo*_*loy 28
当不使用真正的ORM时,这种问题是典型的,并且没有银弹.一个简单的设计方法,对我来说,对于iBatis(myBatis)的(不是很大的)webapp来说,是使用两层来实现持久性:
一个哑的低层:每个表都有它的Java类(POJO或DTO),其中的字段直接映射到表列.假设我们有一个PERSON表格,其中一个ADDRESS_ID字段指向一个ADRESS表格; 然后,我们有一个PersonDb类,只有一个addressId(整数)字段; 我们没有 personDb.getAdress()方法,只有平原personDb.getAdressId().这些Java类非常愚蠢(他们不了解持久性或相关类).相应的PersonDao类知道如何加载/持久化此对象.使用iBatis + iBator(或MyBatis + MYBatisGenerator)等工具可以轻松创建和维护该层.
包含富域对象的更高级别的层:每个层通常是上述POJO 的图形.这些类还具有通过调用相应的DAO来加载/保存图形(可能是懒惰,可能带有一些脏标志)的智能.但重要的是,这些富域对象不会一对一地映射到POJO对象(或DB表),而是映射到域用例.确定每个图形的"大小"(它不会无限增长),并且从外部像特定类一样使用.所以,并不是你有一个丰富的Person类(有一些相关对象的不确定图),使用的是几个用例或服务方法; 相反,你有几个富人阶级,PersonWithAddreses, PersonWithAllData...每一个包裹某个十分有限的图形,有自己的持久性逻辑.这可能看起来效率低或笨拙,并且在某些情况下可能会这样,但是当您需要保存完整的对象图形时,用例实际上是有限的.
另外,对于像表格报告这样的东西(特定的SELECTS返回一堆要显示的列),你不会使用上面的东西,而是直接和愚蠢的POJO(甚至可能是地图)
在这里查看我的相关答案
Gle*_*est 11
持久性方法
从简单/基本到复杂/丰富的解决方案范围是:
您试图实施前两个级别之一.这意味着将焦点从对象模型转移到SQL.但是您的问题要求将涉及对象模型的用例映射到SQL(即ORM行为).您希望从前三个级别中的一个级别添加第三级功能.
我们可以尝试在Active Record中实现此行为.但是这需要将丰富的元数据附加到每个Active Record实例 - 涉及的实际实体,它与其他实体的关系,延迟加载设置,级联更新设置.这将使其有效地成为隐藏的映射实体对象.此外,对于用例1和2,jOOQ和MyBatis不会这样做.
如何实现您的请求?
将简单的ORM行为直接实现到对象中,作为框架或原始SQL/JDBC上的小型自定义层.
用例1:存储每个实体对象关系的元数据:(i)关系是否应该是延迟加载(类级别)和(ii)是否发生了延迟加载(对象级别).然后在getter方法中,使用这些标志来确定是否进行延迟加载并实际执行此操作.
用例2:与用例1类似 - 自己动手.在每个实体中存储脏标志.针对每个实体对象关系,存储一个标志,描述是否应该级联保存.然后,当保存实体时,递归访问每个"保存级联"关系.写下发现的任何脏实体.
模式
优点
缺点
值得???
如果您具有相当简单的数据处理要求和具有较少实体数据的数据模型,则此解决方案很有效:
在过去的十年里,我使用的是JDBC,EJB实体bean,Hibernate,GORM以及最终的JPA(按此顺序).对于我当前的项目,我已经回到使用普通JDBC,因为重点是性能.所以我想要
数据模型在数据字典中定义; 使用模型驱动的方法,生成器创建辅助类,DDL脚本等.数据库上的大多数操作都是只读的.只有少数用例写.
问题1:抚养孩子
系统建立在一个用例上,我们有一个专用的 SQL语句来获取给定用例/请求的所有数据.一些SQL语句大于20kb,它们连接,使用以Java/Scala编写的存储函数计算,排序,分页等,结果直接映射到数据传输对象,后者又被送入视图(在应用程序层中没有进一步处理).因此,数据传输对象也是特定于用例的.它只包含给定用例的数据(仅此而已,仅此而已).
由于结果集已经"完全连接",因此不需要延迟/急切提取等.数据传输对象已完成.不需要缓存(数据库缓存很好); 例外:如果结果集很大(大约50 000行),则数据传输对象用作缓存值.
问题2:储蓄
在控制器从GUI合并回来之后,再次存在一个保存数据的特定对象:基本上是层次结构中具有状态(新的,删除的,修改的......)的行.这是一种手动迭代,可以将数据保存在层次结构中:使用生成SQL insert或update命令的一些辅助类来保留新的或修改的数据.至于删除的项目,这被优化为级联删除(PostgreSql).如果要删除多行,也会将其优化为单个delete ... where id in ...语句.
同样这是用例特定的,因此它不涉及一般的approch.它需要更多代码行,但这些是包含优化的行.
到目前为止的经验
相关信息:
更新:接口
如果Person逻辑数据模型中存在实体,则有一个类来持久化Person实体(用于CRUD操作),就像JPA实体一样.
但中心思想是,没有单一的Person从使用情况/查询/业务逻辑的角度来看实体:每个服务方法定义了它自己的概念Person,它仅包含正是由使用情况下所需的值.没有更多,没有更少.我们使用Scala,因此许多小类的定义和使用非常有效(不需要setter/getter样板代码).
例:
class GlobalPersonUtils {
public X doSomeProcessingOnAPerson(
Person person, PersonAddress personAddress, PersonJob personJob,
Set<Person> personFriends, ...)
}
Run Code Online (Sandbox Code Playgroud)
被替换为
class Person {
List addresses = ...
public X doSomeProcessingOnAPerson(...)
}
class Dto {
List persons = ...
public X doSomeProcessingOnAllPersons()
public List getPersons()
}
Run Code Online (Sandbox Code Playgroud)
通过使用特定的情况下Persons,Adresses等:在这种情况下,Person已经汇集了所有相关数据.需要更多类,但不需要传递JPA实体.
请注意,此处理是只读的,结果由视图使用.示例:City从人员列表中获取不同的实例.
如果数据发生了变化,这是另一个用例:如果更改了某个城市,则会使用其他服务方法处理,并再次从数据库中提取该人员.
| 归档时间: |
|
| 查看次数: |
17319 次 |
| 最近记录: |