Data Access Object (DAO). Уровень класса

При проектировании информационной системы выявляются некоторые слои, которые отвечают за взаимодействие различных модулей системы. Соединение с базой данных является одной из важнейшей составляющей приложения. Всегда выделяется часть кода, модуль, отвечающающий за передачу запросов в БД и обработку полученных от неё ответов. В общем случае, определение Data Access Object описывает его как прослойку между БД и системой. DAO абстрагирует сущности системы и делает их отображение на БД, определяет общие методы использования соединения, его получение, закрытие и (или) возвращение в Connection Pool.

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

public abstract class AbstractController <E, K> {
    public abstract List<E> getAll();
    public abstract E getEntityById(K id);
    public abstract E update(E entity);
    public abstract boolean delete(K id);
    public abstract boolean create(E entity);
}

Набор методов не является завершённым, он зависит от конкретной системы. Фиктивный тип K является ключом сущности, редкая таблица, описывающая сущность, не имеет первичного ключа. Так же, в данном классе будет логичным разместить метод закрытие экземпляра PrepareStatement.

public void closePrepareStatement(PreparedStatement ps) {
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

Уровень класса

Реализация DAO на уровне класса подразумевает использование одного единственного коннекта для вызова более чем одного метода унаследованного DAO класса. В этом случае, в вершине иерархии DAO AbstractController, в качестве поля объявляется connection. Абстрактный класс будет выглядеть следующим образом.


public abstract class AbstractController<E, K> {
    private Connection connection;
    private ConnectionPool connectionPool;

    public AbstractController() {
        connectionPool = ConnectionPool.getConnectionPool();
        connection = connectionPool.getConnection();
    }

    public abstract List<E> getAll();
    public abstract E update(E entity);
    public abstract E getEntityById(K id);
    public abstract boolean delete(K id);
    public abstract boolean create(E entity);

    // Возвращения экземпляра Connection в пул соединений
    public void returnConnectionInPool() {
        connectionPool.returnConnection(connection);
    }

    // Получение экземпляра PrepareStatement
    public PreparedStatement getPrepareStatement(String sql) {
        PreparedStatement ps = null;
        try {
            ps = connection.prepareStatement(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return ps;
    }

    // Закрытие PrepareStatement
    public void closePrepareStatement(PreparedStatement ps) {
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}


Стоит отметить, что в данном примере мы получаем экземпляр Connection из пула соединений, что соответственно стоит реализовать или воспользоваться уже готовыми решениями. Создаём методы по получению getPrepareStatement(String sql) и его закрытию closePrepareStatement(PreparedStatement ps) . Реализация конкретного DAO класса, при такой логике, никогда не должна закрывать в своих методах соединение с базой данных. Соединение закрывается в той части бизнес-логики, от куда был вызван метод. Пример конкретного DAO класса будет выглядеть следующим образом.

public class UserController extends AbstractController<User, Integer> {
    public static final String SELECT_ALL_USERS = "SELECT * FROM SHEMA.USER";

    @Override
    public List<Planet> getAll() {
        List<User> lst = new LinkedList<>();
        PreparedStatement ps = getPrepareStatement(SELECT_ALL_PLANET);
        try {
            ResultSet rs = ps.executeQuery();
            while (rs.next()) { 
                User user = new User();
                planet.setId(rs.getInt(1));
                planet.setName(rs.getString(2));
                lst.add(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            closePrepareStatement(ps);
        }

        return lst;
    }

    @Override
    public Planet getEntityById(Integer id) {
        return null;
    }

    @Override
    public boolean delete(Integer id) {
        return false;
    }

    @Override
    public boolean create(Planet entity) {
        return false;
    }
}

Пример класса-сущности.

public class User implements Serializable {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Экземпляр Connection доступен методу getPrepareStatement(String sql), который в свою очередь доступен любому методу конкретного DAO класса. Стоит помнить, что следует закрывать экземпляр PrepareStatement сразу после его отработки в блоках finally, а возвращать соединение в пул returnConnectionInPool() в части логики системы, где был вызван метод.
  • –6
  • 70.3k
  • 8
Share post

Comments 8

    +1
    Как-то глупо у вас реализовано, почему никакого автосоздания connection?
    Ваш же код только первый раз без ошибки отработает, потом вы подключение закрыли, а новое не получаете и из дочерних классов получать не должны по вашей же задумке. Но тк отдавая connection в пул вы его не nullите, работать будет до тех пор пока пул его не убьет…
      +1
      Чем именно это отличается от классического репозитория?
        –1
        PreparedStatement ps = getPrepareStatement(SELECT_ALL_PLANET);
        

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

        connectionPool = ConnectionPool.getConnectionPool();

        Статический метод возвращающий connectionPool? Очень плохая идея, таким образом тестирование становится очень сложным, а классы сильно связанными, лучше передавать его как параметр конструктора или вообще через Dependency injection.

        } catch (SQLException e) {
                        e.printStackTrace();
                    }
        

        Не надо так писать, если есть exception его нужно прокинуть выше или хотя бы залогировать (но сделать свой exception и прокинуть его дальше намного лучше). Понятно что пример, но не стоит учить плохому…

        public boolean delete(Integer id)
        boolean create(Planet entity)

        Для целей проверки все ли в порядке в Java намного лучше использовать кидание exception, возвращать boolean из таких методов обычно плохая идея по многим причинам, хотя бы потому что вы захламите код, использующий вам класс бессмысленными проверками, после каждого вызова функции и не позволите использовать всю мощь exception.

        public boolean delete(Integer id) {
                return false;
            }
        

        Если метод ещё не готов лучше намного лучше кидать unchecked exception, вроде UnsupportedOperationException.

        P.S. Автор, замечательно что вы пытаетесь писать статьи, но DAO классы были описаны уже несчетное количество раз в самых разных руководствах, в Java уже лет 10 «мода» на другие presistance способы, вроде JAP или SQL шаблонизаторов, таких как JOOQ, QueryDSL, тем более не стоит давать примеры с ошибками, ведь по ним кто-то может учиться.
          –1
          List<User> lst = new LinkedList<>(); 
          


          Всегда используйте ArrayList(Ожидаемый_размер_коллекции) вместо LinkedList для создания List, ребята из Oracla занимающиеся производительностью JVM говорили что производительность LinkedList на практике всегда хуже чем у ArrayList.
            0
            Те кто поставили минус можете объяснить с чем именно выше вы несогласны? Мне реально интересно может вы знаете больше о LinkedList…
            +1
              +2
              >> public static final String SELECT_ALL_USERS = «SELECT * FROM SHEMA.USER»;
              в нашей фирме за такое по пальцам линейкой бьют
                +1
                getPrepareStatement и closePrepareStatement как protected смотрелись бы лучше

                Only users with full accounts can post comments. Log in, please.