Как обновить объект с помощью 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(), который должен все уладить за вас.