Велосипед для извлечения данных
Каждому кто занимается промышленной разработкой кровавым энтерпрайзом не раз приходилось сталкиваться с написанием слоя работы с базой данных. С этим столкнулись и мы.
Наш проект построен на финском фреймворке Vaadin и чистым JDBC в основе слоя работы с базой данных. Без опыта работы с JDBC мы нагородили достаточно большой слой спагетти кода, а потом доблестно с ним разобрались.
О том как мы с этим боролись и какой велосипед изобрели под катом.
Что привело к такому решению
В Vaadin отображать данные в UI компоненты можно с помощью BeanItem Container
, можно почитать здесь.
Коротко о предметной области
Некоторое количество сущностей, которые показываются пользователю и пользователь должен их редактировать: удалять, изменять, добавлять.
Сущности описывались с помощью спецификации Bean
:
public class Element implements Serializable{
private Integer id = 0;
private String name = "";
private Float price = 0.0F;
// getter and setter for property
}
Контейнер для такого элемента создается просто:
BeanItemContainer container = new BeanItemContainer<>(Element.class);
И дальше этот контейнер подставляется в UI с помощью методов setContainerDataSource(...)
.
Чтобы получить этот контейнер, его нужно предварительно разобрать на экземпляры класса Element
из ResultSet
полученного с помощью запроса к базе данных.
Изначально решение получилось такого вида:
public Element(ResutlSet rs){
try {
id = rs.getInt("id");
}catch(SqlException e){
id = 0;
e.printStackTrace();
}
try {
name = rs.getString("name");
}catch(SqlException e){
name = "";
e.printStackTrace();
}
try {
price = rs.getFloat("price");
}catch(SqlException e){
price = 0.0f;
e.printStackTrace();
}
}
И тут на меня должны быть направлены гневные взгляды гуру Java и карма полететь в минусы.
Но концепция такая: если во время разбора данных из ResultSet
поле вызвало ошибку, то система не должна вывалиться с Exception
и продолжить работать и записать логи ошибок (запись логов здесь и дальше описывать не будет тема для отдельной статьи).
Но признаюсь такой код ужасный, а после прочтения небезызвестной книги дядюшки Боба, появилось непреодолимое желание исправить этот код.
В результате написали библиотеку, которая берет на себя получение данных из ResultSet
.
Реализация
Используя паттерн Декоратор создали маленькую библиотеку Executor.
Эта библиотека расширяет функционал ResultSet
, добавляя безопасные методы для получения данных.
public Integer getInt(String columnName) {
Integer i = 0;
try {
i = rs.getInt(columnName);
} catch (SQLException ignored) {
}
return i;
}
public Integer getIntNull(String columnName) {
Integer i = 0;
try {
i = rs.getInt(columnName);
if (rs.wasNull())
return null;
} catch (SQLException ignored) {
}
return i;
}
public String getString(String columnName) {
String s = "";
try {
s = rs.getString(columnName);
if (s == null)
return "";
} catch (SQLException ignored) {
}
return s;
}
Таким образом 100% гарантия получения результата и работа без Exception
при обработке данных, которые в Vaadin выглядят чуть-чуть пугающе.
На вопрос, а если нужен Exception
, ответ: в планах добавить конструктор для Executor
с типом вызываемого Exception
, потому что простой SqlExecption
не информативен, и реализовать методы для корректной работы.
Планируется API следующего вида:
public Executor(ResultSet rs, Class exceptionClass){
this.rs = rs;
this.exceptionClass = exceptionClass;
}
public Integer getIntThrow(String columnName) {
Integer i = 0;
try {
i = rs.getInt(columnName);
if (rs.wasNull())
return null;
} catch (SQLException ex) {
throw (RuntimeExceptoion) exceptionClass.newInstance();
}
return i;
}
И вариант использования
public Element(ResutlSet rs){
Executor ex = new Executor(rs, CreateElementException.class);
id = ex.getIntThrow("id");
name = ex.getStringThrow("name");
price = ex.getFloatThrow("price");
}
private class CreateElementException extends RuntimeException{
private String message = "";
public CreateElementException(String message){
this.message = message;
}
@Override
public String getMessage(){
return "Exception with access to column with name " + this.message;
}
}
Примеры использования
Что получается с кодом, после использования данной библиотеки. Конструктор для класса Element
изменился на следующий:
public Element(ResutlSet rs){
Executor ex = new Executor(rs);
id = ex.getInt("id");
name = ex.getString("name");
price = ex.getFloat("price");
}
В результате:
- кода стало меньше.
- уменьшение объемов кодовой базы, при наличие большого количества классов описывающих сущности.
- код становится понятнее для восприятия и места для возникновения ошибок становится меньше.
- проще писать тесты.
- появление дополнительной библиотеки в зависимостях.
Заключение
Сделан еще один велосипед велосипеды нужно тоже уметь делать, который обеспечивает универсальный и безопасный доступ к получению данных из БД. Люди желающие воспользоваться библиотекой исходники добро пожаловать на GitHub. Хотелось бы получить оценку решения велосипеда и конструктивных предложений и замечаний.