Java: SQL-Abfragen automatisch generieren

    In diesem Artikel beschreibe ich die Erstellung eines Frameworks zum automatischen Generieren von SQL-Abfragen basierend auf Java-Klassen und -Objekten. Ich verstehe, dass es bereits viele fertige ähnliche Lösungen gibt, aber ich wollte dies selbst implementieren.

    Zum Erstellen des Frameworks werden Java-Anmerkungen und die Java Reflection-API verwendet.

    Also fangen wir an.


    Beginnen wir mit einigen Anwendungsfällen.


    Beispiel Nr. 1


    Angenommen, wir haben eine bestimmte Personenklasse:

    public static class Person {
        public String firstName;
        public String lastName;
        public int age;
    }
    

    Der folgende Aufruf erzeugt eine SQL-Abfrage, um eine Tabelle basierend auf dieser Klasse zu erstellen:

    System.out.println(MySQLQueryGenerator.generateCreateTableQuery(Person.class));
    

    Wenn wir es ausführen, erhalten wir die folgende Ausgabe in der Konsole:

    CREATE TABLE  `Person_table` (
    `firstName` VARCHAR(256),
    `lastName` VARCHAR(256),
    `age` INT);
    

    Beispiel Nr. 2


    Das Beispiel ist jetzt mit Anmerkungen komplizierter:

    @IfNotExists // Добавлять в CREATE-запрос IF NOT EXISTS
    @TableName("persons") // Произвольное имя таблицы
    public static class Person {
        @AutoIncrement // Добавить модификатор AUTO_INCREMENT
        @PrimaryKey // Создать на основе этого поля PRIMARY KEY
        public int id;
        @NotNull // Добавить модификатор NOT NULL
        public long createTime;
        @NotNull
        public String firstName;
        @NotNull
        public String lastName;
        @Default("21") // Значение по умолчанию
        public Integer age;
        @Default("")
        @MaxLength(1024) // Длина VARCHAR
        public String address;
        @ColumnName("letter") // Произвольное имя поля
        public Character someLetter;
    }
    

    Basierend auf dieser Klasse erhalten wir die folgende SQL-Abfrage:

    CREATE TABLE IF NOT EXISTS `persons` (
    `id` INT AUTO_INCREMENT,
    `createTime` BIGINT NOT NULL,
    `firstName` VARCHAR(256) NOT NULL,
    `lastName` VARCHAR(256) NOT NULL,
    `age` INT DEFAULT '21',
    `address` VARCHAR(1024) DEFAULT '',
    `letter` VARCHAR(1),
    PRIMARY KEY (`id`));
    

    Beispiel Nr. 3


    Ich habe auch die MySQLClient- Klasse erstellt , die eine Verbindung zum Datenbankserver herstellen und dort generierte SQL-Abfragen senden kann.

    Der Client enthält die folgenden Methoden: createTable , alterTable , insert , update , select .

    Es wird ungefähr so ​​verwendet:

    MySQLClient client = new MySQLClient("login", "password", "dbName");
    client.connect(); // Подключаемся к БД
    client.createTable(PersonV1.class); // Создаем таблицу
    client.alterTable(PersonV1.class, PersonV2.class); // Изменяем таблицу
    PersonV2 person = new PersonV2();
    person.createTime = new Date().getTime();
    person.firstName = "Ivan";
    person.lastName = "Ivanov";
    client.insert(person); // Добавляем запись в таблицу
    person.age = 28;
    person.createTime = new Date().getTime();
    person.address = "Zimbabve";
    client.insert(person);
    person.createTime = new Date().getTime();
    person.firstName = "John";
    person.lastName = "Johnson";
    person.someLetter = 'i';
    client.insert(person);
    List selected = client.select(PersonV2.class); // Извлекаем из таблицы все данные
    System.out.println("Rows: " + selected.size());
    for (Object obj: selected) {
        System.out.println(obj);
    }
    client.disconnect(); // Отключаемся от БД
    

    Wie funktioniert das?


    Zunächst verwendet der Algorithmus die Reflection-API, um alle öffentlichen und nicht statischen Felder der Klasse zu durchlaufen. Wenn das Feld in diesem Fall einen vom Algorithmus unterstützten Typ hat (alle primitiven Datentypen, ihre Objektanaloga sowie der String-Typ werden unterstützt), wird aus dem Field- Objekt ein Column- Objekt erstellt , das Daten über das Datenbanktabellenfeld enthält. Die Konvertierung zwischen Java-Datentypen und MySQL-Typen erfolgt automatisch. Außerdem werden aus den Annotationen des Felds und der Klasse alle Modifikatoren der Tabelle und ihrer Felder extrahiert. Dann wird eine SQL-Abfrage aus allen Spalten gebildet :

    public static String generateCreateTableQuery(Class clazz) throws MoreThanOnePrimaryKeyException {
            List columnList = new ArrayList<>();
            Field[] fields = clazz.getFields(); // получаем массив полей класса
            for (Field field: fields) {
                int modifiers = field.getModifiers();
                if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { // если public и не static
                    Column column = Column.fromField(field); // преобразуем Field в Column
                    if (column!=null) columnList.add(column);
                }
            }
            /* из полученных Column генерируем запрос */
    }
    /***************************/
    public static Column fromField(Field field) {
        Class fieldType = field.getType(); // получаем тип поля класса
        ColumnType columnType;
        if (fieldType == boolean.class || fieldType == Boolean.class) {
            columnType = ColumnType.BOOL;
        } /* перебор остальных типов данных */ {
        } else if (fieldType==String.class) {
            columnType = ColumnType.VARCHAR;
        } else { // Если тип данных не поддерживается фреймворком
            return null;
        }
        Column column = new Column();
        column.columnType = columnType;
        column.name = field.getName();
        column.isAutoIncrement = field.isAnnotationPresent(AutoIncrement.class);
        /* перебор остальных аннотаций */
        if (field.isAnnotationPresent(ColumnName.class)) { // если установлено произвольное имя таблицы
            ColumnName columnName = (ColumnName)field.getAnnotation(ColumnName.class);
            String name = columnName.value();
            if (!name.trim().isEmpty()) column.name = name;
        }
        return column;
    }
    

    Ebenso werden ALTER TABLE-, INSERT- und UPDATE-Abfragen generiert. Bei den beiden letzteren werden zusätzlich zur Spaltenliste auch die Werte der Felder aus dem Objekt extrahiert:

    Column column = Column.fromField(field);
    if (column!=null) {
        if (column.isAutoIncrement) continue;
        Object value = field.get(obj);
        if (value==null && column.hasDefaultValue) continue; // есть один нюанс: для корректной работы значений по умолчанию предпочтительно использовать объектные типы вместо примитивных
        if (column.isNotNull && value==null) {
            throw new NotNullColumnHasNullValueException();
        }
        String valueString = value!=null ? "'" + value.toString().replace("'","\\'") + "'" : "NULL";
        String setValueString = "`"+column.name+"`="+valueString;
        valueStringList.add(setValueString);
    }
    

    Es gibt auch eine ResultSetExtractor- Klasse im Framework , deren Methode extractResultSet (ResultSet resultSet, Class clazz) automatisch eine Liste von Objekten der clazz-Klasse aus resultSet erstellt. Dies geschieht ganz einfach, so dass ich das Prinzip seiner Wirkung hier nicht beschreiben werde.

    Auf Github können Sie den vollständigen Quellcode des Frameworks sehen . Das ist alles für mich.

    Jetzt auch beliebt: