Как реализовать диспетчер DAO с помощью JDBC и пулов соединений?

моя проблема заключается в следующем. Мне нужен класс, который работает как одна точка подключения к базе данных в веб-системе, чтобы избежать одного пользователя с двумя открытыми соединениями. Мне нужно, чтобы он был максимально оптимальным, и он должен управлять каждой транзакцией в системе. Другими словами, только этот класс должен иметь возможность создавать экземпляры DAOs. И чтобы сделать его лучше, он также должен использовать пул соединений! Что мне делать?

2 ответов


вам нужно будет реализовать 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;

}

это не очень нравится класс, но это будет основой того, что мы собираемся делать. Итак, делая это:

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

должно открываться и закрываться соединение с базой данных в объекте.

Шаг 3: Сделайте это одной точкой!

что, если мы сделаем это?

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

некоторые могут возразить, " зачем тебе это делать?". Но тогда никогда не знаешь, что сделает программист. Даже тогда, программист может подделать закрытие соединения перед открытием нового. Кроме того, это пустая трата ресурсов для приложения. остановитесь здесь, Если вы действительно хотите иметь два или более открытых соединений, это будет реализация для одного соединения на пользователя.

чтобы сделать его одной точкой, нам придется преобразовать этот класс в синглтон. А синглтон-это шаблон проектирования, который позволяет нам иметь один и только один экземпляр любого объект. Итак, давайте сделаем его синглтоном!

  • мы должны преобразовать наши public конструктор в частную. Мы должны дать пример только тому, кто его назовет. The 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;
        }        

    }

}

когда приложение запускается, всякий раз, когда кому-то нужен синглтон, система создаст экземпляр 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;
        }        

    }

}

я бы очень хотел не делать этого

catch(Exception e)
{
    return null;
}

но initialValue() не может выдать исключение. О,initialValue() ты имеешь в виду? Этот метод скажет нам, какое значение будет ThreadLocal переменной держать. По сути, мы его инициализируем. Так что, благодаря этому у нас теперь может быть один экземпляр на поток.

Шаг 5: Создайте DAO

A DAOManager ничто без Дао. Так что мы должны создать хотя бы пару из них.

DAO, сокращенно от "объект доступа к данным" - это шаблон проектирования, который дает ответственность за управление операциями базы данных классу, представляющему определенную таблицу.

для того, чтобы использовать наши DAOManager более эффективно, мы определим GenericDAO, который является абстрактным DAO, который будет держите общие операции между всеми 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;
    }

}

пока этого будет достаточно. Давайте создадим DAOs. Предположим, у нас есть два POJOs:First и Second как только String поле с именем data и геттеры и сеттеры.

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 to "SECOND".

Шаг 6: сделать менеджера фабрикой

DAOManager не только должен служить цель служить как одиночный пункт соединения. На самом деле, DAOManager должны ответить на этот вопрос:

кто отвечает за управление подключениями к базе данных?

отдельные DAOs не должны управлять ими, но DAOManager. Мы частично ответили на вопрос, но теперь мы не должны позволять никому управлять другими подключениями к базе данных, даже DAOs. Но, DAOs нужно подключение к базе данных! Кто должен это обеспечить? DAOManager в самом деле! То, что мы должны сделать, это сделать заводской метод внутри DAOManager. Не только это, но DAOManager также передаст им текущее соединение!

Factory-это шаблон проектирования, который позволит нам создавать экземпляры определенного суперкласса, не зная точно, какой дочерний класс будет возвращен.

во-первых, давайте создадим enum список наших столах.

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: Собираем все вместе

теперь мы можем идти. Попробуйте следующий код:

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() закрыть каждое соединение DAOs используют. но как?! Ну, у них одна и та же связь, так что это просто естественно.

Шаг 8: точная настройка нашего класса

мы можем сделать несколько вещи здесь. Чтобы убедиться, что соединения закрыты и возвращены в пул, выполните следующие действия в DAOManager:

@Override
protected void finalize()
{

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

}

вы также можете реализовать методы, которые инкапсулируют setAutoCommit(), commit() и rollback() С Connection таким образом, вы можете иметь лучшую обработку ваших транзакций. То, что я также сделал, вместо того, чтобы просто держать Connection, DAOManager и имеет PreparedStatement и ResultSet. Итак, при вызове close() он также закрывает оба. Быстрый способ закрытия заявлений и результата наборы!

я надеюсь, что это руководство может быть полезным для вас в вашем следующем проекте!


Я думаю, что если вы хотите сделать простой шаблон DAO в простом JDBC, вы должны сохранить его простым:

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

вы можете следовать этому шаблону в классе, называемом, например, CustomersDao или CustomerManager, и вы можете вызвать его с помощью простого

CustomersDao dao = new CustomersDao();
List<Customers> customers = dao.listCustomers();

обратите внимание, что я использую try с ресурсами, и этот код безопасен для утечек соединений, чист и прост, вы, вероятно, не хотите следовать полному шаблону DAO с фабриками, интерфейсами и всей этой сантехникой это во многих случаях не добавляет реальной ценности.

Я не думаю, что это хорошая идея с использованием ThreadLocals, плохо используется, как в принятом ответе, является источником утечек classloader

помните, всегда закрывайте свои ресурсы (операторы, результирующие наборы, соединения) в блоке try finally или используйте try with resources