Вы читаете __hedin

Чт, 11 июл, 2013, 23:23
activejdbc — active record for Java

Посмотрел библиотечку под названием activejdbc.
Тэглайн гласит: ActiveJDBC is a Java implementation of Active Record design pattern. It was inspired by ActiveRecord ORM from Ruby on Rails.

Далее под катом. Может быть интересно жавакодерам.

Началось все с того, что ЛОРовец попоросил помощи, ну я ему эту помощь оказал (15-секундным гуглингом и набиванием трех строчек в идее, лол), и оказалось что эта activejdbc достаточно интересная, чтобы глянуть ближе.

Самое важное, это задекларированные принципы дизайна:

Design principles

  • Should infer metadata from DB (like ActiveRecord)

  • Should be very easy to work with

  • Should reduce amount of code to a minimum

  • No configuration, just conventions

  • Some conventions are overridable in code

  • No need to learn another language

  • No need to learn another QL - SQL is sufficient

  • Code must be lightweight and intuitive, should read like English

  • No sessions, no "attaching, re-attaching"

  • No persistence managers.

  • No classes outside your own models.

  • Models are lightweight, no transient fields

  • No proxying. What you write is what you get (WYWIWYG :))

  • Should have the least possible resistance to startup a project

  • No useless getters and setters (they just pollute code). You can still write them if you like.

  • No DAOs and DTOs - this is mostly junk code anyway


Звучит круто. Хочешь набыдлокодить немного обращений к БД - нафиг юзать монстров типа хибернейта, бери легковесную штуку, просто транслирующую все что ты пишешь один-в-один в sql.

Обзор документации

(https://code.google.com/p/activejdbc/wiki/Features)

  • - авторы в доке более 9000 раз упоминают TDD и тесты, написали жавовскую версию RSpec и даже имеют по этому поводу отдельную инструкцию в вики. Я уже написал в Кащенко %)

  • - используют инструментирование классов. Запускать на фазе generate-sources в мавене, например. Инструментирование работает с бетой Java8. Для остального юзается вывод информации из базки в рантайме.

  • - Геттеров и сеттеров действительно нету. Хочешь - пиши сам. Все эти детали выводятся из БД. Есть автогенерящиеся поля типа created_at, согласно идее, спизженой с Рельсов.

  • - Модель можно вообще оставить пустой, достаточно чтобы она существовала и наследовалась от Model.

  • - find, findFirst, findAll, where, count, findBySQL работают соответственно названию и являются статическими методами модели.

  • - Суррогатные первичные кличи. Композитные ПК делать нельзя. Можно переопределять название поля ПК с помощью аннотации. На используемую БД им наплевать.

  • - К результатам find* можно применять limit, offset, order by. Есть класс Paginator.

  • - "Сеттеры" можно чейнить, сохранять пачками (отдельный массив - значения, отдельный - поля), или сохарнять последовательно (поле,значение,поле,значение)

  • - save отличается от saveIt тем, что у первого проглатываются исключения.

  • - можно обновлять и удалять пачками, updateAll, deleteAll, и наркоманская конструкция

  • Person.update("name = ?, last_name = ?", "name like ?", "Steve", "Johnson", "%J%");

  • - Методы validate* , прописанные в static{} модели делают КО знает что, включая регэкспы. Есть метод Model.validate, можно переопределять сообщения валидации, предусмотрена i18n.

  • - два автоконвертера на дату и время, convert*, прописывающиеся в static{} модели

  • - если в таблице есть поле record_version типа LONG/NUMBER/etc, автоматически юзается optimistic locking. (напомню: если мы два раза получили одну и ту же запись, и апдейтнули первую копию, то при попытке работать со второй копией возникнет StaleModelException, "запись протухла")

  • - commit/rollback для транзакций с выключенным autocommit

  • - one-to-many работает из коробки, чтобы добавить child модели, нужно вызвать .add. Н-р, person.add(address);

  • - для many-to-many можно один объект добавлять нескольким родителям. Есть поиск элементов в родителе: doctor.getAll(Patient.class); Есть .deleteCascade();

  • - @Many2Many(other = Course.class, join = "registrations", sourceFKName = "astudent_id", targetFKName = "acourse_id")

  • - Полиморфные ассоцииации как в рельсах: @BelongsToPolymorphic(parents = {Product.class, Review.class}) (т.е. это many-to-many, где одной записи может соответствовать несколько родителей)

  • - Аннотация @Cached для моделей, кэш конфигурится в activejdbc.properties. Model.purgeCache().

  • - Жаватип Clob. Model.getString("clobField"). Для @cached моделей он вычитывает clob полностью и конвертирует в строку.

  • - .toXml (с настройкой количества пробелов в отступах, human-readable/single-string)

  • - .toJson (с настройкой только нужного диапазона, human-readable/single-string). Для Json в модели использует Jackson абсолютно внешним по отношению к activejdbc способом.

  • - .addListener, хуки на before/after save/create/delete/validation

  • - очень базовая встроенная статистика при включении ее в конфиге collectStatistics=true.

  • - кэширование для поздапросов: Address.findAll().include(User.class); притащит кроме адресов еще и кэшированный список юзверей. Наследников тоже можно притащить. Полученное при желании можно сконвертить в коллекции (мапы).

  • - лог через slf4j, можно включить фильтрацию лога по регэкспу (выводить в лог только то, что попадает под регэксп)

  • - обязательно инструментирование классов. Поддержка maven и ant. Проверил, что в Идее всё работает ОК.

  • - JSpec по аналогии с RSpec. В тестах можно писать как-то так: a(c.result()).shouldBeEqual(4); При этом a и the - синонимы нетипизированной проверки, а it - типизированной. Куча методов shouldBe*

  • - для миграций используется Carbon 5, рекомендуется грузить через мавен

  • - соединения подключаются к локальному треду. Можно открыть несколько соединений с разными базами. Своего connection pool нету, но он может попросить пул у JDNI. Для всяких Ораклов и Постгресов на модель можно поставить аннотацию @DbName("mydbname"), или юзать -Dactivejdbc.mydbname.schema=myschema

  • - comment.deleteCascadeExcept(Comment.getMetaModel().getAssociationForTarget("articles"));

  • - и еще какое-то deleteCascadeShallow(), типа удаляет только непостредственных потомков, и это очень быстро

  • - наследование. "WARNING Inheritance implementation has problems, it is not advised to use it at this time"



Ну и в завершение,
quick start

1) Поднимаем postgresql, создаем табличку:

CREATE SEQUENCE activejdbc_test_auto_id_users;

CREATE TABLE employees (
      id int NOT NULL DEFAULT nextval('activejdbc_test_auto_id_users') PRIMARY KEY,
      first_name VARCHAR(56),
      last_name VARCHAR(56)
  );


2) Делаем в идее новый проект mvn module

3) Создаем модельку, соответствующую БД:

import org.javalite.activejdbc.Model;

public class Employee extends Model {
    public String getFirstName(){
        return getString("first_name");
    }
}


4) Пишем запускатор и добавляем его в run configurations (не забываем на всякий случай в before launch поставить mvn:install или mvn:generate sources сразу после make):

import org.javalite.activejdbc.Base;

public class Main {
    public static void main(String [] args ){
        Base.open("org.postgresql.Driver", "jdbc:postgresql://localhost/test", "user", "password");
        Employee e = new Employee();
        e.set("first_name", "John");
        e.set("last_name", "Doe");
        e.saveIt();
        Employee foundEmpl = Employee.findFirst("first_name = ?", "John");
        String emplName = foundEmpl.getFirstName();
        System.out.println("Employee name: "+emplName);
    }
}


5) Фигачим в pom.xml нечто вроде

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>activeJdbcTest</groupId>
    <artifactId>activeJdbcTest</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.1-901.jdbc4</version>
        </dependency>
        <dependency>
            <groupId>org.javalite</groupId>
            <artifactId>activejdbc</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.javalite</groupId>
            <artifactId>activejdbc-instrumentation</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.6.4</version>
        </dependency>
    </dependencies>

    <properties>
        <generated.sources.dir>"${basedir}/target/generated/src"</generated.sources.dir>
    </properties>


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <fork>true</fork>
                    <meminitial>128m</meminitial>
                    <maxmem>512m</maxmem>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.javalite</groupId>
                <artifactId>activejdbc-instrumentation</artifactId>
                <version>1.4.1</version>
                <executions>
                    <execution>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>instrument</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${generated.sources.dir}</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>


6) Запускаем main() и PROFIT

Чт, 11 июл, 2013 16:51 (UTC)
wayerr

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

Чт, 11 июл, 2013 17:03 (UTC)
__hedin

о, белка :)
на ты ли на рельсах кодишь?
как они с этим справляются?

думаю для быстрого прототипирования оно вполне подходит. Чем это хуже, чем писать SQL ручками?

надо потестить, прежде чем ругать.

Они обещают транслировать напрямую в JDBC, т.е. твой вопрос направляется от автора фреймворка автору JDBC, я правильно понимаю?

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

вообще, белк, а нафига тебе "пара десятков джойнов"? Пара джойнов - и оперативная память сервака УЖЕ закончилась, куда дальше-то?

Чт, 11 июл, 2013 17:21 (UTC)
wayerr

>вообще, белк, а нафига тебе "пара десятков джойнов"? Пара джойнов - и оперативная память сервака УЖЕ закончилась, куда дальше-то?

вот поэтому я не юзаю JPA и прочие штуки с "несколько левелов умного кеширования" 8)

вот смотри, у меня есть "модель" которая мапится на таблицу, она может использоватся разными способами:
- построение запроса
- генерация базы
- маппинг результатов запроса на объекты

и вот, как только возникает задача сложнее чем выбрать кучку объектов, или задача автоматически сгенерировать запрос для поиска, все orm решения оказываются не гибкими,
- например просто сконвертировать результат произвольного запроса в, пусть, двумерный массив - с учетом типов из модели, уже облом.
- или узнать количество дочерних объектов?
- а еще была задумка - показвать юзеру в обекты в списках по имени, заданному аннотацией вида "Объект №${number} ${name} (тип: ${type.name})" очень удобно и прикольно, только orm тут мешает 8)

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

Чт, 11 июл, 2013 17:30 (UTC)
__hedin

так в том-то и дело, что это не ORM. Объекты используются исключительно ради своих названий. У моделей даже нет полей! Например, public class Employee extends Model {} — это вся модель. Ну то есть вообще вся, там больше ничего не будет, может валидаторы какие. Все остальное в рантайме спрашивается у БД. А уровнем ниже, внутри "чудо-библиотечки", это просто мапы ключ-значение, которые потом подставляются в plain sql. Т.е. "чудо-библиотечка" по сути это подставлялка ключ-значение в стандартные запросы, чтобы избежать неудобного синтаксиса PreparedStatement.

Чт, 11 июл, 2013 17:57 (UTC)
wayerr

>так в том-то и дело, что это не ORM. Объекты используются исключительно ради своих названий. У моделей даже нет полей!

черт, я полагал что они генерятся

>это просто мапы ключ-значение, которые потом подставляются в plain sql. Т.е. "чудо-библиотечка" по сути это подставлялка ключ-значение в стандартные запросы, чтобы избежать неудобного синтаксиса PreparedStatement.

тогда эта поделка хуже чем mybatis

Пт, 12 июл, 2013 06:59 (UTC)
__hedin

> тогда эта поделка хуже чем mybatis

ну майбатис щаз почти ынтерпрайз, его в вакансиях пишут

проста и неказиста жизнь быдлопрограммиста :3

Чт, 11 июл, 2013 17:13 (UTC)
__hedin

> али с циклическими зависимостями

не пиши их.
есди есть фундаментальные косяки, нормализуй на форму выше, а лучше сразу не делай фундаментальных косяков.

Чт, 11 июл, 2013 17:23 (UTC)
wayerr

гг, а у меня граф объектов в таблицах object, properties сидит, при чем тут стройная реляционная теория, если мы в реальном мире?

Чт, 11 июл, 2013 17:26 (UTC)
__hedin

я тоже могу сарказм — казалось бы, при чем тут couchbase2? :)