Реформа 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 для методов, возвращающих списки.
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 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.
                                  Сравнение фреймворков это дело хорошее, но боюсь выходит за рамки этой статьи

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

                                Самое читаемое