Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

Вам нужно будет выполнить 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();

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

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

  • Мы должны преобразовать наш конструктор public в частный. Мы должны дать только экземпляр тому, кто его называет. DAOManager затем становится factory!
  • Мы также должны добавить новый класс 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. Поэтому мы должны хотя бы создать пару из них.

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

Чтобы более эффективно использовать наш DAOManager, мы определим a 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;
    }

}

Пока этого будет достаточно. Позвольте создать некоторые DAO. Предположим, что у нас есть два POJO: 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 на "SECOND".

Шаг 6: Создание менеджера a factory

DAOManager не только должен служить цели использования в качестве единственной точки подключения. Собственно, DAOManager должен ответить на этот вопрос:

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

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

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

Сначала создайте enum список наших таблиц.

public enum Table { FIRST, SECOND }

И теперь метод factory внутри DAOManager:

public GenericDAO getDAO(Table t) throws SQLException 
{

    try
    {
        if(this.con == null || this.con.isClosed()) //Let 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(), вы закрываете каждое соединение, которое используют DAO. Но как?! Ну, они используют одно и то же соединение, поэтому это просто естественно.

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

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

@Override
protected void finalize()
{

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

}

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

Я надеюсь, что это руководство может принести вам пользу в следующем проекте!

Ответ 2

Я думаю, что если вы хотите сделать простой шаблон 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
            }
        }

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

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

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

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

Помните ВСЕГДА закрывайте свои ресурсы (утверждения, ResultSets, Connections) в блоке try finally или используя try с ресурсами