[英]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()
if
和userDAO
持久存在的情况下,数据库更改的风险仍然存在...但您有选择:
(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.