如何使用 JDBC 和连接池实现 DAO 管理器? [英] How do I implement a DAO manager using JDBC and connection pools?

查看:34
本文介绍了如何使用 JDBC 和连接池实现 DAO 管理器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题如下.我需要一个类作为 Web 系统中数据库连接的单点,以避免一个用户有两个打开的连接.我需要它尽可能优化,并且它应该管理系统中的每个事务.换句话说,只有那个类应该能够实例化 DAO.为了让它更好,它还应该使用连接池!我该怎么办?

My problem is as follows. I need a class that works as a single point to a database connection in a web system, so to avoid having one user with two open connections. I need it to be as optimal as possible and it should manage every transaction in the system. In other words only that class should be able to instantiate DAOs. And to make it better, it should also use connection pooling! What should I do?

推荐答案

您将需要实施 DAO 管理器.我从 这个网站 中获取了主要想法,但是我自己实现了解决一些问题.

You will need to implement a DAO Manager. I took the main idea from this website, however I made my own implementation that solves some few issues.

首先,您必须配置一个连接池.连接池是一个连接池.当您的应用程序运行时,连接池将启动一定数量的连接,这样做是为了避免在运行时创建连接,因为这是一项昂贵的操作.本指南不打算解释如何配置一个,所以请四处看看.

First of all, you will have to configure a connection pool. A connection pool is, well, a pool of connections. When your application runs, the connection pool will start a certain amount of connections, this is done to avoid creating connections in runtime since it's a expensive operation. This guide is not meant to explain how to configure one, so go look around about that.

作为记录,我将使用 Java 作为我的语言,Glassfish 作为我的服务器.

For the record, I'll use Java as my language and Glassfish as my server.

让我们从创建一个 DAOManager 类开始.让我们为它提供在运行时打开和关闭连接的方法.没什么太花哨的.

Let's start by creating a DAOManager class. Let's give it methods to open and close a connection in runtime. Nothing too fancy.

public class DAOManager {

    public DAOManager() throws Exception {
        try
        {
            InitialContext ctx = new InitialContext();
            this.src = (DataSource)ctx.lookup("jndi/MYSQL"); //The string should be the same name you're giving to your JNDI in Glassfish.
        }
        catch(Exception e) { throw e; }
    }

    public void open() throws SQLException {
        try
        {
            if(this.con==null || !this.con.isOpen())
                this.con = src.getConnection();
        }
        catch(SQLException e) { throw e; }
    }

    public void close() throws SQLException {
        try
        {
            if(this.con!=null && this.con.isOpen())
                this.con.close();
        }
        catch(SQLException e) { throw e; }
    }

    //Private
    private DataSource src;
    private Connection con;

}

这不是一个很花哨的课程,但它将成为我们要做的事情的基础.所以,这样做:

This isn't a very fancy class, but it'll be the basis of what we're going to do. So, doing this:

DAOManager mngr = new DAOManager();
mngr.open();
mngr.close();

应该在对象中打开和关闭与数据库的连接.

should open and close your connection to the database in an object.

现在,如果我们这样做呢?

What, now, if we did this?

DAOManager mngr1 = new DAOManager();
DAOManager mngr2 = new DAOManager();
mngr1.open();
mngr2.open();

有些人可能会争辩说,你到底为什么要这样做?".但是你永远不知道程序员会做什么.即便如此,程序员可能会在打开新连接之前伪造关闭连接.另外,这对应用程序来说是一种资源浪费.如果您确实想要有两个或更多打开的连接,请到此为止,这将是每个用户一个连接的实现.

Some might argue, "why in the world would you do this?". But then you never know what a programmer will do. Even then, the programmer might forger from closing a connection before opening a new one. Plus, this is a waste of resources for the application. Stop here if you actually want to have two or more open connections, this will be an implementation for one connection per user.

为了使它成为一个点,我们必须将这个类转换成一个单例.单例是一种设计模式,它允许我们拥有任何给定对象的一个​​且只有一个实例.所以,让我们让它成为一个单身人士吧!

In order to make it a single point, we will have to convert this class into a singleton. A singleton is a design pattern that allows us to have one and only one instance of any given object. So, let's make it a singleton!

  • 我们必须将我们的 public 构造函数转换为私有构造函数.我们必须只为调用它的人提供一个实例.DAOManager 然后变成一个工厂!
  • 我们还必须添加一个新的 private 类来实际存储一个单例.
  • 除此之外,我们还需要一个 getInstance() 方法,该方法将为我们提供一个可以调用的单例实例.
  • We must convert our public constructor into a private one. We must only give an instance to whoever calls it. The DAOManager then becomes a factory!
  • We must also add a new private class that will actually store a singleton.
  • Alongside all of this, we also need a getInstance() method that will give us a singleton instance we can call.

让我们看看它是如何实现的.

Let's see how it's implemented.

public class DAOManager {

    public static DAOManager getInstance() {
        return DAOManagerSingleton.INSTANCE;
    }  

    public void open() throws SQLException {
        try
        {
            if(this.con==null || !this.con.isOpen())
                this.con = src.getConnection();
        }
        catch(SQLException e) { throw e; }
    }

    public void close() throws SQLException {
        try
        {
            if(this.con!=null && this.con.isOpen())
                this.con.close();
        }
        catch(SQLException e) { throw e; }
    }

    //Private
    private DataSource src;
    private Connection con;

    private DAOManager() throws Exception {
        try
        {
            InitialContext ctx = new InitialContext();
            this.src = (DataSource)ctx.lookup("jndi/MYSQL");
        }
        catch(Exception e) { throw e; }
    }

    private static class DAOManagerSingleton {

        public static final DAOManager INSTANCE;
        static
        {
            DAOManager dm;
            try
            {
                dm = new DAOManager();
            }
            catch(Exception e)
                dm = null;
            INSTANCE = dm;
        }        

    }

}

当应用程序启动时,只要有人需要单例,系统就会实例化一个DAOManager.非常简洁,我们创建了一个接入点!

When the application starts, whenever anyone needs a singleton the system will instantiate one DAOManager. Quite neat, we've created a single access point!

但是单例是一种反模式,因为原因!我知道有些人不会喜欢单身.然而,它很好地解决了这个问题(并且已经解决了我的问题).这只是实现此解决方案的一种方式,如果您有其他方式,欢迎提出建议.

But singleton is an antipattern because reasons! I know some people won't like singleton. However it solves the problem (and has solved mine) quite decently. This is just a way of implementing this solution, if you have other ways you're welcome to suggest so.

是的,确实有.单例只会为整个应用程序创建一个实例! 这在很多层面上都是错误的,特别是如果我们有一个 Web 系统,我们的应用程序将是多线程!那么我们如何解决这个问题?

Yes, indeed there is. A singleton will create only ONE instance for the whole application! And this is wrong in many levels, especially if we have a web system where our application will be multithreaded! How do we solve this, then?

Java 提供了一个名为 ThreadLocal 的类.ThreadLocal 变量每个线程有一个实例.嘿,它解决了我们的问题!详细了解其工作原理,您需要了解其用途这样我们就可以继续了.

Java provides a class named ThreadLocal. A ThreadLocal variable will have one instance per thread. Hey, it solves our problem! See more about how it works, you will need to understand its purpose so we can continue.

让我们制作我们的 INSTANCE ThreadLocal 然后.这样修改类:

Let's make our INSTANCE ThreadLocal then. Modify the class this way:

public class DAOManager {

    public static DAOManager getInstance() {
        return DAOManagerSingleton.INSTANCE.get();
    }  

    public void open() throws SQLException {
        try
        {
            if(this.con==null || !this.con.isOpen())
                this.con = src.getConnection();
        }
        catch(SQLException e) { throw e; }
    }

    public void close() throws SQLException {
        try
        {
            if(this.con!=null && this.con.isOpen())
                this.con.close();
        }
        catch(SQLException e) { throw e; }
    }

    //Private
    private DataSource src;
    private Connection con;

    private DAOManager() throws Exception {
        try
        {
            InitialContext ctx = new InitialContext();
            this.src = (DataSource)ctx.lookup("jndi/MYSQL");
        }
        catch(Exception e) { throw e; }
    }

    private static class DAOManagerSingleton {

        public static final ThreadLocal<DAOManager> INSTANCE;
        static
        {
            ThreadLocal<DAOManager> dm;
            try
            {
                dm = new ThreadLocal<DAOManager>(){
                    @Override
                    protected DAOManager initialValue() {
                        try
                        {
                            return new DAOManager();
                        }
                        catch(Exception e)
                        {
                            return null;
                        }
                    }
                };
            }
            catch(Exception e)
                dm = null;
            INSTANCE = dm;
        }        

    }

}

我真的很想不这样做

catch(Exception e)
{
    return null;
}

但是 initialValue() 不能抛出异常.哦,initialValue() 你是说?这个方法会告诉我们 ThreadLocal 变量将保存什么值.基本上我们正在初始化它.因此,多亏了这一点,我们现在每个线程可以有一个实例.

but initialValue() can't throw an exception. Oh, initialValue() you mean? This method will tell us what value will the ThreadLocal variable hold. Basically we're initializing it. So, thanks to this we can now have one instance per thread.

DAOManager 没有 DAO 就什么都不是.所以我们至少应该创建几个.

A DAOManager is nothing without a DAO. So we should at least create a couple of them.

DAO,数据访问对象"的缩写,是一种设计模式,它将管理数据库操作的责任赋予代表某个表的类.

A DAO, short for "Data Access Object" is a design pattern that gives the responsability of managing database operations to a class representing a certain table.

为了更有效地使用我们的DAOManager,我们将定义一个GenericDAO,它是一个抽象的DAO,它将保存所有DAO之间的通用操作.

In order to use our DAOManager more efficiently, we will define a GenericDAO, which is an abstract DAO that will hold the common operations between all DAOs.

public abstract class GenericDAO<T> {

    public abstract int count() throws SQLException; 

    //Protected
    protected final String tableName;
    protected Connection con;

    protected GenericDAO(Connection con, String tableName) {
        this.tableName = tableName;
        this.con = con;
    }

}

现在,这就足够了.让我们创建一些 DAO.假设我们有两个 POJO:FirstSecond,两者都只有一个名为 dataString 字段及其 getter和二传手.

For now, that will be enough. Let's create some DAOs. Let's suppose we have two POJOs: First and Second, both with just a String field named data and its getters and setters.

public class FirstDAO extends GenericDAO<First> {

    public FirstDAO(Connection con) {
        super(con, TABLENAME);
    }

    @Override
    public int count() throws SQLException {
        String query = "SELECT COUNT(*) AS count FROM "+this.tableName;
        PreparedStatement counter;
        try
        {
        counter = this.con.PrepareStatement(query);
        ResultSet res = counter.executeQuery();
        res.next();
        return res.getInt("count");
        }
        catch(SQLException e){ throw e; }
    }

   //Private
   private final static String TABLENAME = "FIRST";

}

SecondDAO 或多或少具有相同的结构,只是将 TABLENAME 更改为 "SECOND".

SecondDAO will have more or less the same structure, just changing TABLENAME to "SECOND".

DAOManager 不仅应该起到作为单个连接点的作用.其实DAOManager应该回答这个问题:

DAOManager not only should serve the purpose of serving as a single connection point. Actually, DAOManager should answer this question:

谁负责管理与数据库的连接?

Who is the one responsible of managing the connections to the database?

独立的 DAO 不应该管理它们,而是 DAOManager.我们已经部分回答了这个问题,但现在我们不应该让任何人管理与数据库的其他连接,甚至是 DAO.但是,DAO 需要连接到数据库!谁应该提供?DAOManager 确实!我们应该做的是在DAOManager内部创建一个工厂方法.不仅如此,DAOManager 还会将当前连接交给他们!

The individual DAOs shouldn't manage them, but DAOManager. We've answered partially the question, but now we shouldn't let anyone manage other connections to the database, not even the DAOs. But, the DAOs need a connection to the database! Who should provide it? DAOManager indeed! What we should do is making a factory method inside DAOManager. Not just that, but DAOManager will also hand them the current connection!

Factory 是一种设计模式,它允许我们创建某个超类的实例,而无需确切知道将返回哪个子类.

Factory is a design pattern that will allow us to create instances of a certain superclass, without knowing exactly what child class will be returned.

首先,让我们创建一个 enum 列出我们的表.

First, let's create an enum listing our tables.

public enum Table { FIRST, SECOND }

现在,DAOManager中的工厂方法:

public GenericDAO getDAO(Table t) throws SQLException 
{

    try
    {
        if(this.con == null || this.con.isClosed()) //Let's ensure our connection is open   
            this.open();
    }
    catch(SQLException e){ throw e; }

    switch(t)
    {
    case FIRST:
        return new FirstDAO(this.con);
    case SECOND:
        return new SecondDAO(this.con);
    default:
        throw new SQLException("Trying to link to an unexistant table.");
    }

}

第 7 步:将所有内容放在一起

我们现在可以走了.试试下面的代码:

Step 7: Putting everything together

We're good to go now. Try the following code:

DAOManager dao = DAOManager.getInstance();
FirstDAO fDao = (FirstDAO)dao.getDAO(Table.FIRST);
SecondDAO sDao = (SecondDAO)dao.getDAO(Table.SECOND);
System.out.println(fDao.count());
System.out.println(sDao.count());
dao.close();

是不是很花哨而且很容易阅读?不仅如此,当您调用 close() 时,您会关闭 DAO 正在使用的每一个连接.但是怎么做?!嗯,他们共享相同的连接,所以很自然.

Isn't it fancy and easy to read? Not just that, but when you call close(), you close every single connection the DAOs are using. But how?! Well, they're sharing the same connection, so it's just natural.

从现在开始,我们可以做几件事.要确保连接关闭并返回到池中,请在 DAOManager 中执行以下操作:

We can do several things from here on. To ensure connections are closed and returned to the pool, do the following in DAOManager:

@Override
protected void finalize()
{

    try{ this.close(); }
    finally{ super.finalize(); }

}

你也可以实现封装Connectioncommit()rollback()的方法代码> 以便您可以更好地处理您的交易.我还做的是,DAOManager 不仅包含一个 Connection,还包含一个 PreparedStatement 和一个 ResultSet.因此,当调用 close() 时,它也会关闭两者.关闭语句和结果集的快速方法!

You can also implement methods that encapsulate setAutoCommit(), commit() and rollback() from the Connection so you can have a better handling of your transactions. What I also did is, instead of just holding a Connection, DAOManager also holds a PreparedStatement and a ResultSet. So, when calling close() it also closes both. A fast way of closing statements and result sets!

希望本指南对您的下一个项目有用!

I hope this guide can be of any use to you in your next project!

这篇关于如何使用 JDBC 和连接池实现 DAO 管理器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆