Можно ли превратить объединенные таблицы базы данных в поток Java? Ответ — да. Поскольку мы задавали этот вопрос много раз, мы решили добавить еще одну практическую статью, объясняющую, как выполнять более сложные объединения потоков. Итак, вы, пятая статья из шести, дополненная репозиторием GitHub, содержащим инструкции и упражнения для каждого модуля.
Часть 1. Создание потоков
Часть 2: Промежуточные операции
Часть 3: Терминальные операции
Часть 4. Потоки базы данных
Часть 5. Превращение объединенных таблиц базы данных в потоки.
Часть 6: Создание приложения базы данных с использованием потоков
Поток ПРИСОЕДИНЯЕТСЯ
В прошлой статье мы указали на большое сходство потоковых и SQL-конструкций. Хотя в SQL-операции JOIN в общем случае отсутствует естественное отображение. Таким образом, Speedment использует свои собственные
JoinComponent
для объединения до 10 таблиц (используя INNER JOIN, RIGHT JOIN, LEFT JOIN или CROSS JOIN) безопасным для типов способом. Прежде чем мы представим JoinComponent
более подробно, мы рассмотрим сходство между отдельными таблицами и объединениями.
Ранее мы использовали диспетчер скорости в качестве дескриптора таблицы базы данных. Этот процесс представлен ниже:
Менеджер действует как дескриптор таблицы базы данных и может выступать в качестве источника потока. В этом случае каждая строка соответствует экземпляру фильма.
Теперь, когда мы хотим получить данные из нескольких таблиц, Manager
недостаточно. SQL-запрос JOIN выводит виртуальную таблицу, которая по-разному объединяет данные из нескольких таблиц (например, в зависимости от типа объединения и предложений WHERE). В Speedment эта виртуальная таблица представлена в виде объекта Join<T>
содержащего кортежи типа T
Присоединиться к компоненту
Для получения объекта Join нам нужен ранее упомянутый JoinComponent
который использует шаблон компоновщика. Получающиеся объекты соединения можно использовать повторно и выступать в роли дескрипторов «виртуальных таблиц соединения», как показано на следующем рисунке:
JoinComponent создает объект Join, который действует как дескриптор виртуальной таблицы (результат объединения) и может выступать в качестве источника потока. В этом случае каждая строка соответствует экземпляру Tuple2
Теперь, когда мы ввели понятие JoinComponent, мы можем начать демонстрировать, как оно используется.
Многие к одному
Мы начнем с рассмотрения отношения «многие к одному», когда несколько строк из первой таблицы могут совпадать с одной строкой во второй таблице. Например, один язык может использоваться во многих фильмах. Мы можем объединить две таблицы: фильм и язык, используя
JoinCompontent
:
1
2
3
4
|
Join<Tuple2<Film, Language>> join = joinComponent .from(FilmManager.IDENTIFIER) .innerJoinOn(Language.LANGUAGE_ID).equal(Film.LANGUAGE_ID) .build(Tuples::of); |
По сути, мы начинаем с таблицы Film и выполняем INNER JOIN с таблицей Language в строках, которые соответствуют language_id: s.
Затем мы можем использовать объект Join для потоковой передачи полученных кортежей и распечатывать их все для отображения. Как всегда с Streams, никакой определенный порядок элементов не гарантируется, даже если один и тот же элемент соединения используется повторно.
01
02
03
04
05
06
07
08
09
10
11
12
|
join.stream() .forEach(System.out::println); Tuple2Impl {FilmImpl { filmId = 1 , title = ACADEMY DINOSAUR, ... }, LanguageImpl { languageId = 1 , name = English, ... }} Tuple2Impl {FilmImpl { filmId = 2 , title = ACE GOLDFINGER, ... }, LanguageImpl { languageId = 1 , name = English, ... }} Tuple2Impl {FilmImpl { filmId = 3 , title = ADAPTATION HOLES, ... }, LanguageImpl { languageId = 1 , name = English, ... }} … |
Многие-ко-многим
Отношение «многие ко многим» определяется как отношение между двумя таблицами, где множество строк из первой таблицы могут совпадать с несколькими строками во второй таблице. Часто третья таблица используется для формирования этих отношений. Например, актер может участвовать в нескольких фильмах, а в фильме обычно есть несколько актеров.
Связь между фильмами и актерами в Сакиле описывается
FilmActor
который ссылается на фильмы и актеров с использованием внешних ключей. Следовательно, если мы хотим связать каждую запись в фильме с актерами, которые снялись в этом фильме, нам нужно объединить все три таблицы:
1
2
3
4
5
|
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); |
Мы начинаем с таблицы, описывающей отношения между фильмом и актером, и выполняем INNER JOIN с фильмом и актером при сопоставлении FILM_ID: s и ACTOR_ID: s соответственно.
Собрать Присоединиться к потоку на карту
Наш объект Join теперь может быть использован для создания карты, которая коррелирует
Film
со List
Actor
в главных ролях: с. Поскольку элементы нашего потока являются кортежами, мы должны указать на нужные записи. Это делается с использованием нулевых индексированных геттеров ( get0()
ссылающихся на FilmActor
и т. Д.).
1
2
3
4
5
6
|
Map<Film, List<Actor>> actorsInFilms = join.stream() .collect( groupingBy(Tuple3::get1, mapping(Tuple3::get2, toList()) ) ); |
Наконец мы печатаем записи, чтобы отобразить названия фильмов и актеров.
01
02
03
04
05
06
07
08
09
10
|
actorsInFilms.forEach((f, al) -> System.out.format( "%s : %s%n" , f.getTitle(), al.stream() .sorted(Actor.LAST_NAME) .map(a -> a.getFirstName() + " " + a.getLastName()) .collect(joining( ", " ) ) ) ); |
1
2
3
4
|
WONDERLAND CHRISTMAS : HARRISON BALE, CHRIS BRIDGES, HUMPHREY GARLAND, WOODY JOLIE, CUBA OLIVIER BUBBLE GROSSE : VIVIEN BASINGER, ROCK DUKAKIS, MENA HOPPER OPUS ICE : DARYL CRAWFORD, JULIA FAWCETT, HUMPHREY GARLAND, SEAN WILLIAMS … |
Таблицы фильтрации
Если мы изначально знаем, что нас интересует только подмножество
Записи в Film
, более эффективно избавиться от этих случаев, когда мы определяем объект Join
. Это делается с помощью оператора .where () -, который эквивалентен filter()
в потоке (и сопоставляется с ключевым словом SQL WHERE). В качестве фильтра он принимает Predicate
который оценивается как true или false и должен быть выражен с использованием Fields
ускорения для оптимизации. Здесь мы хотим найти язык фильмов с названиями, начинающимися с буквы «А»:
1
2
3
4
5
|
Join<Tuple2<Film, Language>> join = joinComponent .from(FilmManager.IDENTIFIER) .where(Film.TITLE.startsWith(“A”)) .innerJoinOn(Language.LANGUAGE_ID).equal(Film.LANGUAGE_ID) .build(Tuples::of); |
Если необходима дальнейшая фильтрация, можно составить любое количество операций .where (), так как они объединены с ключевым словом SQL И под капотом.
Специализированные Конструкторы
Софар, нам пришлось иметь дело с довольно абстрактными получателями кортежей (get0, get1 и так далее). Хотя при создании нашего объекта Join мы можем предоставить любой конструктор специализированным объектам. В приведенных выше примерах нас интересовали названия фильмов и имена актеров. Это позволяет нам определять наш собственный объект
TitleActorName
как таковое:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
final class TitleActorName { private final String title; private final String actorName; TitleActorName(Film film, Actor actor) { this .title = film.getTitle(); this .actorName = actor.getFirstName() + actor.getLastName(); } public String title() { return title; } public String actorName() { return actorName; } @Override public String toString() { return "TitleLanguageName{" + "title=" + title + ", actorName=" + actorName + '}' ; } } |
Затем мы предоставляем конструктор нашего собственного объекта конструктору Join и отбрасываем связывающий экземпляр FilmActor
поскольку он не используется:
1
2
3
4
5
|
Join<TitleActorName> 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) -> new TitleActorName(f, a)); |
Это значительно улучшает читаемость любых операций, связанных с результирующим объектом Join.
1
2
3
4
5
6
7
8
9
|
Map<String, List<String>> actorsInFilms = join.stream() .collect( groupingBy(TitleActorName::title, mapping(TitleActorName::actorName, toList()) ) ); actorsInFilms.forEach((f, al) -> System.out.format( "%s : %s%n" , f, al) ); |
Упрощение типов
Когда объединено большое количество таблиц, тип Java может быть утомительным для записи (например, Tuple5<...>
). Если вы используете более свежую версию Java, вы можете просто опустить тип локальной переменной, например:
1
2
3
4
5
|
var join = joinComponent .from(FilmManager.IDENTIFIER) .where(Film.TITLE.startsWith(“A”)) .innerJoinOn(Language.LANGUAGE_ID).equal(Film.LANGUAGE_ID) .build(Tuples::of); |
В этом случае Java автоматически определит тип Join<Tuple2<Film, Language>>
Если вы используете более старую версию Java, вы можете встроить объявление соединения и оператор потока следующим образом:
1
2
3
4
5
6
7
|
joinComponent .from(FilmManager.IDENTIFIER) .where(Film.TITLE.startsWith(“A”)) .innerJoinOn(Language.LANGUAGE_ID).equal(Film.LANGUAGE_ID) .build(Tuples::of) .stream() .forEach(System.out::println); |
упражнения
Упражнения на этой неделе потребуют объединенных знаний от всех предыдущих модулей и, следовательно, будут отличным продолжением предыдущих модулей. Все еще существует соединение с экземпляром базы данных Sakila в облаке, поэтому настройка Speedment не требуется. Как обычно, упражнения можно разместить в этом репозитории GitHub. Содержание этой статьи достаточно для решения пятого блока, который называется MyUnit5Extra
. Соответствующий интерфейс Unit5Extra
содержит JavaDocs, которые описывают предполагаемую реализацию методов в
MyUnit5Extra
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public interface Unit5Extra { /** * Creates and returns a new Map with Actors as keys and * a List of Films in which they appear as values. * <p> * The result might look like this: * * ActorImpl { actorId = 126, firstName = FRANCES, lastName = TOMEI, ... }=[FilmImpl { filmId = 21, title = AMERICAN CIRCUS, ...}, ...] * … * * @param joinComponent for data input * @return a new Map with Actors as keys and * a List of Films in which they appear as values */ Map<Actor, List<Film>> filmographies(JoinComponent joinComponent); |
Предоставленные тесты (например, Unit5ExtraTest
) будут действовать как автоматический инструмент оценки, сообщая вам, было ли ваше решение правильным или нет.
Следующая статья
Мы надеемся, что к настоящему времени нам удалось продемонстрировать, насколько аккуратен Stream API для запросов к базе данных. Следующая статья выйдет за рамки проката фильмов и позволит вам создавать автономные приложения баз данных на чистом Java для любого источника данных. Удачного кодирования!
Авторы
Пер Минборг
Юлия Густафссон
Ресурсы
GitHub Opensource Project ускорение
Speedment Stream ORM инициализатор
GitHub Repository «hol-streams»
Статья часть 1: Создание потоков
Статья Часть 2. Промежуточные операции
Статья Часть 3: Терминальные Операции
См. Оригинальную статью здесь: Станьте мастером потоков Java. Часть 5. Превратите объединенные таблицы базы данных в поток Мнения, высказанные участниками Java Code Geeks, являются их собственными. |