Реформа SQL-ориентированного подхода в DAO

    Вводная


    Мне часто в проектах приходится сталкиваться с фреймворками по работе с БД. Концептуально, эти фреймворки можно разбить на 2 больших класса:

    • ORM-ориентированные
    • SQL-ориентированные

    Некоторые из них хороши, какие-то не очень. Но субъективно могу сказать: SQL-ориентированные уступают в развитии ORM-ориентированным. Подчеркну, в развитии, а не в возможностях. Хоть изменить эту чашу весов и не получится, но предложить необычный взгляд на мир SQL-ориентированного подхода — вполне. Кому интересно, добро пожаловать под кат

    Немного обзора


    Прежде чем преступить, предлагаю вспомнить, совсем кратко, что есть. Кто в теме, смело пропускайте обзор.

    И так, на сцене представители ORM-ориентированного подхода:


    Эти представители справляются со своей задачей по-разному. Последний, например, DSL-way, предлагает конструировать SQL запросы, используя, либо сгенерированные объекты по схеме Вашей БД, либо просто строки. Другие, требуют описывать соответствие между java-объектами и таблицами БД. Но не суть. Их все объединяет одна идея: максимально изолировать разработчика от написания SQL-запросов, предлагая взамен — ORM-мышление.

    С другой стороны собрались представители SQL-ориентированного подхода:


    Все эти решения в том или ином виде являются надстройками над пакетом java.sql.*. И тем эти фреймворки круче, чем сильнее изолируют разработчика от него. Тем не менее, используя их, мыслить Вам придется сначала категориями SQL, а потом уж ORM.

    Я знаю о существовании и третьего класса фреймворков: генераторы. Им тяжело отвоевать нишу, потому что такие решение как правило пишутся под конкретный проект и им трудно быть универсальными. Идея в них следующая: сгенерировать DAO-слой полностью, используя знания конкретного проекта, знание о БД и специфики бизнес требований. Встречался с такими решениями дважды. Очень не привычно, когда приходится дорабатывать генератор DAO-слоя, вместо написания SQL-запросов или меппинга.

    Что не так?


    Я умышленно не давал оценочных суждений тому или иному подходу, ORM vs SQL vs Генераторы. Каждый решает сам, в купе с имеющимися обстоятельствами, что выбирать. Но вот готов предложить определенную альтернативу как в стилистическом выражении так и в концептуальном для SQL-ориентированных. Но прежде скажу, что мне не нравится на уровне кода (производительность, debug и прочее — опускаю) в существующих решениях:

    1. Определенная многословность для достижения простых вещей
    2. Бойлерплейт, бойлерплейт, бойлерплейт… и еще раз бойлерплейт
    3. Отсутсвие точки в коде, где можно посмотреть sql-orm или orm-sql варианты
    4. В том или ином виде конструирование SQL-запроса по условиям фильтрации
    5. Многознание для использования API фреймворка — узнай про +100500 сущностей прежде, чем преступить кодить

    Многое из перечисленного заставило спросить: 'А каким фреймворк должен быть, чтобы тебе понравилось?'

    Декларативный стиль


    Каким? Думаю простым, таким, чтобы взял и начал кодить. Но, а если серьезно? Декларативным. Да, я сторонник декларативного стиля в таких вещах, нежели императивного. Что в java приходит на ум первым, если речь идет о декларативном подходе? Да всего 2 вещи: аннотации и интерфейсы. Если эти 2 субстанции скрестить и направить в русло SQL-ориентированного решения, то получим следующее:

    ОРМ
    public class Client {
        private Long id;
        private String name;
        private ClientState state;
        private Date regTime;
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public ClientState getState() {
            return state;
        }
        public void setState(ClientState state) {
            this.state = state;
        }
        public Date getRegTime() {
            return regTime;
        }
        public void setRegTime(Date regTime) {
            this.regTime = regTime;
        }
    }
    enum ClientState {
        ACTIVE(1),
        BLOCKED(2),
        DELETED(3);
        private int state;
        ClientState(int state) {
            this.state = state;
        }
        @TargetMethod
        public int getState() {
            return state;
        }
        @TargetMethod
        public static ClientState getClientState(int state) {
            return values()[state - 1]; // только для краткости
        }
    }

    public interface IClientDao {
        @TargetQuery(query =
                "SELECT id, name, state " +
                "  FROM clients " +
                "  WHERE id = ?", type = QT_SELECT)
        Client findClient(long clientId);
    }

    Табличка
    -- Таблица клиентов
    CREATE TABLE clients (
      id bigint NOT NULL,
      name character varying(127) NOT NULL,
      state int NULL,
      reg_time timestamp NOT NULL,
      CONSTRAINT pk_clients PRIMARY KEY (id)
    );

    Это простой рабочий пример, суть которого подчеркнуть идею декларативного стиля, которая находит отражение в рассматриваемом фреймворке. Сама идея разумеется не нова, заметки о похожем я встречал в статьях IBM где-то за 2006 год, а некоторые приведенные фреймворки уже используют эту идею. Но увидев такой пример, я бы резонно задал несколько вопросов:

    1. А кто реализует контракт IClientDao и как получить доступ к реализации?
    2. А где описывается меппинг полей?
    3. А как насчет чего-то посложнее? А то эти примеры на 3 копейки уже надоели.

    Предлагаю по порядку ответить на эти вопросы и походу раскрыть возможности фреймворка.

    Просто Proxy


    >>1. А кто реализует этот контракт и как получить доступ к реализации?

    Контракт реализуется самим фреймворком с помощью инструмента java.lang.reflect.Proxy и реализовывать его самому для SQL целей не нужно. А получить доступ к реализации очень просто, с помощью… Да в прочим на примере показать легче:

    IClientDao clientDao = com.reforms.orm.OrmDao.createDao(connection, IClientDao.class);
    Client client = clientDao.findClient(1L);
    

    Где connection — это объект для доступа к БД, например, реализация java.sql.Connection или javax.sql.DataSource или вообще Ваш объект. Client — Ваш ORM объект и пожалуй единственная вещь от самого фреймворка — это класс com.reforms.orm.OrmDao, который покроет 98% всех Ваших нужд.

    Концепция


    >>2. А где описывается меппинг полей?

    Как и обещал выше, я затрону 2 вещи: стиль и концепцию. Чтобы ответить на второй вопрос, нужно рассказать о концепции. Посыл таков, что, чтобы предложить новое — нужен радикальный взгляд на решение. А как насчет SQL-92 парсера? Когда мне впервые пришла эта мысль, я откинул ее так далеко, что думал больше с ней не встречусь. Но как тогда сделать SQL-ориентированный фреймворк удобным? Пилить очередную надстройку? Или делать очередной хелпер к хелперу фреймворка? На мой взгляд, лучше ограничить поддерживаемый набор SQL конструкций — как неплохой компромисс, и взамен получить нечто удобное, на мой взгляд. Мэппинг осуществляется на основе дерева выражений, после парсинга SQL-запроса. В примере выше имена колонок соотносятся с именами полей ORM объекта один к одному. Разумеется, фреймворк поддерживает меппинг и более сложный, но о нем чуть позже.

    Примеры


    >> 3. А как насчет чего-то посложнее? А то эти примеры на 3 копейки уже надоели.

    А есть ли смысл вообще заморачиваться с SQL-92 парсером, если фреймворк не будет уметь делать, чего-то посложнее? Но показать все в примерах без объема — не простая задача. Разумеется я покажу, правда буду опускать местами декларации таблиц в SQL и части java-кода.

    Одной из немногих вещей, которая мне никогда не нравилась в SQL-ориентированных решениях — это необходимость конструирования SQL-запросов. Например, когда определенные критерии фильтрации могут быть заданы, а могут быть и не заданы. И Вам наверняка знаком такой фрагмент кода, точнее его упрощенный вариант:

        // где-то в DAO
        private String makeRegTimeFilter(Date beginDate, Date endDate) {
            StringBuilder filter = new StringBuilder();
            if (beginDate != null) {
                filter.append(" reg_time >= ?");
            }
            if (endDate != null) {
                if (filter.length() != 0) {
                    filter.append(" AND");
                }
                filter.append(" reg_time < ?");
            }
            return filter.length() == 0 ? null : filter.toString();
        }
    

    И написал я этот фрагмент именно так, как чаще всего встречаю в старых проектах. И это при том, что проверки дат появятся еще раз при установке значений в PreparedStatement. А что предлагается наш друг? А предлагает он динамические фильтры. На примере разбираться легче, поэтому посмотрим на поиск клиентов за определенный интервал:

    public interface IClientDao {
        @TargetQuery(query =
                "SELECT id, name, state " +
                "  FROM clients " +
                "  WHERE regTime >= ::begin_date AND " +
                "        regTime < ::end_date", type = QT_SELECT, orm = Client.class)
        List<Client> findClients(@TargetFilter("begin_date") Date beginDate,
                                 @TargetFilter("end_date") Date endDate);
    }
    

    И суть такова, что если значение параметра, например, beginDate будет равно null, то ассоциированный с ним SQL-фильтр regTime >= ::begin_date будет вырезан из финального SQL-запроса и в нашем случае на сервер БД уйдет следующая строка:

    SELECT id, name, state FROM clients WHERE regTime < ?

    Если оба значения будут равны null, то секции WHERE не будет в итоговом запросе. И заметьте — в коде только декларация и никакой логики. На мой взгляд, но очень хочется послушать и других, это сильное оружие и сильная сторона фреймворка. По java коду скажу, что я сам не фанат аннотаций и большое их количество в большинстве проектов просто раздражает. Поэтому предусмотрел определенную им альтернативу — фильтры по свойствам bean объекта:

    // Бойлерплейт get/set код вырезал. В своем файле.
    public class ClientFilter {
        private Date beginDate;
        private Date endDate;
    }
    public interface IClientDao {
        @TargetQuery(query =
                "SELECT id, name, state " +
                "  FROM clients " +
                "  WHERE regTime >= ::begin_date AND " +
                "        regTime < ::end_date", type = QT_SELECT, orm = Client.class)
        List<Client> findClients(@TargetFilter ClientFilter period);
    }
    

    Получается довольно лаконично и понятно. Про динамические фильтры стоит отдельно сказать, что они поддерживают, точнее они могут быть вырезанными из подзапросов и всех предикатов, исключая OVERLAPS и MATCH. Последние я ни разу не встречал в 'живых' SQL выражениях, но упоминание о них в спецификации SQL-92 имеется.

    Разумеется, фреймворк поддерживает и статические, обязательные к указанию фильтры. И синтаксис у них такой же, как в HQL или SpringTemplate — ': именованный параметр'.

    Правда, со статическими фильтрами всегда связана одна проблема: 'Как реагировать на null параметр'? Быстрым ответом кажется сказать — 'Кидай исключение, не ошибешься'. Но всегда ли это нужно? Давайте перейдем к примеру, загрузки клиентов с определенным статусом и проверим это:

    public interface IClientDao {
        @TargetQuery(query =
                "SELECT id, name, state " +
                "  FROM clients " +
                "  WHERE state = :state", type = QT_SELECT, orm = Client.class)
        List<Client> findClients(@TargetFilter("state") ClientState state);
    }
    

    Но вот задача, что делать, если статус клиента в БД может отсутствовать? Колонка state допускает NULL значения и нам нужен поиск именно таких клиентов, безстатусников? Концепция SQL-92 парсера спасает вновь. Достаточно заменить выражение фильтрации по статусу с ':state' на ':state?' как движок фреймворка модифицирует секцию WHERE в следующий вид '… WHERE state IS NULL', если, конечно, на вход методу падать null.

    Про меппинг


    Фильтры в запросах это конечно хорошо, но в java решениях большое внимание уделяется мэппингу результата работы SQL-запроса на ORM объекты и их связывание и связывание самих сущностей. Этого настолько много, посмотрите чего стоит одна спецификация JPA. Или посмотрите в своих проектах на перекладывания из ResultSet в объекты предметной области или установку значений в PreparedStatement. Громоздко, не правда ли? Я выбрал путь, может меннее надежный и менее изящный, но однозначно — простой. Зачем далеко ходить, когда можно устроить меппинг прям в SQL-запросе. Это ведь первое, что приходит в голову?

    Давайте сразу к примерам. И вот задача, как мепить результат запроса, если все колонки таблицы отличаются от полей ORM класса, плюс ORM имеет вложенный объект?

    ORM классы
    public class Client {
        private long clientId;
        private String clientName;
        private ClientState clientState;
        private Address address;
        private Date regTime;
        // ниже как обычно...
    }
    
    enum ClientState {
    
        ACTIVE(1),
        BLOCKED(2),
        DELETED(3);
    
        private int state;
    
        ClientState(int state) {
            this.state = state;
        }
    
        @TargetMethod
        public int getState() {
            return state;
        }
    
        @TargetMethod
        public static ClientState getClientState(int state) {
            return values()[state - 1]; // для примера сойдет
        }
    }
    
    public class Address {
        private long addressId;
        private String refCity;
        private String refStreet;
    }
    

    public interface IClientDao {
        @TargetQuery(query =
                "SELECT cl.id AS client_id, " + // underscore_case -> camelCase преобразование
                "       cl.name AS clientName, " + // можно сразу в camelCase
                "       cl.state AS client_state, " + // any_type to enum преобразование автоматически поддерживается, если в enum имеется аннотация @TargetMethod
                "       cl.regDate AS t#regTime, " + // t# - это директива, что нам нужна и дата и время java.sql.Timestamp -> java.util.Date
                "       addr.addressId AS address.addressId, " + // обращение к вложенному объекту через точку, а как еще?
                "       addr.city AS address.refCity, " +
                "       addr.street AS address.refStreet " +
                "  FROM clients cl, addresses addr" +
                "  WHERE id = :client_id", // допускается указание именованного параметра для простого фильтра
                type = QT_SELECT)
        Client findClient(long clientId);
    }
    

    Этот пример более преближен к реальным условиям. Эстетичность меппинга определенно хромает, но все же мне ближе этот вариант, чем бесконечные аннотации или xml файлы. Да, с надежностью беда, здесь и runtime и вопросы рефакторинга ORM объектов и не всегда возможность взять SQL и протестировать в своем любимом БД-клиенте. Но я не скажу, что это безвыходная ситуация — от runtime и рефакторинга спасают тесты. Чтобы запрос проверить в БД-клиенте придется его 'почистить'. Еще момент: Какой SQL-запрос уйдет на сервер БД? Все секции AS будут вырезаны из запроса. Если же потребуется сохранить, например, для client_id значение cid в качестве алиаса, то нужно добавить перед этим алиасом двоеточие: cl.id AS cid:client_id и cid будет жить -> cl.id AS cid в финальном запросе.

    И последнее, немного бизнес логики


    Идеальные дао, когда одна операция — один декларативный метод. И это конечно хорошо, но так бывает не всегда. В приложении часто нужно получить гибридную или составную сущность, когда часть данных формируется одним SQL-запросом, другая часть вторым SQL-запросом и т.д. Или сделать определенные склейки, проверки. Возможно это и не суть самого дао, но такие манипуляции как правило имеют место быть и их изолируют, но делают публичными, дабы вызывать из разных участков программы. Но что нам делать с интерфейсами, если все же приспичит засунуть бизнес логику в дао? Неожиданно для меня и к удивлению многих разработчиков в java8 появились дефолтные методы. Круто? Да я знаю, что новость протухла, ведь на дворе 2017 год, но не сыграть ли на этом? А что если скрестить декларативный стиль принятый за основу разработки DAO слоя и дефолтные методы для бизнес логики? А давайте посмортим, что из этого получится, если понадобиться добавить проверку ORM объекта на null и загрузить данные из другого DAO:

    public interface IClientDao {
        // этот метод уже видели
        @TargetQuery(query =
                "SELECT id, name, state " +
                "  FROM clients " +
                "  WHERE id = ?", type = QT_SELECT)
        Client findClient(long clientId);
    
        // получить дао внутри другого дао доступно из коробки
        IAddressDao getAddressDao();
    
        // метод с бизнес логикой
        default Client findClientAndCheck(long clientId, long addressId) throws Exception {
            Client client = findClient(clientId);
            if (client == null) {
                throw new Exception("Клиент с id '" + clientId + "' не найден");
            }
            // Здесь код может быть сколь угодно сложным, если нужно
            IAddressDao addressDao = getAddressDao();
            client.setAddress(addressDao.loadAddress(addressId));
            return client;
        }
    }
    

    Не знаю, прав ли, но мне хочется назвать это — интерфейсным программированием. Как-то так. На самом деле и все, чего хотелось рассказать. Но это точно не все, что умеет фреймворк, помимо оговоренного: выборку ограниченного числа колонок, управление схемами, постраничную разбивку (не для всего правда), манипуляции с упорядочиванием данных, отчеты, обновление, встаку и удаление.

    Заключение


    Не знаю дорогой читатель, удалось ли мне привнести чего-то нового, в этот заполненный мир SQL-фреймворков или нет, судить Вам. Но я попробовал и доволен тем, что попробовал. Жду с нетерпением критического взгляда на предложенный подход и идей, если таковые имеются. Решение доступно на гитхабе, ссылка на который уже указана в главе Немного обзора в списке SQL-ориентированных фреймворков последней строчкой. Всего хорошего.

    Редакции


    N1. Добавил тип объекта Client.class в аннотацию @TargetQuery для методов, возвращающих списки.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 37
    • +1

      При наличии парсера запроса немного странным выглядит необходимость указания его типа (type = QT_SELECT). Его можно из запроса извлечь.


      А также, не пробовали получить названия параметров методов, чтобы явно не писать в аннотации TargetFilter имена? Вроде это можно.

      • 0
        >Его можно из запроса извлечь

        В общем случае — нельзя. Хранимая процедура может возвращать результат как select, а может и нет. Узнать вы это можете — но только при выполнении.
        • 0

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

          • 0
            Не, ну в принципе в метаданных процедуры скорее всего есть все что нужно, я это просто к тому, что это а) это не слишком просто, б) не является частью стандарта SQL-92, и в) все это можно узнать только у базы, путем выполнения запроса. То есть просто парсер тут ничего не даст, по внешнему виду вызова процедуры ничего не видно.
        • 0
          А также, не пробовали получить названия параметров методов, чтобы явно не писать в аннотации TargetFilter имена

          В семерке, не знаю как в 8-9, есть такой аттрибут (речь про формат .class) MethodParameters в котором есть часть, отвечающая за имя параметра:
          MethodParameters_attribute {
                 u2 attribute_name_index;
                 u4 attribute_length;
                 u1 parameters_count;
                 {   u2 name_index;
                     u2 access_flags;
                 } parameters[parameters_count];
             }
          

          name_index — это индекс указывает на значение в ConstantPool, где храниться имя параметра, но в описании этого элемента имеем:
          The value of the name_index item must either be zero or a valid index into
          the constant_pool table.
          На своем опыте скажу, что частенько встречал 0, вместо реально-ожидаемых значений. Поэтому, отвечая на Ваш 2ой вопрос, скажу НЕТ. Если же имеется альтернативный способ извлечения, подскажите, буду Вам признателен.
          • 0

            посмотрите как это делает Spring Data — они же тоже имена у Param из имён переменных могут брать

            • 0

              Просто Netbeans в некоторых случаях показывает нормальные имена у параметров, даже если исходного кода или документации к джарнику не предоставляется, следовательно, эта информация где-то в class-файле. С другой стороны, это он делает не всегда, значит, она необязательная.


              Если вы по аннотациям генерируете код во время компиляции, то сделать точно можно. А если рефлексией балуетесь, то вот не всегда, выходит, можно будет.

          • +1
            Согласен и подумаю как справить. Сейчас это проблема связанна с тем, что выбор стратегии работы с типом SQL-запроса начинается задолго до непосредственного разбора самого запроса. промахнулся
            • +1
              >ограничить поддерживаемый набор SQL конструкций

              А вы уверены, что не потеряете при этом всю расширенную функциональность конкретной реализации?
              • 0
                А вы уверены, что не потеряете при этом всю расширенную функциональность конкретной реализации?


                Да нет конечно. Вы только гляньте на примерный обзор возможных конструкций в том или ином диалекте. Здесь и десятка людей не хватит, чтобы их все поддерживать. Здесь возможен на мой взгляд только компромисс и точечный выбор специфичных, но ЧАСТО используемых вещей, вроде постраничной разбивки, определенных форматов тип данных (даты, время). Возможно что-то еще. Но в целом, если придерживаться стандарта SQL-92, то думаю можно поддерживать оговоренную функциональность
                • 0
                  Увы, в стандарт например не входит такая вещь, как merge. И при этом она еще и делается сильно по-разному у MS SQL и PostgreSQL, так, для примера.
              • +1
                очень похоже на mybatis. и кстати, т.н. «динамические фильтры» идут там из коробки.
                • 0
                  Когда готовил статью, я погружался в парадигму того или иного существующего фреймворка, где-то, чтобы просто освежить память, с чем-то просто никогда не работал. Мне кажется, то, что Вы называете динамическими фильтрами в MyBatis суть Dynamic SQL и кодить там все же придется, но в xml. Из достоинств Dynamic SQL — достаточно неплохой язык выражений и допускает его быстрое расширение, если потребуется. Но ключевое слово здесь кодить. С похожестью, наверное я не соглашусь, он просто другой.
                  • 0
                    Мне кажется, то, что Вы называете динамическими фильтрами в MyBatis суть Dynamic SQL и кодить там все же придется, но в xml

                    Не понимаю, что в вашем представлении значит «кодить». Написание SQL к кодингу не относится?
                    Есть ещё один известный мне способ делать запросы с динамической подстановкой параметров — Sql Provider:
                    @InsertProvider(type=ContactMapperSqlProvider.class, method=ContactMapperSqlProvider.CREATE_CONTACT_QUERY_NAME)
                    Long createContact(Contact contact);
                    ...
                    class ContactMapperSqlProvider{
                            private static final String CREATE_CONTACT_QUERY_NAME = "createContact";
                    
                            public String createContact(Contact contact){
                                return new SQL() {{
                                    INSERT_INTO("TUTORS");
                                    VALUES("name", "#{name}").VALUES("surname", "#{surname}").VALUES("patronymic", "#{patronymic}").VALUES("description", "#{description}")
                                        .VALUES("user_id", "#{user.id}");
                                    if (contact.getBirthDate() != null)
                                        VALUES("birthday", "#{birthday}");
                                }}.toString();
                            }
                        }
                    

                    Чем то похоже на Criteria API в JPA/Hibernate, но выглядит симпатичнее
                    • 0
                      Да в том то и дело
                           if (contact.getBirthDate() != null) VALUES("birthday", "#{birthday}");
                      
                      Под этим я и понимаю кодинг — именно наличие оператора if.
                      Разумеется, указывать сам параметр и его значение в том или ином виде при любом подходе придется, другое дело для того же батиса, кто им мешал сделать так:
                           // как вариант N1
                           VALUES("birthday", "#{birthday}?");
                           // или так как другой вариант N2
                           VALUES("birthday", "#{birthday or nothing}");
                      
                      или еще, как-то, более декларативно, согласно их представлениям понятия краткости и лаконичности.
                      Самое главное, я ни в коем случае не пытаюсь приуменьшить значение батиса для эко системы java, не подумайте или какого-либо другого фреймворка. Более того, я даже очень рад, что нам с Вами есть что сравнивать и из чего выбирать
                      • 0
                        Как мне кажется, в данном случае явное лучше неявного, и вариант с if-ом будет более поддерживаемым.
                        • 0
                          Согласен, конструкция с if нагляднее. Краткость != удобство.
                • +3

                  ORM-фреймворки, ORM-объекты, SQL-фреймворки, XML, ещё какая-то херь — ради формирования текста sql- запроса? Да на кой это нужно?

                  • +1
                    это все не для формирвания sql запроса а скорее для удобногго и корректного маппинга в объекты.
                    • 0
                      Лично я не против такой позиции. Но если высказали А, то нужно сказать и Б.
                      A — есть у Вас подготовленный String sql-запрос к БД на выборку данных.
                      Б — а что дальше?
                      • 0
                        А дальше у нас всё просто — создаём соединение, создаём Recordset, выполняем запрос, итоги запроса обрабатываем так, как того требует текущая задача, закрываем соединение, и во избежание утечек памяти присваиваем использованным для выгрузки объектам значение Nothing.
                        А у Вас как?
                        • 0
                          А дальше у нас всё просто — создаём соединение, создаём Recordset, выполняем запрос, итоги запроса обрабатываем так, как того требует текущая задача, закрываем соединение, и во избежание утечек памяти присваиваем использованным для выгрузки объектам значение Nothing.
                          А у Вас как?
                          • 0
                            Пишешь sql-запрос, скармливаешь его движку, вуа ля и получаешь то, что нужно. Разумеется, соединение достаешь из пула, отработал таск, кладешь обратно.
                      • 0
                        В мире .net как-то с этим проще… например https://github.com/linq2db/linq2db или https://github.com/StackExchange/Dapper/blob/master/Readme.md
                      • 0
                        Скажите, а чем Вам таки не угодил JDBI? Вы указали, что рассматривали его, а в Вашем решении предлагаете нечто до боли напоминающее его Sql Objects: https://jdbi.github.io/#_sql_objects

                        Собственно, мы активно используем в продакшене именно его, и большинство методов DAO действительно выглядят как методы без реализации с аннотациями, внутри которых описаны sql-запросы.
                        • 0
                          Про угодил, не угодил я ничего в разрезе JDBI не писал. Его подход, а точнее стиль программирования очень похож c рассматриваемым решением. Он отлично покроет такие операции как INSERT, UPDATE и DELETE. Но больше всего в проектах приходится возиться с операцией SELECT. И здесь прослеживается основная разница: JDBI пошел по пути SpringTemplate — RowMapper, ColumnMapper и все такое. RefOrms пошел по пути анализа SQL-запроса, для того, чтобы мепить, фильтровать и прочие.
                        • +1
                          Автору спасибо за статью, но должен обратить внимание на орфографические ошибки — «преступить», «походу», «Да в прочим» и т.п. И разные переводы одного понятия — «меппинг — мэппинг».
                          • +1
                            А собственно я так и не понял, автор сам пишет такую библиотеку или показывает какую-то готовую?
                            • 0
                              сам
                              • 0
                                Для своего использования или в публику тоже выйдет?
                                • 0
                                  Исходники открыты на гитхабе, есть артефакт в мавен централ репозитории, не вижу каких-либо противопоказаний для использования желающими :) Другое дело, что его пилить нужно, батчинг, хранимые процедуры, генерация id для insert и так по-мелочи. Присоединяйтесь, коли будет желание, пару пул реквестов я уже заапрувил.
                                  • 0
                                    Интересно сравнение со Spring Data…
                            • 0
                              Вы забыли пласт фреймворков на базе EAV, который в сумме по объему рынка существенно обходит ORM.
                              Entity Attribute Value — аналог ORM, как и ORM является абстракцией над SQL.
                              Однако по своей механике довольно сильно отличается. Конечно же имеет как плюсы, так и минусы относительно ORM. Минусы относительно SQL у них схожи.
                              EAV есть основа платформ типа WP & Drupal. Которые сильно отличаются от MVC/ORM фреймворков, но при этом имеют примерно те же возможности, где то даже больше.
                              Сюда принимаем еще 2 факта:
                              — Грань между сайтом и веб приложением сегодня стерта
                              — Вслед за этим некогда простые CMS сумели эволюционировать в полноценные веб фреймворки

                              Однако они оставили за собой право лидеров рынка. Существенно обгоняя ORM подобные фреймворки как по доле рынка, так и по гибкости.

                              Я не против ORM, даже за в некоторых задачах. Также как за SQL в тех задачах где это уместно. Однако чаще всего я предпочитаю работать с EAV. Как и большинство участников мирового рынка если принимать во внимание доли реального использования и занимаемого объема рынка.

                              И тот факт что вы не учли сей тип фреймворков работы с данными — говорит о пробелах в данном материале.
                              Возможно стоит это восполнить.
                              • 0
                                Пробелы есть, скрывать не буду. Никогда не сталкивался в проде с таким решением.
                              • 0

                                Простите, из какого фреймворка из вышеперечисленных это: @TargetFilter?
                                И хотелось бы конечно сравнения, какие из них поддерживают эту и другие фичи..

                                • 0
                                  @TargetFilter из RefOrms
                                  Указание имени параметра фильтра в аннотации поддерживает JDBI, называется правда там это аннотация Bind.
                                  Сравнение фреймворков это дело хорошее, но боюсь выходит за рамки этой статьи

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.