Как использовать em.merge () для вставки ИЛИ обновления для сущностей jpa, если первичный ключ генерируется базой данных? - PullRequest
8 голосов
/ 22 октября 2010

У меня есть объект JPA, подобный этому:

@Entity
@Table(name = "category")
public class Category implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Integer id;

    @Basic(optional = false)
    @Column(name = "name")
    private String name;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "category")
    private Collection<ItemCategory> itemCategoryCollection;

    //...
}

Используйте Mysql в качестве основной базы данных. «Имя» разработано как уникальный ключ. Используйте Hibernate в качестве поставщика JPA.

Проблема с использованием метода слияния состоит в том, что, поскольку pk генерируется db, поэтому, если запись уже существует (имя уже существует), Hibernate попытается вставить ее в db, и я получу исключение нарушения ограничения уникального ключа и не делаю обновления. У кого-нибудь есть хорошая практика, чтобы справиться с этим? Спасибо!

P.S: мой обходной путь такой:

public void save(Category entity) {

    Category existingEntity = this.find(entity.getName());
    if (existingEntity == null) {
       em.persist(entity);
       //code to commit ...
    } else {
        entity.setId(existingEntity.getId());
        em.merge(entity);
        //code to commit ...
    }
}

public Category find(String categoryName) {
    try {
        return (Category) getEm().createNamedQuery("Category.findByName").
                setParameter("name", categoryName).getSingleResult();
    } catch (NoResultException e) {
        return null;

    }
}

Ответы [ 2 ]

9 голосов
/ 23 октября 2010

Как использовать em.merge () для вставки ИЛИ обновления для сущностей jpa, если первичный ключ генерируется базой данных?

Независимо от того, используете ли вы сгенерированные идентификаторы или нет, IMO не имеет значения. Проблема здесь в том, что вы хотите реализовать «upsert» на каком-то уникальном ключе, отличном от PK, и JPA на самом деле не обеспечивает эту поддержку (merge зависит от идентичности базы данных).

Таким образом, у вас есть 2 варианта AFAIK.

Либо сначала выполните INSERT, либо внедрите некоторый механизм повтора в случае сбоя из-за нарушения уникального ограничения, а затем найдите и обновите существующую запись (используя новый диспетчер сущностей).

Или сначала выполните SELECT, а затем вставьте или обновите в зависимости от результата SELECT (это то, что вы сделали). Это работает, но не гарантируется на 100%, так как вы можете иметь условие состязания между двумя параллельными потоками (они могут не найти запись для данного categoryName и попытаться вставить параллельно; самый медленный поток потерпит неудачу). Если это маловероятно, это может быть приемлемым решением.

Обновление: Возможно, существует третий вариант бонуса, если вы не возражаете против использования проприетарной функции MySQL, см. 12.2.5.3. INSERT ... ON DUPLICATE KEY UPDATE Синтаксис . Никогда не тестировался с JPA.

1 голос
/ 15 ноября 2015

Я не видел ни одного упомянутого выше, поэтому я просто хотел бы добавить возможное решение, которое позволит избежать нескольких запросов. Versioning .

Обычно используется как простой способ проверить, не устарела ли обновляемая запись в сценарии оптимистическая блокировка , столбцы, помеченные @ Version , также можно использовать для проверки возможности записи является постоянным (присутствует в БД) или нет.

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

CREATE TABLE example
(
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  version INT,   -- <== It really is that simple!
  value VARCHAR(255)
);

И пометьте соответствующее поле в нашем классе Java с помощью @Version следующим образом:

@Entity
public class Example {
    @Id 
    @GeneratedValue
    private Integer id;

    @Version  // <-- that's the trick!
    private Integer version;

    @Column(length=255)
    private String value;
}

Аннотация @Version заставит JPA использовать этот столбец с оптимистической блокировкой, включив его в качестве условия в любые операторы обновления, например:

UPDATE example 
SET value = 'Hello, World!' 
WHERE id = 23
AND version = 2  -- <-- if version has changed, update won't happen

(JPA делает это автоматически, не нужно писать самостоятельно)

Затем он проверяет, была ли обновлена ​​одна запись (как ожидалось) или нет (в этом случае объект устарел).

Мы должны убедиться, что никто не может установить поле версии, иначе это может испортить оптимистическую блокировку, но мы можем сделать геттер на version, если захотим. Мы также можем использовать поле версии в методе isPersistent, который проверит, находится ли запись в БД уже или нет, не выполняя запрос:

@Entity
public class Example {
    // ...
    /** Indicates whether this entity is present in the database. */
    public boolean isPersistent() {
        return version != null;
    }
}

Наконец, мы можем использовать этот метод в нашем insertOrUpdate методе:

public insertOrUpdate(Example example) {
    if (example.isPersistent()) {
        // record is already present in the db
        // update it here
    }
    else {
        // record is not present in the db
        // insert it here
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...