Как мне обновить сущность, используя spring-data-jpa?

196

Ну, вопрос в значительной степени говорит обо всем. Используя JPARepository, как мне обновить сущность?

В JPARepository есть только метод сохранения , который не сообщает мне, действительно ли он создается или обновляется. Например, вставить простой объект в базе данных пользователя, который имеет три поля: firstname, lastnameа age:

 @Entity
 public class User {

  private String firstname;
  private String lastname;
  //Setters and getters for age omitted, but they are the same as with firstname and lastname.
  private int age;

  @Column
  public String getFirstname() {
    return firstname;
  }
  public void setFirstname(String firstname) {
    this.firstname = firstname;
  }

  @Column
  public String getLastname() {
    return lastname;
  }
  public void setLastname(String lastname) {
    this.lastname = lastname;
  }

  private long userId;

  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  public long getUserId(){
    return this.userId;
  }

  public void setUserId(long userId){
    this.userId = userId;
  }
}

Затем я просто вызываю save(), который на самом деле является вставкой в ​​базу данных:

 User user1 = new User();
 user1.setFirstname("john"); user1.setLastname("dew");
 user1.setAge(16);

 userService.saveUser(user1);// This call is actually using the JPARepository: userRepository.save(user);

Все идет нормально. Теперь я хочу обновить этого пользователя, скажем, изменить его возраст. Для этой цели я мог бы использовать QueryDSL или NamedQuery. Но, учитывая, что я просто хочу использовать spring-data-jpa и JPARepository, как мне сказать, что вместо вставки я хочу сделать обновление?

В частности, как мне сказать spring-data-jpa, что пользователи с одинаковыми именем пользователя и именем на самом деле равны и что существующий объект должен быть обновлен? Переопределение равных не решило эту проблему.

Евгений
источник
1
Вы уверены, что идентификатор переписывается при сохранении существующего объекта в базе данных? Никогда такого не было в моем проекте
Байрон Вурбах
@ByronVoorbach Да, ты прав, только что проверил это. обновить вопрос также, спасибо
Евгений
2
Здравствуйте, друг, вы можете посмотреть эту ссылку stackoverflow.com/questions/24420572/… вы можете использовать такой подход, как saveOrUpdate ()
ibrahimKiraz
stackoverflow.com/questions/39741102/…
Роберт Нестрой
Я думаю, что у нас есть красивое решение здесь: введите описание ссылки здесь
Clebio Vieira

Ответы:

208

Идентичность сущностей определяется их первичными ключами. Поскольку firstnameи lastnameне являются частями первичного ключа, вы не можете указать JPA рассматривать Users с одинаковыми firstnames и lastnames как равные, если они имеют разные userIds.

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

РЕДАКТИРОВАТЬ:

Возможно, мне следует остановиться на общей семантике JPA. Существует два основных подхода к разработке постоянных API:

  • вставить / обновить подход . Когда вам нужно изменить базу данных, вы должны явно вызывать методы API персистентности: вы вызываете insertдля вставки объекта или updateдля сохранения нового состояния объекта в базе данных.

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

JPA следует последнему подходу. save()в Spring Data JPA поддерживается merge()простым JPA, поэтому он управляет вашей сущностью, как описано выше. Это означает, что вызов save()объекта с предопределенным идентификатором обновит соответствующую запись базы данных, а не вставит новую, а также объясняет, почему save()не вызывается create().

axtavt
источник
ну да, я думаю, что знаю это. Я строго имел в виду spring-data-jpa. У меня сейчас две проблемы с этим ответом: 1) бизнес-ценности не должны быть частью первичного ключа - это известная вещь, верно? Поэтому иметь имя и фамилию в качестве первичного ключа нехорошо. И 2) Почему тогда этот метод не называется create, а вместо этого сохраняется в spring-data-jpa?
Евгений
1
«save () в Spring Data JPA опирается на merge () в простом JPA» Вы действительно смотрели код? Я только что сделал, и они оба подкреплены либо сохранением, либо слиянием. Он будет сохраняться или обновляться в зависимости от наличия идентификатора (первичный ключ). Это, я думаю, должно быть задокументировано в методе сохранения. Так что сохранить на самом деле либо слить или сохранить.
Евгений
Я думаю, что это также называется сохранить, потому что он должен сохранить объект, независимо от того, в каком он состоянии - он собирается выполнить обновление или вставку, равную состоянию сохранения.
Евгений
1
Это не будет работать для меня. Я попытался сохранить на объекте с действительным первичным ключом. Я захожу на страницу с помощью «order / edit /: id», и это фактически дает мне правильный объект по Id. Ничто из того, что я пытаюсь ради любви Бога, не обновит сущность. Он всегда публикует новую сущность. Я даже пытался создать собственный сервис и использовать «слияние» с моим EntityManager, и он все равно не будет работать. Он всегда будет публиковать новую сущность.
DtechNet
1
@DTechNet У меня была похожая проблема с вами, DtechNet, и оказалось, что моя проблема заключалась в том, что у меня был неправильный тип первичного ключа, указанный в моем интерфейсе репозитория Spring Data. Сказано, extends CrudRepository<MyEntity, Integer>а не так, extends CrudRepository<MyEntity, String>как должно быть. Это помогает? Я знаю, это почти год спустя. Надеюсь, это поможет кому-то еще.
Кент Булл
141

Так как ответ на @axtavt фокусируется на JPAнеspring-data-jpa

Обновить сущность путем запроса, тогда сохранение неэффективно, поскольку требует двух запросов, и, возможно, запрос может быть довольно дорогим, поскольку он может объединять другие таблицы и загружать любые коллекции, которые имеют fetchType=FetchType.EAGER

Spring-data-jpaподдерживает операцию обновления.
Вы должны определить метод в интерфейсе репозитория и добавить к нему пометки @Queryи @Modifying.

@Modifying
@Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);

@Queryпредназначен для определения пользовательского запроса и @Modifyingдля сообщения о spring-data-jpaтом, что этот запрос является операцией обновления, а для этого executeUpdate()не требуется executeQuery().

Вы можете указать другие типы возврата:
int- количество обновляемых записей.
boolean- true, если запись обновляется. В противном случае, ложь.


Примечание . Запустите этот код в транзакции .

hussachai
источник
10
Убедитесь, что вы запустили его в транзакции
hussachai
1
Привет! Спасибо, я использую бобы данных весны. Так что он автоматически позаботится о моем обновлении. <S расширяет T> S сохранить (S-сущность); автоматически заботится об обновлении. Мне не нужно использовать ваш метод! В любом случае спасибо!
bks4line
3
В любое время :) Метод save работает, если вы хотите сохранить объект (он делегирует вызов em.persist () или em.merge () за сценой). В любом случае, пользовательский запрос полезен, когда вы хотите обновить только некоторые поля в базе данных.
Hussachai
как насчет того, когда один из ваших параметров является идентификатором дочерней сущности (manyToOne), как это обновить? (у книги есть Автор, и вы передали идентификатор книги и идентификатор автора, чтобы обновить автора книги)
Махди
1
To update an entity by querying then saving is not efficientэто не единственные два варианта. Есть способ указать id и получить объект строки, не запрашивая его. Если вы сделаете a, row = repo.getOne(id)а затем row.attr = 42; repo.save(row);и посмотрите логи, вы увидите только запрос на обновление.
Нуреттин
21

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

public void updateUser(Userinfos u) {
    User userFromDb = userRepository.findById(u.getid());
    // crush the variables of the object found
    userFromDb.setFirstname("john"); 
    userFromDb.setLastname("dew");
    userFromDb.setAge(16);
    userRepository.save(userFromDb);
}
Kalifornium
источник
4
Производительность не является проблемой, если вам нужно загрузить объект из базы данных перед обновлением? (извините за мой английский)
август 0490
3
есть два запроса вместо одного, что крайне нежелательно
Тигран Бабаджанян
я знаю, я просто пришел, чтобы сделать еще один метод! но почему jpa реализовал функцию обновления, когда идентификатор тот же?
калифорний
17

Как уже упоминалось другими, save()сам содержит операции создания и обновления.

Я просто хочу добавить дополнение о том, что стоит за save()методом.

Во-первых, давайте посмотрим иерархию расширения / реализации CrudRepository<T,ID>, введите описание изображения здесь

Хорошо, давайте проверим save()выполнение на SimpleJpaRepository<T, ID>,

@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

Как вы можете видеть, он сначала проверит, существует ли идентификатор или нет, если сущность уже существует, будет происходить только обновление по merge(entity)методу, а если еще, новая запись вставляется по persist(entity)методу.

Евгений
источник
7

Используя spring-data-jpa save(), у меня возникла та же проблема, что и у @DtechNet. Я имею в виду, что каждый save()создавал новый объект вместо обновления. Чтобы решить эту проблему, я должен был добавить versionполе к сущности и связанную таблицу.

улыбка
источник
что вы подразумеваете под добавлением поля версии к сущности и связанной таблице.
Jcrshankar
5

Вот как я решил проблему:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Адам
источник
Используйте @Transactionвышеуказанный метод для запроса нескольких дБ. В этом случае нет необходимости userRepository.save(inbound);, изменения сбрасываются автоматически.
Григорий Кислин
5

save()Метод весенних данных поможет вам выполнить и добавление нового элемента, и обновление существующего элемента.

Просто позвони save()и наслаждайся жизнью :))

Амир Мхп
источник
Таким образом, если я отправил по-другому Id, сохраню его, как я могу избежать сохранения новой записи.
Абд Абугхазалех
1
@AbdAbughazaleh проверьте, существует ли входящий Id в вашем хранилище или нет. Вы можете использовать 'repository.findById (id) .map (entity -> {// сделать что-нибудь, вернуть repository.save (entity)}) .orElseGet (() -> {// сделать что-то, вернуть;}); '
Amir Mhp
1
public void updateLaserDataByHumanId(String replacement, String humanId) {
    List<LaserData> laserDataByHumanId = laserDataRepository.findByHumanId(humanId);
    laserDataByHumanId.stream()
            .map(en -> en.setHumanId(replacement))
            .collect(Collectors.toList())
            .forEach(en -> laserDataRepository.save(en));
}
BinLee
источник
@JoshuaTaylor действительно, пропустил это полностью :) удаляю комментарий ...
Евгений
1

В частности, как мне сказать spring-data-jpa, что пользователи, которые имеют одинаковые имя пользователя и имя, на самом деле равны и что он должен обновить сущность. Переопределение равных не сработало.

Для этой конкретной цели можно ввести составной ключ, например:

CREATE TABLE IF NOT EXISTS `test`.`user` (
  `username` VARCHAR(45) NOT NULL,
  `firstname` VARCHAR(45) NOT NULL,
  `description` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`username`, `firstname`))

Отображение:

@Embeddable
public class UserKey implements Serializable {
    protected String username;
    protected String firstname;

    public UserKey() {}

    public UserKey(String username, String firstname) {
        this.username = username;
        this.firstname = firstname;
    }
    // equals, hashCode
}

Вот как это использовать:

@Entity
public class UserEntity implements Serializable {
    @EmbeddedId
    private UserKey primaryKey;

    private String description;

    //...
}

JpaRepository будет выглядеть так:

public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>

Затем вы можете использовать следующую идиому: принять DTO с информацией о пользователе, извлечь имя и имя и создать UserKey, затем создать UserEntity с этим составным ключом и затем вызвать Spring Data save (), который должен все уладить за вас.

Андреас Гелевер
источник