繁体   English   中英

DAO实施的最佳实践

[英]Best Practices for DAO Implementation

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

我实现的一件事是围绕我的DAO实现的“包装器”以进行验证。 包装器接受我的DAO实例作为构造函数参数,并实现与DAO类似的接口,但抛出的异常类型除外。

例如:

业务逻辑接口

public interface UserBLInt {

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

}

DAO接口

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实现中似乎“冗余”进行另一次检查以确保在添加Right之前User不是null,但是,作为程序员,我看到了机会对于空指针。

显然,我可以在实体管理器上调用find之后添加一个空检查,如果返回null则抛出异常,但这是将DAO包装在业务逻辑实现中的全部目的,事先做好所有验证工作,以便DAO代码是干净的,并且不需要在空检查或很多逻辑方面做很多事情。 既然我有DAO的包装器,那么在DAO中进行空检查仍然是一个好主意吗? 我在理论上知道可以在业务逻辑调用和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 知道业务逻辑。 它知道为auser分配权利是为权利列表添加权利 (似乎很明显,分配权利只是将其添加到列表中,但想象一下,将来可能会变得更加复杂,对其他用户和权利产生副作用等等。)

所以这个分配不属于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:如果用户被这两个命令搞砸的风险很高(比如你的系统有很多并发用户),如果发生这个问题就很大,那么考虑将user锁定在整个交易中; 也就是说,如果我开始使用它,其他任何事务都不会改变它。

  • 另一个(更好的)解决方案是,如果您的系统具有吨并发用户,那就是“设计问题”,即重新设计您的设计,以便在业务事务中更改的内容具有更严格(更小)的范围 - 你的例子的范围足够小,所以这个建议可能没那么有意义,但只要考虑你的业务交易做了很多事情(然后让它做得少很多,每一个反过来,可能是解决方案)。 这是一个完整的话题,所以我不会在这里详细介绍,只要对此持开放态度。

2:另一种可能性,你会发现这是最常用的方法,如果你正在处理具有约束检查的SQL DB,例如UNIQUE ,只是让DAO异常被吹掉。 我的意思是,这将是一个如此罕见且几乎不可能发生的事情,你可以通过接受它来解决它可能发生,你的系统只会显示一个很好的消息,如“出了问题,请再试一次” - - 这只是基本成本与收益加权。


更新:

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

DAO在这里有太多的逻辑。 DAO的作用不是为用户分配权限。 这是业务逻辑。 DAO的作用是查找用户或查找权限。 DAO中的代码应该在服务中:

interface UserDAO {
    User findById(int userId);
    //...
}

interface RightDAO {
    Right findById(int rightId);
    // ...
}

public class UserBLImpl implements  UserBLInt {

    private UserDAO userDAO;
    private RightDAO rightDAO;

    // ...

    @Override
    public void assignRightToUser(int userId, int rightId) {
        User u = userDAO.findById(userId);
        Right right = rightDAO.findById(rightId);
        // do some null checks and throw exception
        user.addRight(right);
    } 
}

这也显示了设计中的一个基本问题:不能在DAO层启动事务。 它们必须在服务层启动。 这就是允许DAO方法可重用的服务方法,以及在单个事务中使用多个DAO的服务方法。 如果你的代码显示,那么不这样做会导致贫穷的服务调用包含整个业务逻辑的DAO方法。

这就是为什么像EJB或Spring这样的框架允许以声明方式划分事务:你不需要明确地启动和提交trannsactions,处理异常和回滚异常。 您所要做的就是使用注释将服务方法标记为事务性。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM