Статьи

Как: вставлять и читать из базы данных с помощью Json

В этой статье мы создадим плагин для Speedment, который генерирует логику сериализации и десериализации с использованием Gson, чтобы упростить отображение между объектами базы данных и строками JSON. Это поможет продемонстрировать расширяемость генерации кода Speedment и в то же время изучить некоторые интересные возможности библиотеки Gson.

Speedment — это инструмент генерации кода для Java, который подключается к базе данных и использует его в качестве справочного материала для создания файлов сущностей и менеджера для вашего проекта. Инструмент очень модульный, позволяющий вам писать собственные плагины, которые изменяют внешний вид получаемого кода. Одна вещь, о которой несколько человек упомянули в чате Gitter, заключается в том, что объекты Speedment объявлены абстрактными, что предотвращает их автоматическую десериализацию. В этой статье мы рассмотрим, как можно десериализовать объекты Speedment с помощью Gson, автоматически создавая собственный TypeAdapter для каждой таблицы в базе данных. Это не только даст нам лучшую производительность при работе с JSON-представлениями содержимого базы данных, но также может послужить общим примером того, как вы можете расширить генератор кода для решения ваших проблем.

Шаг 1: Создание проекта плагина

В предыдущей статье я подробно рассказал о том, как создать новый плагин для Speedment, так что вот короткая версия. Создайте новый проект Maven и установите Speedment и Gson как зависимости.

pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<name>Speedment Gson Plugin</name>
<description>
    A plugin for Speedment that generates Gson Type Adapters for every
    table in the database.
</description>
 
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <speedment.version>2.3.7</speedment.version>
</properties>
 
<dependencies>
    <dependency>
        <groupId>com.speedment</groupId>
        <artifactId>speedment</artifactId>
        <version>${speedment.version}</version>
    </dependency>
 
    <dependency>
        <artifactId>gson</artifactId>
        <groupId>com.google.code.gson</groupId>
        <version>2.6.2</version>
    </dependency>
</dependencies>

Шаг 2. Создайте класс переводчика для адаптера типа

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

GeneratedTypeAdapterTranslator.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
public GeneratedTypeAdapterTranslator(
       Speedment speedment, Generator gen, Table table) {
    super(speedment, gen, table, Class::of);
}
 
@Override
protected Class makeCodeGenModel(File file) {
    return newBuilder(file, getClassOrInterfaceName())
        .forEveryTable((clazz, table) -> {
            // Code generation goes here
        }).build();
}
 
@Override
protected String getClassOrInterfaceName() {
    return "Generated" + getSupport().typeName() + "TypeAdapter";
}
 
@Override
protected String getJavadocRepresentText() {
    return "A Gson Type Adapter";
}
 
@Override
public boolean isInGeneratedPackage() {
    return true;
}
...

Каждый переводчик создается с использованием шаблона компоновщика, который можно вызвать с помощью метода newBuilder (). Это становится важным позже, когда мы хотим изменить существующий переводчик. Фактический код генерируется внутри метода forEveryTable () конструктора. Это обратный вызов, который будет выполнен для каждой таблицы, которая встречается в области интересов. В этом случае транслятор будет выполняться только для одной таблицы за раз, поэтому обратный вызов будет выполняться только один раз.

Для полных источников для GeneratedTypeAdapterTranslator-class, пожалуйста, перейдите на эту страницу github .

Шаг 3. Создание декоратора для изменения интерфейса диспетчера

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

GeneratedManagerDecorator.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public final class GeneratedManagerDecorator
implements TranslatorDecorator<Table, Interface> {
 
    @Override
    public void apply(JavaClassTranslator<Table, Interface> translator) {
        translator.onMake((file, builder) -> {
            builder.forEveryTable(Translator.Phase.POST_MAKE,
            (clazz, table) -> {
                clazz.add(Method.of(
                    "fromJson",
                    translator.getSupport().entityType()
                ).add(Field.of("json", STRING)));
            });
        });
    }
}

Декоратор похож на переводчик, за исключением того, что он определяет только те изменения, которые должны быть внесены в существующий файл. Каждый декоратор выполняется в определенной фазе. В нашем случае мы хотим выполнить после того, как код по умолчанию был сгенерирован, поэтому мы выбираем POST_MAKE. Логика, которую мы хотим добавить, проста. В интерфейсе мы хотим, чтобы потребовался дополнительный метод fromJson (String). Нам не нужно определять toJson, так как каждый менеджер Speedment уже имеет его из унаследованного интерфейса.

Шаг 4. Создание декоратора для изменения реализации менеджера

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

GeneratedManagerImplDecorator.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public final class GeneratedManagerImplDecorator
implements TranslatorDecorator<Table, Class> {
 
    @Override
    public void apply(JavaClassTranslator<Table, Class> translator) {
        final String entityName = translator.getSupport().entityName();
        final String typeAdapterName = "Generated" + entityName +
            "TypeAdapter";
        final String absoluteTypeAdapterName =
            translator.getSupport().basePackageName() + ".generated." +
            typeAdapterName;
 
        Final Type entityType = translator.getSupport().entityType();
         
        translator.onMake((file, builder) -> {
            builder.forEveryTable(Translator.Phase.POST_MAKE,
            (clazz, table) -> {
                 
                // Make sure GsonBuilder and the generated type adapter
                // are imported.
                file.add(Import.of(Type.of(GsonBuilder.class)));
                file.add(Import.of(Type.of(absoluteTypeAdapterName)));
                 
                // Add a Gson instance as a private member
                clazz.add(Field.of("gson", Type.of(Gson.class))
                    .private_().final_()
                );
                 
                // Find the constructor and define gson in it
                clazz.getConstructors().forEach(constr -> {
                    constr.add(
                        "this.gson = new GsonBuilder()",
                        indent(".setDateFormat(\"" + DATE_FORMAT + "\")"),
                        indent(".registerTypeAdapter(" + entityName +
                            ".class, new " + typeAdapterName + "(this))"),
                        indent(".create();")
                    );
                });
                 
                // Override the toJson()-method
                clazz.add(Method.of("toJson", STRING)
                    .public_().add(OVERRIDE)
                    .add(Field.of("entity", entityType))
                    .add("return gson.toJson(entity, " + entityName +
                         ".class);"
                    )
                );
                 
                // Override the fromJson()-method
                clazz.add(Method.of("fromJson", entityType)
                    .public_().add(OVERRIDE)
                    .add(Field.of("json", STRING))
                    .add("return gson.fromJson(json, " + entityName +
                        ".class);"
                    )
                );
            });
        });
    }
}

Шаг 5: Установите все новые классы на платформу

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

GsonComponent.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public final class GsonComponent extends AbstractComponent {
 
    public GsonComponent(Speedment speedment) {
        super(speedment);
    }
 
    @Override
    public void onResolve() {
        final CodeGenerationComponent code =
            getSpeedment().getCodeGenerationComponent();
 
        code.put(Table.class,
            GeneratedTypeAdapterTranslator.KEY,
            GeneratedTypeAdapterTranslator::new
        );
        code.add(Table.class,
            StandardTranslatorKey.GENERATED_MANAGER,
            new GeneratedManagerDecorator()
        );
        code.add(Table.class,
             StandardTranslatorKey.GENERATED_MANAGER_IMPL,
             new GeneratedManagerImplDecorator()
        );
    }
 
    @Override
    public Class<GsonComponent> getComponentClass() {
        return GsonComponent.class;
    }
 
    @Override
    public Software asSoftware() {
        return AbstractSoftware.with("Gson Plugin", "1.0", APACHE_2);
    }
 
    @Override
    public Component defaultCopy(Speedment speedment) {
        return new GsonComponent(speedment);
    }
}

GsonComponentInstaller.java

1
2
3
4
5
6
7
8
public final class GsonComponentInstaller
implements ComponentConstructor<GsonComponent> {
 
    @Override
    public GsonComponent create(Speedment speedment) {
        return new GsonComponent(speedment);
    }
}

использование

Когда мы хотим использовать наш новый плагин в проекте, мы просто добавляем его в качестве зависимости как в разделе зависимостей в pom, так и в качестве зависимости в плагине speedment maven. Затем мы добавляем тег конфигурации в плагин, как показано ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<plugin>
    <groupId>com.speedment</groupId>
    <artifactId>speedment-maven-plugin</artifactId>
    <version>${speedment.version}</version>
 
    <dependencies>
        <dependency>
            <groupId>com.speedment.plugins</groupId>
            <artifactId>gson</artifactId>
            <version>1.0.0</version>
        </dependency>
 
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>
    </dependencies>
 
    <configuration>
        <components>
            <component implementation="com.speedment.plugins.gson.GsonComponentInstaller" />
        </components>
    </configuration>
</plugin>

Затем мы можем восстановить наш код, и у нас должен быть доступ к новой логике сериализации и десериализации.

01
02
03
04
05
06
07
08
09
10
11
final String pippi = "{" +
    "\"id\":1," +
    "\"bookId\":-8043771945249889258," +
    "\"borrowedStatus\":\"AVAILABLE\"," +
    "\"title\":\"Pippi Långström\"," +
    "\"authors\":\"Astrid Lindgren\"," +
    "\"published\":\"1945-11-26\"," +
    "\"summary\":\"A story about the world's strongest little girl.\"" +
    "}";
 
books.fromJson(pippi).persist();

Резюме

В этой статье мы создали новый плагин Speedment, который генерирует адаптеры типа Gson для каждой таблицы в базе данных и интегрирует эти адаптеры с существующим поколением менеджеров. Если вы хотите больше примеров того, как вы можете использовать генератор кода Speedment для повышения вашей производительности, посетите страницу GitHub !

До скорого!