Обработка создания объектов ORM до сохранения / генерации первичных ключей? - PullRequest
1 голос
/ 06 июня 2009

Терпите меня, стараясь максимально упростить мою проблему.

Я создаю новый объект ORM. Этот объект имеет автоматически сгенерированный первичный ключ, который создается в базе данных с использованием идентификатора. Внутри этого объекта находится дочерний объект, имеющий отношение многие к одному с родительским объектом. Одним из атрибутов, которые мне нужно установить для создания дочернего объекта, является первичный ключ родительского объекта, , который еще не был создан . Важно отметить, что первичный ключ дочерний объект - это составной ключ, который включает первичный ключ родительского объекта .

Диаграмма http://xs941.xs.to/xs941/09291/fieldrule.1degree221.png

На этой диаграмме FieldRule является дочерней таблицей , а SearchRule является родительской таблицей . Проблема в том, что SearchRuleId не был сгенерирован при создании объектов FieldRule. Так что связать их невозможно.

Как мне решить эту проблему?


Вот некоторые соответствующие фрагменты из классов сущностей, которые используют сопоставления на основе аннотаций.

С SearchRule.java (Родительский класс):

public class SearchRule implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = true)
    @Column(name = "ID")
    private Integer id;
    @Basic(optional = false)
    @Column(name = "Name", unique = true)
    private String name;
    @Basic(optional = false)
    @Column(name = "Threshold")
    private int threshold;
    @Basic(optional = false)
    @Column(name = "LastTouched", insertable = false, updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastTouched;
    @Column(name = "TouchedBy")
    private String touchedBy;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "searchRule", fetch = FetchType.LAZY)
    private Collection<FieldRule> fieldRuleCollection;
    @JoinColumn(name = "IndexTemplateId", referencedColumnName = "ID")
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private IndexTemplate indexTemplateId;

С FieldRule.java (дочерний класс):

public class FieldRule implements Serializable {
    private static final long serialVersionUID = 1L;
    @EmbeddedId
    protected FieldRulePK fieldRulePK;
    @Basic(optional = false)
    @Column(name = "RuleValue")
    private String ruleValue;
    @JoinColumns({@JoinColumn(name = "IndexTemplateId", referencedColumnName = "IndexTemplateId", insertable = false, updatable = false), @JoinColumn(name = "FieldNumber", referencedColumnName = "FieldNumber", insertable = false, updatable = false)})
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private Field field;
    @JoinColumn(name = "SearchRuleId", referencedColumnName = "ID", insertable = false, updatable = false)
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private SearchRule searchRule;

С FieldRulePK.java (дочерний класс ПК):

@Embeddable
public class FieldRulePK implements Serializable {
    @Basic(optional = false)
    @Column(name = "IndexTemplateId")
    private Integer indexTemplateId;
    @Basic(optional = false)
    @Column(name = "FieldNumber")
    private Integer fieldNumber;
    @Basic(optional = false)
    @Column(name = "SearchRuleId")
    private Integer searchRuleId;

Ответы [ 9 ]

3 голосов
/ 06 июня 2009

Почему вы должны установить первичный ключ исходного объекта в подобъектах? При правильном отображении ссылка будет автоматически установлена ​​приложением JPA.

Таким образом, ответ: сделать правильное отображение.

Если вам нужен более подробный ответ, укажите более подробный вопрос. В том числе:

  • исходный код участвующих классов
  • исходный код, используемый для создания и сохранения экземпляров
  • исключения произошли
  • информация о том, какую реализацию jpa вы используете

Изменить, после более подробной информации, где предусмотрено в вопросе:

Я думаю, что ваш встраиваемый ПК должен выглядеть примерно так:

@Embeddable
public class FieldRulePK implements Serializable {
    @Basic(optional = false)
    @Column(name = "IndexTemplateId")
    private Integer indexTemplateId;
    @Basic(optional = false)
    @Column(name = "FieldNumber")
    private Integer fieldNumber;
    @ManyToOne( ... some not so trivial details here ..)
    private SearchRule searchRule;
}

И свойство searchRule вашего FieldRule должно быть удалено. Ссылка на сущность в встраиваемом объекте должна приводить к полю id в базе данных.

2 голосов
/ 13 июля 2009

Думаю, это проблема проектирования базы данных. Если FieldRule может быть создан независимо от SearchRule (другими словами, SearchRuleId не является полем «не нулевым»), вам не нужно включать его в составной первичный ключ. Если SearchRuleId не может быть нулевым, тогда вам просто нужно сохранить объекты в правильном порядке, который ваш ORM должен обработать для вас, если ваше отображение правильное.

1 голос
/ 17 июля 2009

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

Ссылки на объекты не очень хорошо отображаются в мире баз данных, и поэтому у нас есть свойства первичного ключа. При этом использование свойств первичного ключа должно быть сведено к минимуму. Я считаю полезным минимизировать тип первичного ключа свойства , который отображается непосредственно на первичный ключ столбцы (обычно целочисленные свойства, которые сопоставляются со столбцом первичного ключа).

В любом случае, исходя из этого, вот как я думаю, вы должны сделать свое отображение (изменения выделены горизонтальными разделителями):

С FieldRule.java (дочерний класс):

public class FieldRule implements Serializable {
    private static final long serialVersionUID = 1L;
    @EmbeddedId
    protected FieldRulePK fieldRulePK;
    @Basic(optional = false)
    @Column(name = "RuleValue")
    private String ruleValue;

    // Removed field and searchRule mapping as those are already in the 
    // primary key object, updated setters/getters to pull properties from
    // primary key object
    public Field getField() {
        return fieldRulePK != null ? fieldRulePK.getField() : null;
    }
    public void getField(Field field) {
        // ... parameter validation ...
        if (fieldRulePK == null) fieldRulePK = new FieldRulePK();
        fieldRulePK.setField(field);
    }

    public SearchRule getSearchRule() {
        return fieldRulePK != null ? fieldRulePK.getSearchRule() : null;
    }
    public void setSearchRule(SearchRule searchRule) {
        // ... parameter validation ...
        if (fieldRulePK == null) fieldRulePK = new FieldRulePK();
        fieldRulePK.setSearchRule(searchRule);
    }

С FieldRulePK.java (детский класс ПК):

@Embeddable
public class FieldRulePK implements Serializable {

    // Map relationships directly to objects instead of using integer primary keys
    @JoinColumns({@JoinColumn(name = "IndexTemplateId", referencedColumnName = "IndexTemplateId", insertable = false, updatable = false), @JoinColumn(name = "FieldNumber", referencedColumnName = "FieldNumber", insertable = false, updatable = false)})
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private Field field;
    @JoinColumn(name = "SearchRuleId", referencedColumnName = "ID", insertable = false, updatable = false)
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private SearchRule searchRule;

SearchRule.java должно быть хорошо, как есть.

Надеюсь, все это имеет смысл.

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

1 голос
/ 16 июля 2009

Итог, ваши searchRule ДОЛЖНЫ быть сохранены, прежде чем ваши fieldRule s могут быть сохранены.

Однако вместо того, чтобы иметь определение столбца в поле, вы могли бы попытаться использовать его в геттере ...

@Embeddable
public class FieldRulePK implements Serializable {
    //snip other columns
    @Basic(optional = false)
    @Column(name = "SearchRuleId")
    private Integer getSearchRuleId()
    {
        return this.fieldRule.searchRule.getId();
    }
    private void setSearchRuleId(Integer id)
    {
        this.fieldRule.searchRule = new SearchRule(id);
    }

Это будет означать, что когда saveSearchRule(searchRule) каскадно входит в FieldRuleCollection для его сохранения, searchRuleId автоматически извлекается из searchRule после его сохранения, вместо того, чтобы хакерски добавлять его в.

Это означает, что все, что создает ваш объект FieldRulePK, должно передавать ссылку на его родителя, но в противном случае означает, что ваш хакерский цикл setSearchRuleId() не нужен.

1 голос
/ 15 июля 2009

Здесь что-то немного подозрительно. Вообще говоря, если у вас есть родительская сущность A и дочерняя сущность B, и вы сохраняете A с некоторыми дочерними элементами, правильный порядок операций - сначала вставка A в базу данных, а затем вставка потомков (я предполагаю правильный каскад от A до B). Так что в этом общем случае идентификаторы будут сгенерированы правильно, и все должно быть в порядке.

Однако, похоже, что в вашем случае дети (FieldRules) сохраняются первыми. Единственное разумное объяснение этого, которое я могу придумать, состоит в том, что если у вас есть дополнительная сущность C (в вашем случае, вероятно, полевая сущность), которая уже сохранена, когда ваш код работает, и имеет каскад для FieldRules. В этом случае у вас есть два конфликтующих каскада: один SearchRule -> FieldRule и другой Field -> FieldRule. Поскольку JPA не проводит интеллектуальный анализ этого, это случайный случай (и порядок загрузки), который будет вызван первым. И в вашем случае, вероятно, вызывается Field-> FieldRules, в результате чего дочерние элементы вставляются перед родительским.

Поэтому я бы попытался найти в вашем коде любые дополнительные каскады к FieldRules и попытаться их удалить. Если вы можете удалить их все, это, вероятно, решит вашу проблему

1 голос
/ 13 июля 2009

Публикация в основном потому, что я не могу оставить этот сложный комментарий ... но в любом случае ...

Обычно, когда я смотрю на вещи типа EmbeddedId, я вижу такие вещи из этого примера Embeddable keys . Обычно я ожидаю что-то вроде

С ChildPK.java :

@Basic(optional = false)
@Column(name = "ParentId")
private Parent parent;

Но здесь, я думаю, у нас есть 2 других FK, превращаемых в составные PK, IndexTemplateId и FieldNumber ... и идентификатор этого родительского объекта генерируется автоматически с использованием последовательности.

Теперь я предполагаю, что вы должны уже сохранять объект Parent до того, как пытаться сохранить дочерний объект, или вы должны пометить объект Parent в дочернем как каскадный, что должно обеспечить заполнение идентификатора, составные ключи, кажется, сильно усложняют проблема.

Поскольку это новый ORM, я бы посоветовал вам использовать один PK в каждой таблице вместо составных идентификаторов и просто иметь отношения FK между таблицами.

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

0 голосов
/ 15 июля 2009

Прямо сейчас моя работа вокруг делает что-то вроде

Collection<FieldRule> fieldRules = searchRule.getFieldRuleCollection();

if (searchRule.getId() == null)
{
    //null out the collection so it doesn't cascade on persist
    searchRule.setFieldRuleCollection(null);
    //save to get id
    dao.saveSearchRule(searchRule);
    for (FieldRule fr : fieldRules) {
        fr.getFieldRulePK().setSearchRuleId(searchRule.getId());
    }
}
//re set collection
searchRule.setFieldRuleCollection(fieldRules);
//remove double refrence, which jpa doesn't like, to FieldRuleCollection
fieldRules = null;

//save again, this time for real
dao.saveSearchRule(searchRule);

Мне кажется, что это действительно глупо, но это работает (может быть, я решаю некоторые другие проблемы, но они могут быть не связаны).

Должен быть лучший способ отключить каскаду для одного персистента.

0 голосов
/ 14 июля 2009

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

Если вы не кодируете какое-то конкретное значение в свои ключи (анти-шаблон базы данных), они могут быть любым случайным, уникальным значением, таким как UUID (GUID для Microsofties).

А вот о чем стоит подумать, когда вы используете свой уровень персистентности для генерации идентификатора / первичного ключа: используете ли вы первичный ключ сущности в хэш-коде или равно методу?

Если вы используете идентификатор / первичный ключ в методе hashcode / equals , тогда вы нарушите ожидаемый контракт от объектов при хранении в коллекции Java. См. Эту страницу Hibernate для более подробной информации.

0 голосов
/ 06 июня 2009

Почему «подобъекту» (я думаю, что вы имеете в виду «потомок») нужен ключ к родительскому объекту? Если у вас есть OneToMany для родительского объекта и ManyToOne для дочернего объекта с mappedBy, ваш дочерний объект уже будет иметь внешний ключ (и ссылку на родительский объект).

Кроме того, необходимо проверить каскад в аннотации родительского объекта OneToMany.

...