Статьи

Java Stream ORM теперь с соединениями

Speedment — это Java Stream ORM Toolkit и Runtime, которые позволяют просматривать таблицы базы данных как стандартные потоки Java. Поскольку вам не нужно смешивать Java и SQL, приложение становится намного более компактным, что ускоряет его разработку, менее подвержено ошибкам и проще в обслуживании. Потоки также строго типобезопасны и лениво построены так, что из базы данных извлекается только минимальный объем данных, поскольку элементы потребляются потоками.

Новая версия Speedment 3.1.1 «Гомер» теперь также поддерживает динамически объединенные таблицы, которые можно рассматривать как стандартные потоки Java. Это большая проблема при разработке приложений Java, которые исследуют отношения между таблицами базы данных.

В приведенных ниже примерах я использовал контент базы данных фильма Sakila с открытым исходным кодом для MySQL, который вы можете скачать здесь. Ускорение работает для любых основных типов реляционных баз данных, таких как Oracle, MySQL, Microsoft SQL Server, PostgreSQL, DB2, MariaDB, AS400 и других.

Потоковая передача по одной таблице

Следующий фрагмент кода создаст List всех объектов Film , для которых Film.RATING имеет значение «PG-13» и где List отсортирован в порядке Film.LENGTH:

1
2
3
4
List<Film> list = films.stream()
    .filter(Film.RATING.equal("PG-13"))
    .sorted(Film.LENGTH)
    .collect(toList());

Поток будет автоматически отображен в запросе SQL изнутри. Если мы включим потоковое ведение журнала, мы увидим следующее (подготовленный оператор «?» — переменные приведены в качестве значений в конце):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
SELECT
    `film_id`,`title`,`description`,`release_year`,
    `language_id`,`original_language_id`,
    `rental_duration`,`rental_rate`,
    `length`,`replacement_cost`,`rating`,`special_features`,
    `last_update`
FROM
    `sakila`.`film`
WHERE
    (`rating`  = ? COLLATE utf8_bin)
ORDER BY
    `length` ASC
 
values:[PG-13]

Таким образом, преимущество заключается в том, что вы можете выражать свои запросы к базе данных, используя типобезопасную Java, а затем использовать результат с помощью стандартных потоков Java. Вам не нужно писать какой-либо код SQL.

Соединение нескольких столов

Appart из таблицы «фильм», база данных Sakila также содержит другие таблицы. Одним из них является таблица «язык». Каждый объект Film имеет внешний ключ к Language , на котором говорят в фильме, используя столбец с именем «language_id».

В этом примере я покажу, как мы можем создать стандартный поток Java, представляющий объединение этих двух таблиц. Таким образом, мы можем получить поток Java совпадающих пар сущностей Film/Language .

Join объекты создаются с использованием JoinComponent который можно получить следующим образом:

1
2
3
4
5
// to see how a Speedment app is created. It is easy!
Speedment app = …;
 
JoinComponent joinComponent = app.getOrThrow(JoinComponent.class);

Как только мы захватили JoinComponent , мы можем начать создавать объекты Join следующим образом:

1
2
3
4
Join<Tuple2<Film, Language>> join = joinComponent
    .from(FilmManager.IDENTIFIER)
    .innerJoinOn(Language.LANGUAGE_ID).equal(Film.LANGUAGE_ID)
    .build(Tuples::of);

Теперь, когда мы определили наш объект Join мы можем создать реальный поток Java:

1
2
3
4
5
6
7
join.stream()
    .map(t2 -> String.format(
        "The film '%s' is in %s",
            t2.get0().getTitle(), // get0() -> Film
            t2.get1().getName()   // get1() -> Language
    ))
    .forEach(System.out::println);

Это даст следующий результат:

1
2
3
4
The film 'ACADEMY DINOSAUR' is in English
The film 'ACE GOLDFINGER' is in English
The film 'ADAPTATION HOLES' is in English
...

В приведенном выше коде метод t2.get0() извлекает первый элемент из кортежа ( Film ), а метод t2.get1() извлекает второй элемент из кортежа ( Language ). Общие кортежи по умолчанию встроены в Speedment, и поэтому Tuple2 не является классом Guava. Скорость не зависит от любой другой библиотеки. Ниже вы увидите, как вы можете использовать любой конструктор класса для соединяемых таблиц. Опять же, Speedment автоматически отобразит код SQL из Java и преобразует результат в поток Java. Если мы включим потоковое ведение журнала, мы сможем увидеть, как именно рендерит код SQL:

01
02
03
04
05
06
07
08
09
10
11
12
13
SELECT
    A.`film_id`,A.`title`,A.`description`,
    A.`release_year`,A.`language_id`,A.`original_language_id`,
    A.`rental_duration`,A.`rental_rate`,A.`length`,
    A.`replacement_cost`,A.`rating`,A.`special_features`,
    A.`last_update`,
    B.`language_id`,B.`name`,B.`last_update`
FROM
    `sakila`.`film` AS A
INNER JOIN
    `sakila`.`language` AS B
ON
    (B.`language_id` = A.`language_id`)

Интересно, что объект Join может быть создан один раз и многократно использоваться для создания новых потоков.

Отношения многие ко многим

База данных Sakila также определяет несколько отношений «многие ко многим». Например, таблица «film_actor» содержит ряды ссылок на фильмы с актерами. В каждом фильме может быть несколько актеров, и каждый актер мог появиться в нескольких фильмах. Каждая строка в таблице связывает определенный Film с определенным Actor . Например, если в Film изображены 12 entities, then актеров entities, then FilmActor содержит 12 записей, каждый из которых имеет одинаковый film_id, но разные actor_ids. Цель этого примера — создать полный список всех фильмов и актеров в потоке Java. Вот как мы можем объединить три таблицы вместе:

1
2
3
4
5
6
7
8
Join<Tuple3<FilmActor, Film, Actor>> join = joinComponent
    .from(FilmActorManager.IDENTIFIER)
    .innerJoinOn(Film.FILM_ID).equal(FilmActor.FILM_ID)
    .innerJoinOn(Actor.ACTOR_ID).equal(FilmActor.ACTOR_ID)
    .build(Tuples::of);
 
    join.stream()
        .forEach(System.out::println);

Приведенный выше код выдаст следующий вывод (отформатированный для удобства чтения):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
...
Tuple3Impl {
    FilmActorImpl { actorId = 137, filmId = 249, lastUpdate = 2006-02-15 05:05:03.0 },
    FilmImpl { filmId = 249, title = DRACULA CRYSTAL, description =...,
    ActorImpl { actorId = 137, firstName = MORGAN, lastName = WILLIAMS,...}
}
 
Tuple3Impl {
    FilmActorImpl { actorId = 137, filmId = 254, lastUpdate = 2006-02-15 05:05:03.0 },
    FilmImpl { filmId = 254, title = DRIVER ANNIE, description = ...,
    ActorImpl { actorId = 137, firstName = MORGAN, lastName = WILLIAMS, ...}
}
 
Tuple3Impl {
    FilmActorImpl { actorId = 137, filmId = 263, lastUpdate = 2006-02-15 05:05:03.0 },
    FilmImpl { filmId = 263, title = DURHAM PANKY, description = ... },
    ActorImpl { actorId = 137, firstName = MORGAN, lastName = WILLIAMS,... }
}
...

Объединяет пользовательские кортежи

Как мы заметили в приведенном выше примере, у нас нет фактического использования объекта FilmActor в Stream, поскольку он используется только для связывания объектов Film и Actor во время фазы соединения.

Когда объекты Join создаются с использованием метода build() , мы можем предоставить собственный конструктор, который мы хотим применить к входящим объектам из базы данных. Конструктор может быть любого типа, так что вы можете написать свои собственные объекты Java, которые содержат, например, Film и Actor или любой из столбцов, которые они содержат и которые представляют интерес.

В этом примере я доказал (лямбда) конструктор, который просто отбрасывает ссылки на объекты FilmActor :

1
2
3
4
5
6
7
8
Join<Tuple2<Film, Actor>> join = joinComponent
    .from(FilmActorManager.IDENTIFIER)
    .innerJoinOn(Film.FILM_ID).equal(FilmActor.FILM_ID)
    .innerJoinOn(Actor.ACTOR_ID).equal(FilmActor.ACTOR_ID)
    .build((fa, f, a) -> Tuples.of(f, a));
 
    join.stream()
        .forEach(System.out::println);

Приведенный выше код выдаст следующий вывод (отформатированный для удобства чтения):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
...
Tuple2Impl {
    FilmImpl { filmId = 249, title = DRACULA CRYSTAL, description = ... },
    ActorImpl { actorId = 137, firstName = MORGAN, lastName = WILLIAMS, ...}
}
Tuple2Impl {
    FilmImpl { filmId = 254, title = DRIVER ANNIE, description = A... },
    ActorImpl { actorId = 137, firstName = MORGAN, lastName = WILLIAMS,...}
}
Tuple2Impl {
    FilmImpl { filmId = 263, title = DURHAM PANKY, description = ... },
    ActorImpl { actorId = 137, firstName = MORGAN, lastName = WILLIAMS,...}
}
...

Таким образом, мы получаем только совпадающие пары сущностей Film и Actor где в фильме присутствует актер. FilmActor объект FilmActor никогда не FilmActor в Stream.

Возьми это за спин!

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

Посетите сайт с открытым исходным кодом Speedment на GitHub и попробуйте!

Прочитайте все о новой функциональности JOIN в Руководстве пользователя .

Смотрите оригинальную статью здесь: Java Stream ORM теперь с соединениями

Мнения, высказанные участниками Java Code Geeks, являются их собственными.