如何使用JDBC和连接池实现DAO管理器?

Car*_*ara 23 java dao connection-pooling jdbc genericdao

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

Car*_*ara 84

您需要实现DAO Manager.我从这个网站上获取了主要想法,但是我做了自己的实现,解决了一些问题.

第1步:连接池

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

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

第2步:连接到数据库

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

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;

}
Run Code Online (Sandbox Code Playgroud)

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

DAOManager mngr = new DAOManager();
mngr.open();
mngr.close();
Run Code Online (Sandbox Code Playgroud)

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

第3步:让它成为一个点!

现在,如果我们这样做了什么?

DAOManager mngr1 = new DAOManager();
DAOManager mngr2 = new DAOManager();
mngr1.open();
mngr2.open();
Run Code Online (Sandbox Code Playgroud)

有人可能会争辩说,"为什么你会这样做呢?" .但是你永远不知道程序员会做什么.即使这样,程序员也可能会在打开新连接之前关闭连接.另外,这会浪费应用程序的资源.如果你真的想要有两个或更多的开放连接,请停在这里,这将是每个用户一个连接的实现.

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

  • 我们必须将public构造函数转换为私有构造函数.我们必须只给那个叫它的人一个实例.在DAOManager随后变成了工厂!
  • 我们还必须添加一个private实际存储单例的新类.
  • 除此之外,我们还需要一个getInstance()方法来为我们提供一个可以调用的单例实例.

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

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;
        }        

    }

}
Run Code Online (Sandbox Code Playgroud)

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

但是单身是反模式的原因! 我知道有些人不会喜欢单身人士.然而,它解决了这个问题(并且已经解决了我的问题)相当不错.这只是实现此解决方案的一种方式,如果您有其他方式,欢迎您这样做.

第4步:但是出了点问题......

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

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

让我们来做吧INSTANCE ThreadLocal.以这种方式修改类:

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;
        }        

    }

}
Run Code Online (Sandbox Code Playgroud)

我真的很想不要这样做

catch(Exception e)
{
    return null;
}
Run Code Online (Sandbox Code Playgroud)

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

第5步:创建DAO

如果DAOManager没有DAO,A 就没有了.所以我们至少应该创建几个.

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

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

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

现在,这就足够了.让我们创建一些DAO.假设我们有两个POJO:First并且Second,只有一个String名为的字段data及其getter和setter.

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";

}
Run Code Online (Sandbox Code Playgroud)

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

第6步:让经理成为工厂

DAOManager不仅应该服务于作为单一连接点的目的.实际上,DAOManager应该回答这个问题:

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

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

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

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

public enum Table { FIRST, SECOND }
Run Code Online (Sandbox Code Playgroud)

而现在,工厂方法里面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.");
    }

}
Run Code Online (Sandbox Code Playgroud)

第7步:把所有东西放在一起

我们现在很高兴.请尝试以下代码:

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();
Run Code Online (Sandbox Code Playgroud)

是不是很花哨,容易阅读?不只是,但是当你打电话时close(),你关闭了DAO正在使用的每一个连接.但是如何?!好吧,他们共享相同的连接,所以这很自然.

第8步:微调我们的课程

我们可以从这里做几件事.要确保关闭连接并返回池,请执行以下操作DAOManager:

@Override
protected void finalize()
{

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

}
Run Code Online (Sandbox Code Playgroud)

您也可以实现封装方法setAutoCommit(),commit()rollback()Connection这样你就可以有一个更好的处理您的交易.我也做的是,而不是只持有一个Connection,DAOManager也持有一个PreparedStatement和一个ResultSet.因此,当close()它调用它时也会关闭它们.关闭语句和结果集的快捷方法!

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

  • 为了上帝的利益,不要把这个"解决方案"投入生产:你想要有数百个连接泄漏吗? (12认同)
  • 同意@NestorHernandezLoli不能依赖于finalize(). (2认同)

Nes*_*oli 7

我认为如果你想在普通JDBC中做一个简单的DAO模式,你应该保持简单:

      public List<Customer> listCustomers() {
            List<Customer> list = new ArrayList<>();
            try (Connection conn = getConnection();
                 Statement s = conn.createStatement();
                 ResultSet rs = s.executeQuery("select * from customers")) { 
                while (rs.next()) {
                    list.add(processRow(rs));
                }
                return list;
            } catch (SQLException e) {
                throw new RuntimeException(e.getMessage(), e); //or your exceptions
            }
        }
Run Code Online (Sandbox Code Playgroud)

您可以在名为CustomersDao或CustomerManager的类中遵循此模式,您可以使用简单的方法调用它

CustomersDao dao = new CustomersDao();
List<Customers> customers = dao.listCustomers();
Run Code Online (Sandbox Code Playgroud)

请注意,我正在尝试使用资源,这个代码对于连接泄漏,干净和简单是安全的,您可能不希望使用Factorys,接口以及在许多情况下不会发生的所有管道的完整DAO模式增加实际价值.

我认为使用ThreadLocals不是一个好主意,在接受的答案中使用的Bad是类加载器泄漏的来源

记住始终在try finally块中关闭您的资源(Statements,ResultSet,Connections)或使用try with resources