DAO方法和同步

9

以下是我目前在一个抽象DAO类中拥有的方法。如果存在并发调用,它们是否安全或者应该使用同步?我知道如果方法范围外有属性引用,则应该使用同步,但是对于外部资源应该如何处理还不清楚。

public Connection getConnection() {
    // Call to singleton handling JDBC stuff
    return Database.getInstance().getCon();
}

public boolean isConnectionAvailable(){     
    if( getConnection() != null ){
        return true;
    }

    return false;
}

public PreparedStatement getPreparedStatement( String sqlStatement ){
    Connection connection = getConnection();
    PreparedStatement pS = null;

    if( connection != null ){
        try {
            pS = connection.prepareStatement( sqlStatement );
        } catch (SQLException e) {
            return null;
        }
    }

    return pS;
}

编辑:我可能会重新表述这个问题,包括关于编写DAO的信息,因为这是重要的内容。


3
不要使用单例模式来访问数据库连接。 - matt b
我经常听到这个。但是当你使用纯Java时,有什么替代方案呢? - James P.
7个回答

13

我完全不同意这种实现方式。

首先,DAO应该由拥有工作单元和事务的服务提供其连接信息。

其次,我没有看到接口。

第三,我没有看到模型或领域对象。

第四,预处理语句应该只是内部实现的一部分。如果它们泄漏出了DAO,那么你就做错了。

第五,从对象中传递预处理语句会使关闭和清理责任变得不太清晰。你的DAO很快就会成为资源泄漏的死亡陷阱。

这里是一个通用DAO的接口。你会发现它全部都是CRUD操作,没有提及任何来自java.sql包的接口:

package persistence;

import java.io.Serializable;
import java.util.List;

public interface GenericDao<T, K extends Serializable>
{
    T find(K id);
    List<T> find();
    List<T> find(T example);
    List<T> find(String queryName, String [] paramNames, Object [] bindValues);

    K save(T instance);
    void update(T instance);
    void delete(T instance);
}

这可以让你走得更远。它是更好的抽象化。 T 是您的业务对象类型,K 是主键。


2
Spring拥有一个不错的DAO框架:http://static.springsource.org/spring/docs/2.5.x/reference/dao.html - Dave
相当不错。这是应该追随的模型。 - duffymo
谢谢你的回答,duffymo。我必须承认我有些困难,因为我被要求不使用Spring这样的框架。如果你有一个可以用作模板的例子,那就更加受用了 :)。 - James P.
1
@James:真是太不幸了,我很难理解这样的请求背后的动机(让你不使用框架)。对我来说,Spring 特别是一个巨大的时间节省者。 - Nathan Hughes

3
如果getCon()每次调用时返回一个新的Connection,或者返回一个ThreadLocal连接,那么您是安全的,无需使用synchronized。如果您将相同的连接返回给所有人,您可能仍然可以节省同步开销,因为连接中没有被更改的状态(在您当前的代码中)。但是,您应该避免这种做法。考虑使用连接池。关于一般设计原则的几点说明。DAO形成了一个单独的层。每个层都有自己的原因,而不仅仅是为了拥有酷炫的名称。DAO层存在的目的是为了抽象或者说隐藏从使用DAO对象的服务中访问数据库的细节。为了更清楚地想象它 - DAO必须以这样的方式编写,即如果明天您决定从RDBMD存储(通过JDBC)切换到XML存储,您应该能够仅通过更改DAO对象而无需更改其他任何内容来实现。

具体的DAO类按照您所解释的方式工作。上面的方法只是在抽象类中添加一些实用方法的想法,但也许这是一种不好的方法。我可能还没有理解它应该如何与JDBC和数据库交互。 - James P.

3
JDBC Connection类不能保证线程安全。如果Database.getInstance().getCon()方法总是返回相同的连接,则会遇到问题。但是,如果它使用池,以便每次调用getInstance().getCon()都返回不同的连接,则不会有问题。
话虽如此,如果每次调用getCon()时返回不同的连接,则getPreparedStatement()将无法工作,如果您想要两个Prepared Statement调用使用相同的连接(和相同的事务)。
我喜欢Spring的JDBCTemplate类作为我的DAO类的基础。

1
“Connection”仅仅是一个接口,实现是否线程安全完全取决于所使用的JDBC驱动程序。 - matt b

2

您不应该在每个线程中使用相同的连接。JDBC驱动程序不应该是线程安全的。如果您想要一个线程安全的代码,您应该为每个线程创建一个连接。

您的其余代码似乎是安全的。


你觉得我应该在DAO内部添加某种线程机制吗? - James P.
不是你的DAO不是线程安全的,而是连接部分。就像@Bozho所说,如果getCon()每次为每个线程返回一个特定的连接,则没有竞争条件。 - Colin Hebert
好的,你的意思是我应该隔离对连接的任何访问。我已经查看了单例并且它每次都返回相同的连接,所以那可能是个问题。 - James P.
不,连接应该传递到DAO中。让知道工作单元的对象 - 服务 - 实例化连接,将其提供给DAO,然后在方法范围内清理它。 - duffymo

2
看看Spring是如何做的,他们已经解决了所有这些问题,没有必要重新发明轮子。查看Spring完整分发版中捆绑的petclinic示例代码,或(非Spring方法)阅读Bauer / King Hibernate书的DAO章节。
绝对不应该由DAO负责获取数据库连接,因为您希望在同一事务中组合多个DAO调用。Spring的处理方式是有一个服务层,它的事务方法被包装在拦截器中,从数据源中提取连接并将其放入线程本地变量中,DAO可以找到它。
吞掉SQLException并返回null是不好的。正如duffymo指出的那样,让PreparedStatement四处传递而没有保证它会被关闭是非常糟糕的。此外,现在没有人应该再使用原始JDBC,Ibatis或spring-jdbc是更好的选择。

“-1” 看起来有点严厉。你用过 Spring JDBC 吗?它几乎是原始的 JDBC,你仍然可以操作 PreparedStatement 和 ResultSet(如果愿意),但所有的异常处理(和资源管理)和与事务交互都已经被照顾好了。因此,你可以在具有更少泄漏风险的情况下获得对数据的原始访问优势。 - Thierry
我认为这是一个可辩护的立场,GreenieMeanie。我更希望人们仅使用Spring来处理JDBC,而不是在SO上看到的可怕的JDBC代码。我投了Nathan Hughes一票。 - duffymo
@stepanian:为什么?我认为JDBC即使对于专家来说也太痛苦了。结果要么是剪切粘贴的代码,要么就是一个内部框架,可能比spring-jdbc或Mybatis(它们都经过了大量的工作)还要差。这有什么不对吗? - Nathan Hughes
Nathan Hughes:说你更喜欢使用spring-jdbc、mybaits、ibatis、unnecessarybatis或者其他你喜欢的框架是一回事,而说出“再也没有人应该使用原始JDBC了”这样绝对的话又是另一回事。Java开发就是关于选择的。我不想和你辩论框架的优点和缺点以及它们与各种项目类别的关系,网上已经有很多这方面的内容了。但我希望你意识到,你的编码技巧偏好并不总是被所有人所分享。 - stepanian

0

连接只是一个接口。它不是线程安全的。JDBC驱动程序也不是线程安全的。


-1

我觉得没问题。这些函数是线程安全的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接