我一直在使用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实现中似乎“冗余”进行另一项检查以确保在添加User
之前Right
不为空但是,作为程序员,我看到了一个空指针的机会。
显然,我可以在实体管理器上调用find后添加一个null检查,如果返回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();
}
答案 0 :(得分:7)
但是,看起来你的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:如果用户在这两个命令之间混乱的风险很高(比如你的系统有很多并发用户),如果发生这个问题就很重要,那么考虑锁定{{1整个交易;也就是说,如果我开始使用它,其他任何交易都不会改变它。
2:另一种可能性,如果您正在处理具有约束检查的SQL DB(例如user
),您将认为这是最常用的方法,只是让DAO异常被吹走。我的意思是,这将是一个如此罕见且几乎不可能发生的事情,你可以通过接受它来解决它可能发生,你的系统只会显示一个很好的消息,如“出了问题,请再试一次” - - 这只是基本成本与收益加权。
更新
程序化事务处理可能很棘手(难怪使用声明性替代方法,例如Spring和EJB / CDI)。尽管如此,我们并不总是乐于使用它们(也许你正在调整遗留系统,谁知道)。所以这是一个建议:https://gist.github.com/acdcjunior/94363ea5cdbf4cae41c7
答案 1 :(得分: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);
}
}
这也显示了设计中的一个基本问题:不能在DAO层启动事务。它们必须在服务层启动。这就是允许DAO方法可重用的原因,以及在单个事务中使用多个DAO的服务方法。如果你的代码显示,那么不这样做会导致服务不足,调用包含整个业务逻辑的DAO方法。
这就是为什么像EJB或Spring这样的框架允许以声明方式划分事务:你不需要明确地启动和提交事务,处理异常和回滚异常。您所要做的就是使用注释将服务方法标记为事务性。
答案 2 :(得分:0)