java DAO 实施的最佳实践
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29525312/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best Practices for DAO Implementation
提问by user1154644
I have been using a DAO pattern to provide access to my persistence tier in an application that I have been building.
我一直在使用 DAO 模式来提供对我一直在构建的应用程序中的持久层的访问。
One of the things that I have implemented is a 'wrapper' around my DAO implementations for purposes of validation. The wrapper takes in an instance of my DAO as a constructor parameter, and implements a similar interface as the DAO, with the exception being the types of exceptions thrown.
我已经实现的一件事是围绕我的 DAO 实现的“包装器”,以进行验证。包装器接受我的 DAO 的一个实例作为构造函数参数,并实现与 DAO 类似的接口,例外是抛出的异常类型。
For example:
例如:
Business Logic Interface
业务逻辑接口
public interface UserBLInt {
private void assignRightToUser(int userId, int rightId) throws SomeAppException;
}
DAO Interface
DAO接口
public interface UserDAOInt {
private void assignRightToUser(int userId, int rightId) throws SomeJPAExcption;
}
Business Logic Implementation
业务逻辑实现
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 Implementation
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();
}
}
This is just a quick example, but my question is, it seems 'redundant' within the DAO implementation to do another check to be sure that the User
isn't null before adding a Right
, but, as a programmer, I see an opportunity for a null pointer.
这只是一个简单的例子,但我的问题是,在 DAO 实现中进行另一次检查以确保User
在添加 a 之前不为空似乎是“冗余的” Right
,但是,作为一名程序员,我看到了一个机会空指针。
Obviously I could add a null check after calling find on the entity manager and throw an exception if null is returned, but that's the whole purpose of having the DAO wrapped in a business logic implementation, to do all the validation work beforehand, so that the DAO code is clean and doesn't have to do much in the way of null checks or much logic at all. Since I have the wrapper around the DAO, is it still a good idea to do the null checking anyway in the DAO? I know in theory the object could be deleted between the business logic call and the dao call, it's just unlikely, and checking for null seems like duplicated work. What is the best practice for a situation like this?
显然,我可以在实体管理器上调用 find 后添加一个空检查,如果返回空则抛出异常,但这就是将 DAO 包装在业务逻辑实现中的全部目的,预先完成所有验证工作,以便DAO 代码很干净,根本不需要做很多空检查或很多逻辑的工作。既然我有围绕 DAO 的包装器,那么无论如何在 DAO 中进行空检查仍然是个好主意吗?我知道理论上可以在业务逻辑调用和 dao 调用之间删除对象,这不太可能,并且检查 null 似乎是重复的工作。对于这种情况,最佳做法是什么?
EDIT:
编辑:
Does this look like a suitable DAO modification?
这看起来像一个合适的 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();
}
回答by acdcjunior
DAOs, although a bit of a now generic and overused term, are (usually) meant to abtract the Data layer (so among other benefits, it could be changed without having to touch the rest of the app).
DAOs,虽然现在有点通用和过度使用的术语,但(通常)是为了抽象数据层(因此除其他好处外,它可以改变而无需触及应用程序的其余部分)。
It seems, though, your DAO is actually doing a lot more than just abstracting the data layer. In:
但是,看起来您的 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();
}
}
Your DAO knowsbusiness logic. It knows that assigning a right to auser is adding a right to the list of rights. (It may seem obvious that assigning a right is just adding it to a list, but imagine this could get, in the future, much more complicated, with side-effects to other users and rights and so on.)
您的 DAO了解业务逻辑。它知道为用户分配权限就是在权限列表中添加权限。(分配权限似乎很明显只是将其添加到列表中,但想象一下,这可能会在未来变得更加复杂,对其他用户和权限产生副作用等等。)
So this assigning does not belong in the DAO. It should be in the business layer. Your DAO should have just something like userDAO.save(user)
that the business layer would call once it is done setting the rights and stuff.
所以这个分配不属于 DAO。它应该在业务层。您的 DAO 应该具有类似于userDAO.save(user)
业务层在完成设置权限和内容后会调用的内容。
Another problem: Your transaction is too localized. It is almost not a transaction.
另一个问题:您的交易过于本地化。这几乎不是交易。
Remember the transaction is a business unit, something you do atomic (a "batch" of) business work within, not just something you open because the EntityManager
makes you.
请记住,事务是一个业务单位,是您在其中执行原子(“一批”)业务工作的事物,而不仅仅是您打开的事物,因为它EntityManager
使您成为可能。
What I mean, in terms of code, is that the business layer should have the initiative of opening the transaction, not the DAO (actually, the DAO should have "open a transaction" as a service -- method -- that would be called).
我的意思是,就代码而言,业务层应该拥有打开交易的主动权,而不是 DAO(实际上,DAO 应该将“打开交易”作为一种服务——方法——将被调用)。
Consider, then, opening the transaction in the Business Layer:
然后考虑在业务层打开交易:
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();
}
}
Now, to your question: that risk of the DB changing between the userExists()
if
and the userDAO
persisting it still exists... but you have options:
现在,对于您的问题:DB 在userExists()
if
和userDAO
持久化之间变化的风险仍然存在......但您有选择:
(1) Lock the user until the transaction is over; or (2) Let it be.
(1) 锁定用户,直到交易结束;或 (2) 顺其自然。
1: If the risk of the user being messed up with between those two commands is high (say your system has a lot of concurrent users) and if that problem happened it would be big deal, then consider locking the user
for the whole transaction; that is, if I begin working with it, no other transaction may change it.
1:如果用户在这两个命令之间被搞乱的风险很高(比如你的系统有很多并发用户),如果出现这个问题就很严重,那么考虑锁定user
整个事务;也就是说,如果我开始使用它,任何其他事务都不能改变它。
- Another (better) solution to that if your system has tons os concurrent users is to "design the problem away", that is, rework your design so that what is changed in a business transaction has a more strict (smaller) scope -- the scope of your example is small enough, so this advice may not make so much sense, but just consider your business transaction did a whole lot more (then making it do a whole lot less, each in its turn, could be the solution). This is a whole topic all by itself, so I won't go into more details here, just keep your mind open to that.
- 如果您的系统有大量 os 并发用户,另一个(更好)的解决方案是“设计问题”,即重新设计您的设计,以便业务事务中的更改具有更严格(更小)的范围——你的例子的范围足够小,所以这个建议可能没有多大意义,但只要考虑到你的业务交易做得更多(然后让它做得更少,每个轮到它,可能是解决方案)。这本身就是一个完整的话题,所以我不会在这里详细介绍,请保持开放的心态。
2: Another possibility, and you will figure this is the most common approach, if you are dealing with a SQL DB that has constraint checks, such as UNIQUE
, is just to let the DAO exception blow away. I mean, it will be such a rare and near-impossible thing to happen that you may as well deal with it by just accepting it can happen and your system will just display a nice message like "Something went wrong, please try again" -- this is just basic costs vs. benefits weighting.
2:另一种可能性,您会认为这是最常见的方法,如果您正在处理具有约束检查的 SQL DB,例如UNIQUE
,只是让 DAO 异常消失。我的意思是,这将是一种罕见且几乎不可能发生的事情,您不妨接受它可能发生的情况来处理它,而您的系统只会显示一条很好的消息,例如“出现问题,请再试一次”- - 这只是基本成本与收益权重。
Update:
更新:
Programatic transaction handling can be tricky (no wonder declarative alternatives, such as Spring and EJB/CDI, are so used). Still, we don't always have the luxury of using them (maybe you are adapting a legacy system, who knows). So here's a suggestion: https://gist.github.com/acdcjunior/94363ea5cdbf4cae41c7
程序化事务处理可能很棘手(难怪会如此使用声明式替代方案,例如 Spring 和 EJB/CDI)。尽管如此,我们并不总是有幸使用它们(也许你正在适应一个遗留系统,谁知道)。所以这里有一个建议:https: //gist.github.com/acdcjunior/94363ea5cdbf4cae41c7
回答by JB Nizet
The DAO has too much logic here. The role of a DAO is not to assign rights to a user. That is business logic. The role of a DAO is to find users or find rights. The code that is in the DAO should be in the service:
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);
}
}
This also shows a fundamental problem in your design: the transactions must not be started at the DAO layer. They must be started at the service layer. That's what allows DAO methods to be reusable and services methods to use several DAOs in a single transaction. Not doing so will lead to anemic services that will call DAO methods containing the whole business logic, as your code shows.
这也说明了您设计中的一个基本问题:交易一定不能在 DAO 层启动。它们必须在服务层启动。这就是允许 DAO 方法可重用和服务方法在单个事务中使用多个 DAO 的原因。如您的代码所示,不这样做将导致调用包含整个业务逻辑的 DAO 方法的无效服务。
And that's why frameworks like EJBs or Spring allow demarcating transactions declaratively: you don't need to explicitely start and commit trannsactions, handle exceptions and rollback exceptions. All you have to do is mark a service method as transactional using an annotation.
这就是为什么像 EJB 或 Spring 这样的框架允许以声明方式划分事务的原因:您不需要明确地启动和提交事务、处理异常和回滚异常。您所要做的就是使用注释将服务方法标记为事务性的。
回答by GregD
Actually DAO's themselves became an anti pattern and shouldn't really be used anymore.
实际上,DAO 本身已经成为一种反模式,不应该再被使用了。
http://www.adam-bien.com/roller/abien/entry/how_to_deal_with_j2ee
http://www.adam-bien.com/roller/abien/entry/how_to_deal_with_j2ee
http://www.adam-bien.com/roller/abien/entry/jpa_ejb3_killed_the_dao
http://www.adam-bien.com/roller/abien/entry/jpa_ejb3_killed_the_dao
Java EE Architecture - Are DAO's still recommended when using an ORM like JPA 2?