При проектировании информационной системы выявляются некоторые слои, которые отвечают за взаимодействие различных модулей системы. Соединение с базой данных является одной из важнейшей составляющей приложения. Всегда выделяется часть кода, модуль, отвечающающий за передачу запросов в БД и обработку полученных от неё ответов. В общем случае, определение Data Access Object описывает его как прослойку между БД и системой. DAO абстрагирует сущности системы и делает их отображение на БД, определяет общие методы использования соединения, его получение, закрытие и (или) возвращение в Connection Pool.
Вершиной иерархии DAO является абстрактный класс или интерфейс с описанием общих методов, которые будут использоваться при взаимодействии с базой данных. Как правило, это методы поиска, удаление по ключу, обновление и т.д.
Набор методов не является завершённым, он зависит от конкретной системы. Фиктивный тип K является ключом сущности, редкая таблица, описывающая сущность, не имеет первичного ключа. Так же, в данном классе будет логичным разместить метод закрытие экземпляра PrepareStatement.
Реализация DAO на уровне класса подразумевает использование одного единственного коннекта для вызова более чем одного метода унаследованного DAO класса. В этом случае, в вершине иерархии DAO AbstractController, в качестве поля объявляется connection. Абстрактный класс будет выглядеть следующим образом.
Стоит отметить, что в данном примере мы получаем экземпляр Connection из пула соединений, что соответственно стоит реализовать или воспользоваться уже готовыми решениями. Создаём методы по получению getPrepareStatement(String sql) и его закрытию closePrepareStatement(PreparedStatement ps) . Реализация конкретного DAO класса, при такой логике, никогда не должна закрывать в своих методах соединение с базой данных. Соединение закрывается в той части бизнес-логики, от куда был вызван метод. Пример конкретного DAO класса будет выглядеть следующим образом.
Пример класса-сущности.
Экземпляр Connection доступен методу getPrepareStatement(String sql), который в свою очередь доступен любому методу конкретного DAO класса. Стоит помнить, что следует закрывать экземпляр PrepareStatement сразу после его отработки в блоках finally, а возвращать соединение в пул returnConnectionInPool() в части логики системы, где был вызван метод.
Вершиной иерархии 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() в части логики системы, где был вызван метод.