数据访问层的设计模式

2024-01-05

我有一个使用数据库(MongoDB)来存储信息的应用程序。过去,我使用了一个充满静态方法的类来保存和检索数据,但后来我意识到这不是非常面向对象或面向未来的。

尽管我不太可能更改数据库,但我宁愿拥有一些不会将我与 MongoDB 联系得太紧密的东西。我还希望能够通过从数据库刷新缓存对象的选项来缓存结果,但这不是必需的,可以在其他地方完成。

我研究过数据访问对象,但它们似乎定义得不是很好,而且我找不到任何好的实现示例(在 Java 或类似语言中)。我还有许多一次性案例,例如查找用于选项卡补全的用户名,这似乎不太适合并且会使 DAO 变得庞大且臃肿。

是否有任何设计模式可以方便获取和保存对象而又不会过于特定于数据库?好的实现示例会很有帮助(最好是用 Java 实现)。


正如您所指出的,Java 中数据存储的常见方法根本不是面向对象的。这本身既不好也不好:“面向对象”既不是优点也不是缺点,它只是众多范例之一,有时有助于良好的架构设计(有时则没有)。

Java 中的 DAO 通常不是面向对象的原因正是您想要实现的目标 - 放松对数据库的依赖。在一种设计更好、允许多重继承的语言中,这当然可以通过面向对象的方式非常优雅地完成,但对于 Java,这似乎比它值得的更麻烦。

从更广泛的意义上来说,非面向对象方法有助于将应用程序级数据与其存储方式解耦。这不仅(非)依赖于特定数据库的细节,而且还依赖于存储模式,这在使用关系数据库时尤其重要(不要让我开始谈论 ORM):您可以拥有一个精心设计的关系数据库模式由 DAO 无缝转换为应用程序 OO 模型。

所以,现在 Java 中的大多数 DAO 本质上就是你在开头提到的——类,充满静态方法。一个区别是,最好有一个静态“工厂方法”(可能在不同的类中),而不是使所有方法都静态,该方法返回 DAO 的(单个)实例,该实例实现特定的接口,由应用程序代码用来访问数据库:

public interface GreatDAO {
    User getUser(int id);
    void saveUser(User u);
}
public class TheGreatestDAO implements GreatDAO {
   protected TheGreatestDAO() {}
   ... 
}
public class GreatDAOFactory {
     private static GreatDAO dao = null;
     protected static synchronized GreatDao setDAO(final GreatDAO d) {
         final GreatDAO old = dao;
         dao = d;
         return old;
     }
     public static synchronized GreatDAO getDAO() {
         return dao == null ? dao = new TheGreatestDAO() : dao;
     }
}

public class App {
     void setUserName(final int id, final String name) {
          final GreatDAO dao =  GreatDAOFactory.getDao();
          final User u = dao.getUser(id);
          u.setName(name);
          dao.saveUser(u);
     }
}

为什么这样做而不是静态方法?那么,如果您决定切换到不同的数据库怎么办?当然,您将创建一个新的 DAO 类,实现新存储的逻辑。如果您使用静态方法,那么您现在必须检查所有代码,访问 DAO,并将其更改为使用您的新类,对吧?这可能是一个巨大的痛苦。如果您改变主意并想切换回旧数据库怎么办?

使用这种方法,您所需要做的就是更改GreatDAOFactory.getDAO()并使其创建不同类的实例,并且您的所有应用程序代码将使用新数据库而不进行任何更改。

在现实生活中,这通常不需要对代码进行任何更改即可完成:工厂方法通过属性设置获取实现类名称,并使用反射实例化它,因此,切换实现所需要做的就是编辑属性文件。实际上有框架——比如spring or guice– 为您管理这种“依赖注入”机制,但我不会详细介绍,首先,因为它确实超出了您的问题范围,而且,因为我不一定相信您从使用中获得的好处对于大多数应用程序来说,这些框架值得与它们集成。

与“静态”相反,这种“工厂方法”的另一个(可能更可能被利用)好处是可测试性。想象一下,您正在编写一个单元测试来测试您的逻辑App类独立于任何底层 DAO。您不希望它使用任何真正的底层存储,原因有几个(速度、必须设置它并随后清理、可能与其他测试发生冲突、DAO 中的问题可能污染测试结果、与App,实际上正在测试等)。

为此,您需要一个测试框架,例如Mockito,它允许您“模拟”任何对象或方法的功能,将其替换为具有预定义行为的“虚拟”对象(我将跳过详细信息,因为这又超出了范围)。因此,您可以创建这个虚拟对象来替换您的 DAO,并使GreatDAOFactory通过致电返回您的虚拟物品而不是真实物品GreatDAOFactory.setDAO(dao)测试前(并在测试后恢复)。如果您使用静态方法而不是实例类,这是不可能的。

还有一个好处,与我上面描述的切换数据库有点相似,是用附加功能“增强”您的 DAO。假设您的应用程序随着数据库中数据量的增长而变慢,并且您决定需要缓存层。实现一个包装类,它使用真实的 DAO 实例(作为构造函数参数提供给它)来访问数据库,并将其读取的对象缓存在内存中,以便可以更快地返回它们。然后你可以让你的GreatDAOFactory.getDAO实例化此包装器,以便应用程序利用它。

(这称为“委托模式”……似乎很麻烦,尤其是当您在 DAO 中定义了很多方法时:您必须在包装器中实现所有这些方法,甚至只改变其中一个的行为。或者,您可以简单地对 DAO 进行子类化,并以这种方式向其添加缓存。这将大大减少前期编码的乏味,但当您决定更改数据库时,或者更糟糕的是,可以选择切换时,可能会出现问题来回实施。)

“工厂”方法的一种同样广泛使用(但在我看来较差)的替代方法是制作dao所有需要它的类中的成员变量:

public class App {
   GreatDao dao;
   public App(final GreatDao d) { dao = d; }
}

这样,实例化这些类的代码需要实例化 DAO 对象(它仍然可以使用工厂)并将其作为构造函数参数提供。我上面提到的依赖注入框架通常会做类似的事情。

这提供了我之前描述的“工厂方法”方法的所有好处,但正如我所说,在我看来,它并不那么好。这里的缺点是必须为每个应用程序类编写一个构造函数,一遍又一遍地做同样的事情,而且在需要时无法轻松实例化类,并且有些失去了可读性:具有足够大的代码库,不熟悉代码的读者将很难理解使用了 DAO 的哪个实际实现、它是如何实例化的、它是否是单例、线程安全的实现、它是否保持状态或缓存任何内容,如何做出选择特定实现的决定等。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据访问层的设计模式 的相关文章

  • @RestController 没有 @ResponseBody 方法工作不正确

    我有以下控制器 RestController RequestMapping value base url public class MyController RequestMapping value child url method Req
  • 如何在 Antlr4 中为零参数函数编写语法

    我的函数具有参数语法 如下面的词法分析器和解析器 MyFunctionsLexer g4 lexer grammar MyFunctionsLexer FUNCTION FUNCTION NAME A Za z0 9 DOT COMMA L
  • Java:在 eclipse 中导出到 .jar 文件

    我正在尝试将 Eclipse 中的程序导出到 jar 文件 在我的项目中 我添加了一些图片和 PDF s 当我导出到 jar 文件时 似乎只有main已编译并导出 我的意愿是如果可能的话将所有内容导出到 jar 文件 因为这样我想将其转换为
  • GWT - 如何组织项目以拥有多个网页以及它们之间的导航

    我是 GET 的新手 顺便说一句 它给我留下了深刻的印象 并且发现它对于像我这样熟悉 C NET 桌面技术并愿意编写 Web 应用程序的人来说非常有吸引力 我根据 GWT Eclipse 向导生成的示例启动了自己的项目 该项目生成带有面板的
  • Spring RestTemplate 使用 cookie 遵循重定向

    最近我遇到了一个问题 我需要做一个GET请求远程服务 我假设使用一个简单的 servlet 并且 RestTemplate 返回Too many redirects 经过一番调查 似乎对指定远程服务发出的第一个请求实际上只是一个 302 重
  • Spring Data JPA 选择不同

    我有一个情况 我需要建立一个select distinct a address from Person a 其中地址是 Person 内的地址实体 类型的查询 我正在使用规范动态构建我的 where 子句并使用findAll Specifi
  • org.hibernate.QueryException:无法解析属性:文件名

    我正在使用休眠Criteria从列中获取值filename在我的桌子上contaque recording log 但是当我得到结果时 它抛出异常 org hibernate QueryException 无法解析属性 文件名 com co
  • 自动生成Flyway的迁移SQL

    当通过 Java 代码添加新模型 字段等时 JPA Hibernate 的自动模式生成是否可以生成新的 Flyway 迁移 捕获自动生成的 SQL 并将其直接保存到新的 Flyway 迁移中 以供审查 编辑 提交到项目存储库 这将很有用 预
  • 是否可以通过编程方式查找 logback 日志文件?

    自动附加日志文件以支持电子邮件会很有用 我可以以编程方式设置路径 如以编程方式设置 Logback Appender 路径 https stackoverflow com questions 3803184 setting logback
  • 如何检测 Java 字符串中的 unicode 字符?

    假设我有一个包含 的字符串 我如何找到所有这些 un icode 字符 我应该测试他们的代码吗 我该怎么做呢 例如 给定字符串 A X 我想将其转换为 AYXY 我想对其他 unicode 字符做同样的事情 并且我不想将它们存储在某种翻译映
  • 生成的序列以 1 开头,而不是注释中设置的 1000

    我想请求一些有关 Hibernate 创建的数据库序列的帮助 我有这个注释 下面的代码 在我的实体类中 以便为合作伙伴表提供单独的序列 我希望序列以 1000 开头 因为我在部署期间使用 import sql 将测试数据插入数据库 并且我希
  • 从 GitHub 上托管的 Spring Cloud Config Server 访问存储库的身份验证问题

    我在 GitHub 上的存储库中托管配置 如果我将回购公开 一切都好 但如果我将其设为私有 我将面临 org eclipse jgit errors TransportException https github com my user m
  • 在另一个模块中使用自定义 gradle 插件模块

    我正在开发一个自定义插件 我希望能够在稍后阶段将其部署到存储库 因此我为其创建了一个独立的模块 在对其进行任何正式的 TDD 之前 我想手动进行某些探索性测试 因此 我创建了一个使用给定插件的演示模块 到目前为止 我发现执行此操作的唯一方法
  • 如何避免 ArrayIndexOutOfBoundsException 或 IndexOutOfBoundsException? [复制]

    这个问题在这里已经有答案了 如果你的问题是我得到了java lang ArrayIndexOutOfBoundsException在我的代码中 我不明白为什么会发生这种情况 这意味着什么以及如何避免它 这应该是最全面的典范 https me
  • Java:如何为山区时间创建 TimeZone 对象?

    必须不禁用夏令时 嗯 在这个清单 http en wikipedia org wiki List of tz database time zones在 zoneinfo 时区名称中 有很多声称是 山地时间 找到最适合您想要的那个 然后使用它
  • 如何在 Spring 属性中进行算术运算?

  • java库维护数据库结构

    我的应用程序一直在开发 所以偶尔 当版本升级时 需要创建 更改 删除一些表 修改一些数据等 通常需要执行一些sql代码 是否有一个 Java 库可用于使我的数据库结构保持最新 通过分析类似 db structure version 信息并执
  • 使用按钮作为列表的渲染器

    我想使用一个更复杂的渲染器 其中包含列表的多个组件 更准确地说 类似于this https stackoverflow com questions 10840498 java swing 1 6 textinput like firefox
  • 如何重新启动死线程? [复制]

    这个问题在这里已经有答案了 有哪些不同的可能性可以带来死线程回到可运行状态 如果您查看线程生命周期图像 就会发现一旦线程终止 您就无法返回到新位置 So 没有办法将死线程恢复到可运行状态 相反 您应该创建一个新的 Thread 实例
  • 如何使用play框架上传多个文件?

    我在用play framework 2 1 2 使用java我正在创建视图来上传多个文件 我的代码在这里 form action routes upload up enctype gt multipart form data

随机推荐