DAO实施的最佳实践

use*_*644 10 java hibernate jpa

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

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

例如:

业务逻辑接口

public interface UserBLInt {

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

}
Run Code Online (Sandbox Code Playgroud)

DAO接口

public interface UserDAOInt {

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

}
Run Code Online (Sandbox Code Playgroud)

业务逻辑实现

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);
      }
   } 

}
Run Code Online (Sandbox Code Playgroud)

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();
      }
}
Run Code Online (Sandbox Code Playgroud)

这只是一个简单的例子,但我的问题是,在DAO实现中看起来"冗余"进行另一次检查以确保User在添加a之前不是null Right,但是,作为程序员,我看到了一个机会空指针.

显然我可以在实体管理器上调用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();
}
Run Code Online (Sandbox Code Playgroud)

acd*_*ior 8

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

你的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();
   } 
}
Run Code Online (Sandbox Code Playgroud)

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

(1)锁定用户直到交易结束; 或(2)让它成为.

1:如果用户被这两个命令搞砸的风险很高(比如你的系统有很多并发用户),如果发生这个问题就很大,那么考虑锁定user整个交易; 也就是说,如果我开始使用它,其他任何事务都不会改变它.

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

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


更新:

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


JB *_*zet 3

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);
    } 
}
Run Code Online (Sandbox Code Playgroud)

这也显示了设计中的一个基本问题:事务不能在 DAO 层启动。它们必须从服务层启动。这使得 DAO 方法可以重用,并且服务方法可以在单个事务中使用多个 DAO。不这样做将导致服务贫乏,这些服务将调用包含整个业务逻辑的 DAO 方法,如代码所示。

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