Как обновить объект с помощью spring -data-jpa?

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

JPARepository имеет только метод save, который не сообщает мне, действительно ли он создается или обновляется. Например, я вставляю простой объект в базу данных User, который имеет три поля: 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

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

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

EDIT:

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

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

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

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

Ответ 2

Так как ответ @axtavt фокусируется на JPA not 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() not executeQuery().

Ответ 3

Вы можете просто использовать эту функцию с 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);
}

Ответ 4

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

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

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

Хорошо, давайте проверим реализацию 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);
    }
}

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

Ответ 5

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

Ответ 6

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

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);

Ответ 7

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

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

Ответ 8

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));
}

Ответ 9

В частности, как мне сказать 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(), который должен все уладить за вас.