jjzjj

java - DAO 实现的最佳实践

coder 2024-03-03 原文

我一直在使用 DAO 模式来访问我一直在构建的应用程序中的持久层。

我已经实现的其中一件事是围绕我的 DAO 实现进行“包装”以进行验证。包装器将我的 DAO 实例作为构造函数参数,并实现与 DAO 类似的接口(interface),除了抛出的异常类型。

例如:

业务逻辑接口(interface)

public interface UserBLInt {

   private void assignRightToUser(int userId, int rightId) throws SomeAppException;

}

DAO 接口(interface)

public interface UserDAOInt {

   private void assignRightToUser(int userId, int rightId) throws SomeJPAExcption;

}

业务逻辑实现

public class UserBLImpl implements  UserBLInt {

   private UserDAOInt userDAO;

   public UserBLImpl(UserDAOInt userDAO){
      this.userDAO = userDAO;
   }

   @Override
   private void assignRightToUser(int userId, int rightId) throws SomeAppException{
      if(!userExists(userId){
         //throw an exception
      }
      try{
         userDAO.assignRightToUser(userId, rightId);
      } catch(SomeJpAException e){
        throw new SomeAppException(some message);
      }
   } 

}

DAO 实现

public class UserDAOImpl implements UserDAOInt {
      //......
      @Override
      public void assignRightToUser(int userId, int rightId){
         em.getTransaction().begin();
         User userToAssignRightTo = em.find(User.class, userId);
         userToAssignRightTo.getRights().add(em.find(Right.class, rightId));
         em.getTransaction().commit();
      }
}

这只是一个简单的示例,但我的问题是,在 DAO 实现中进行另一项检查以确保在添加 >对,但是,作为一名程序员,我看到了空指针的机会。

显然,我可以在实体管理器上调用 find 之后添加空检查,如果返回空则抛出异常,但这就是将 DAO 包装在业务逻辑实现中的全部目的,以便预先完成所有验证工作,因此 DAO 代码是干净的,并且根本不需要在空检查或逻辑方面做太多事情。因为我有 DAO 的包装器,所以在 DAO 中进行 null 检查仍然是个好主意吗?我知道理论上可以在业务逻辑调用和 dao 调用之间删除对象,这不太可能,而且检查 null 似乎是重复的工作。对于这种情况,最佳做法是什么?

编辑:

这看起来像是一个合适的 DAO 修改吗?

public EntityManager beginTransaction(){
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction entityTransaction = entityManager.getTransaction();
    entityTransaction.begin();
    return entityManager;
}

public void rollback(EntityManager entityManager){
    entityManager.getTransaction().rollback();
    entityManager.close();
}

public void commit(EntityManager entityManager){
    entityManager.getTransaction().commit();
    entityManager.close();
}

最佳答案

DAO,虽然现在有点通用和过度使用的术语,但(通常)意在抽象数据层(因此除其他好处外,它可以在不接触应用程序其余部分的情况下进行更改)。

不过,看起来您的 DAO 实际上所做的不仅仅是抽象数据层。在:

public class UserDAOImpl implements UserDAOInt {
  ......
  @Override
  public void assignRightToUser(int userId, int rightId){
     em.getTransaction().begin();
     User userToAssignRightTo = em.find(User.class, userId);
     userToAssignRightTo.getRights().add(em.find(Right.class, rightId));
     em.getTransaction().commit();
  }
}

您的 DAO 知道业务逻辑。它知道向用户分配权限就是在权限列表中添加权限。 (似乎很明显,分配权限只是将其添加到列表中,但想象一下,这在未来可能会变得更加复杂,并对其他用户和权限等产生副作用。)

所以这个赋值不属于DAO。应该在业务层。您的 DAO 应该只有类似 userDAO.save(user) 的东西,业务层在完成设置权限和内容后会调用它。


另一个问题:您的交易过于本地化。这几乎不是交易。

请记住事务是一个业务单元,您在其中执行原子(“批处理”)业务工作,而不仅仅是因为 EntityManager 使您打开的事务。

我的意思是,从代码的角度来说,应该是业务层主动开启事务,而不是DAO(实际上,DAO应该有“开启事务”的服务——方法——会被调用)。

然后考虑在业务层中打开事务:

public class UserBLImpl implements  UserBLInt {
   ...
   @Override
   private void assignRightToUser(int userId, int rightId) throws SomeAppException{
      userDAO.beginTransaction(); // or .beginUnitOfWork(), if you wanna be fancy

      if(!userExists(userId){
         //throw an exception
         // DON'T FORGET TO ROLLBACK!!! before throwing the exception
      }
      try{
         userDAO.assignRightToUser(userId, rightId);
      } catch(SomeJpAException e){
        throw new SomeAppException(some message);
      }

      userDAO.commit();
   } 
}

现在,对于您的问题:数据库在 userExists() ifuserDAO 之间变化的风险仍然存在。 .. 但你有选择:

(1)锁定用户,直到交易结束;或 (2) 顺其自然。

1:如果用户在这两个命令之间被搞砸的风险很高(比如你的系统有很多并发用户)并且如果这个问题发生了那将是一件大事,那么考虑锁定 整个交易的用户;也就是说,如果我开始使用它,则没有其他事务可以更改它。

  • 如果您的系统有大量并发用户,另一个(更好的)解决方案是“设计问题”,即重新设计您的设计,以便业务交易中的更改具有更严格(更小)的范围- 你的例子的范围足够小,所以这个建议可能没有多大意义,但只要考虑你的业务交易做的更多(然后让它做的少得多,每个依次,可能是解决方案)。这本身就是一个完整的主题,所以我不会在这里详细介绍,请您保持开放的心态。

2:另一种可能性,你会认为这是最常见的方法,如果你正在处理具有约束检查的 SQL DB,例如 UNIQUE,只是让 DAO 异常吹走。我的意思是,这将是一件非常罕见且几乎不可能发生的事情,您不妨通过接受它可能发生来处理它,您的系统只会显示一条好消息,例如“出了点问题,请重试”- - 这只是基本成本与 yield 的比重。


更新:

程序化事务处理可能很棘手(难怪使用声明式替代方案,例如 Spring 和 EJB/CDI)。尽管如此,我们并不总是有机会使用它们(也许您正在调整遗留系统,谁知道呢)。所以这里有一个建议:https://gist.github.com/acdcjunior/94363ea5cdbf4cae41c7

关于java - DAO 实现的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29525312/

有关java - DAO 实现的最佳实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  6. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  7. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  8. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

随机推荐