Ebean: Как создать столбец для геттера без свойства

Я не могу хранить вычисленные значения из Java в моей базе данных.

Например, у меня может быть класс Person с firstName и lastName. Мне может понадобиться метод получения, который возвращает общую длину имени Persons, не являясь фактическим свойством.

@Entity
public class Person extends Model {
    @Id
    public Long id;

    public String firstName;

    public String lastName;

    public Int getNameLength() {
        return firstName.length() + lastName.length();
    }

    public Person (String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Итак, если я создам новый Person вот так:

Person bob = new Person("Bob", "Roy");
bob.save();

Тогда в таблице должно получиться следующее:

|  id  |  first_name  |  last_name  |  name_length |
====================================================
|  1   |    "Bob"     |    "Roy"    |      6       |

Кто-нибудь знает возможно ли это?


person ncphillips    schedule 24.03.2016    source источник


Ответы (3)


Пожалуйста, ради старых и новых богов этого не делайте

Выполнение чего-то подобного полностью испортит вашу базу данных. Вы рано или поздно столкнетесь с проблемами. Просматривая свой профиль, вы получили степень CS, так что вы определенно прошли курс по базам данных. Помните вторую нормальную форму и Третья нормальная форма и как вы ее нарушите, если у вас есть атрибуты, которые зависят от других атрибутов.

Что вам нужно сделать, так это иметь либо временное поле (отмеченное @Transient), либо вы можете использовать геттер и предоставлять информацию оттуда. Каждый раз, когда вам нужно получить доступ к name_length, вы будете вызывать этот геттер, но вы не будете хранить информацию в базе данных.

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


Изменить в соответствии с требованием, указанным OP:

В JPA есть два способа объявления столбцов - либо в полях, либо в методах (геттерах / сеттерах). Это будет примерно так:

@Column(name = "complex_calculation") // Due to some bad requirement
public Integer getNameLength() {
    return fisrtName.length() + lastName.length();
}

Однако вы упомянули Ebean в своем вопросе, и Ebean не рассматривается как эталонная реализация JPA. Есть большая вероятность, что это еще не поддерживается, но вы можете попробовать это в своем конкретном случае.

Есть еще один способ, который доказал свою эффективность. Вы определяете свою модель следующим образом:

@Entity
public class Person extends Model {
    @Id
    private Long id;

    private String firstName;

    private String lastName;

    private Integer nameLength;

    public Long getId() {
        return id;
    }

    // getter for first name and last name with @Column annotation

    @Column(name = "complex_calculation")
    public Integer getNameLength() {
        return firstName.length() + lastName.length();
    }

    public Person (String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
        updateComplexCalculation();
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        updateComplexCalculation();
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        updateComplexCalculation();
    }

    private void updateComplexCalculation() {
        this.nameLength = firstName.length() + lastName.length();
    }
}

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

Следующий код:

Person p = new Person("foo", "bar");
p.save();

Logger.debug("Complex calculation: " + p.getNameLength());

p.setFirstName("somethingElse");
p.save();

Logger.debug("Complex calculation: " + p.getNameLength());

дает тогда:

[debug] application - Complex calculation: 6
[debug] application - Complex calculation: 16
person Anton    schedule 24.03.2016
comment
Я хорошо помню свои нормальные формы и согласен с вами. Но иногда возникают требования. Клиенту нужно поле в базе данных, но он не хочет, чтобы оно было просто вычисленным значением в базе данных. Это из соображений производительности. Это не настоящий вариант использования, поэтому длина не сработает. Правила вычисления этих полей значительно сложнее, существуют и используются в нашем Java-коде. Они нужны нам как поля при создании отчетов с помощью SQL, и было бы предпочтительнее не дублировать бизнес-логику в группе представлений SQL. - person ncphillips; 25.03.2016
comment
Я понимаю вашу точку зрения. Хотя мне все еще не нравится эта идея, я обновил свой ответ, указав, как заставить эту работу работать. Я предполагаю, что вам нужно будет определить свойство при использовании Ebean. Он должен работать без свойства в Hibernate. - person Anton; 25.03.2016
comment
Принял ваш комментарий. Тот факт, что он получал длину имени вручную вычислял значения, меня сбил с толку. - person ncphillips; 31.03.2016

Что не так с собственностью в модели? Просто заставьте его private добавить public геттер, но без сеттера, и, наконец, переопределить методы save и update.

private Integer nameLength;

// BTW shouldn't you also count the space between first and last name?
public Integer getNameLength() {
    return firstName.length() + lastName.length();
}

@Override
public void save() {
    nameLength = firstName.length() + lastName.length();
    super.save();
}

@Override
public void update() {
    nameLength = firstName.length() + lastName.length();
    super.update();
}

Если вы по-прежнему хотите избежать свойства в модели, вам нужно будет использовать пользовательский запрос SQL (возможно, также в рамках переопределенных методов save/update), как показано в другом ответе или в документации Ebean, обратите внимание, что будет выполняться как минимум 2 SQL-запроса на каждую операцию сохранения или обновления.

person biesior    schedule 25.03.2016

Спасибо Антону и Биесиору за то, что поделились некоторыми идеями.

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

public class Person {
    @Id
    public Long id;
    private String firstName;
    private String lastName;
    private Integer nameLength;

    public String getFirstName() { 
        return firstName; 
    }

    public String getLastName() { 
        return lastName; 
    }

    public void setFirstName() {
        this.firstName = firstName;
        calculateNameLength();
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        calculateNameLength();
    }

    private void calculateNameLength() {
        nameLength = getFirstName().length + getLastName().length;
    }

}

Это дает несколько преимуществ по сравнению с предлагаемыми методами обновления значения в методах save или update.

Пересчет в методе save или update означает, что нам нужно вызывать один из этих методов каждый раз, когда мы устанавливаем поле для объекта. Если мы этого не сделаем, nameLength выйдет из синхронизации. Я не мог, например, изменить лиц firstName, а затем использовать nameLength для чего-то еще, не сохраняя предварительно объект в базе данных.

Кроме того, использование методов _11 _ / _ 12_ связывает состояние объектов с Ebean. ORM предназначен для сохранения состояния объекта, а не для установки состояния объекта.

person ncphillips    schedule 31.03.2016
comment
Учитывая тот факт, что решение, которое вы решили выбрать, на самом деле является тем, что я предложил, вы можете просто принять мой ответ :) - person Anton; 31.03.2016
comment
Нет проблем, рад, что вы смогли решить свою проблему! Удачной игры :) - person Anton; 31.03.2016