Як оновити сутність за допомогою 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, що користувачі з тим самим іменем та іменем насправді є EQUAL і що існуюча сутність повинна бути оновлена? Перевищення рівних не вирішило цю проблему.


1
Ви впевнені, що ідентифікатор перезаписується, коли ви зберігаєте існуючий об’єкт у базі даних ?? Ніколи цього не було в моєму проекті
tbh

@ByronVoorbach, ти правий, просто перевірив це. оновіть питання також, thx
Євген

2
Привіт друже, ти можеш переглянути це посилання stackoverflow.com/questions/24420572/… ти можеш бути таким підходом, як saveOrUpdate ()
ibrahimKiraz


Я думаю, що у нас є прекрасне рішення тут: введіть опис посилання тут
Клебіо Віейра

Відповіді:


208

Ідентифікація об'єктів визначається їх первинними ключами. Оскільки firstnameі lastnameне є частинами первинного ключа, ви не можете сказати JPA, щоб вони розглядали Users з однаковими firstnames та lastnames як рівними, якщо вони мають різні userIds.

Отже, якщо ви хочете оновити Userідентифікований за ним firstnameі lastname, вам потрібно знайти це Userза допомогою запиту, а потім змінити відповідні поля знайденого об'єкта. Ці зміни автоматично будуть передані в базу даних в кінці транзакції, так що вам не потрібно робити нічого, щоб явно зберегти ці зміни.

Редагувати:

Можливо, я повинен детальніше зупинитися на загальній семантиці JPA. Є два основні підходи до проектування стійких API:

  • підхід вставити / оновити . Коли вам потрібно змінити базу даних, вам слід чітко викликати методи стійкості API: ви закликаєте insertвставити об'єкт або updateзберегти новий стан об'єкта в базі даних.

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

JPA дотримується останнього підходу. save()У весняних даних JPA підтримується merge()простим JPA, тому це дозволяє вашій організації керуватися, як описано вище. Це означає, що виклик save()об’єкта з попередньо визначеним ідентифікатором оновить відповідний запис бази даних, а не вставить новий, а також пояснить, чому save()його не викликають create().


ну так, я думаю, що я це знаю. Я строго мав на увазі spring-data-jpa. Зараз у мене є дві проблеми з цією відповіддю: 1) значення бізнесу не повинні бути частиною первинного ключа - це відома річ, правда? Тож мати прізвище та прізвище в якості основного ключа - це не добре. І 2) Чому цей метод тоді не називається create, а зберігається натомість у spring-data-jpa?
Євген

1
"save () у Spring Data JPA підтримується merge () у звичайному JPA", чи справді ви подивилися на код? Я щойно це зробив, і це обидва підкріплені або збереженням, або злиттям. Він зберігатиметься або оновлюватиметься на основі наявності id (первинний ключ). Я думаю, це має бути задокументовано методом збереження. Тож зберегти насправді ВСЕ ВІДКЛЮЧЕННЯ або збереження.
Євген

Я думаю, що його ще називають зберегти, тому що він повинен зберегти об'єкт незалежно від того, в якому стані він знаходиться - він буде виконувати оновлення або вставлення, що дорівнює стану збереження.
Євген

1
Це не допоможе мені. Я спробував зберегти на об'єкті дійсний первинний ключ. Я переходжу до сторінки з "замовлення / редагування /: 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- вірно, якщо є запис, який оновлюється. В іншому випадку - неправдиво.


Примітка : запустіть цей код в транзакції .


10
Переконайтеся, що ви запускаєте його в операції
hussachai

1
Гей! Дякую, я використовую джерельні дані. Тож воно автоматично піклується про моє оновлення. <S розширює T> S зберегти (S сутність); автоматично піклується про оновлення. я не повинен був використовувати ваш метод! Спасибі все одно!
bks4line

3
У будь-який час :) Метод збереження працює, якщо ви хочете зберегти об'єкт (він передасть виклик або 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 () JPA, але об'єкт, надісланий як параметр, повинен містити наявний ідентифікатор у базі даних, інакше він не буде працювати, оскільки зберегти (), коли ми надсилаємо об'єкт без ідентифікатора, він додає безпосередньо рядок у базі даних, але якщо ми надсилаємо об'єкт із наявним ідентифікатором, він змінює стовпці, вже знайдені в базі даних.

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
чи не проблема з роботою, якщо вам доведеться завантажити об’єкт із бази даних перед оновленням? (вибачте за мою англійську)
серпень0490,

3
є два запити замість одного, який дуже не надається переваги
Tigran Babajanyan

я знаю, що я показав ще один метод! але чому jpa реалізував функцію оновлення, коли id збігається?
Каліфорнія

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вищевказаний метод для декількох db запитів. У цьому випадку немає необхідності userRepository.save(inbound);, зміни вимикаються автоматично.
Григорій Кіслін

5

весняний save()метод даних допоможе вам виконати і те, і інше: додавання нового елемента та оновлення існуючого елемента.

Просто зателефонуйте save()та насолоджуйтесь життям :))


Таким чином, якщо я надсилаю різні Id, збережу його, як я можу уникнути збереження нового запису.
Абд Абугазале

1
@AbdAbughazaleh перевірте, чи існує вхідний ідентифікатор у вашому сховищі чи ні. ви можете використовувати 'repository.findById (id) .map (сутність -> {// робити щось повернення repository.save (сутність)}) .orElseGet (() -> {// робити щось повернення;}); '
Амір 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));
}

@JoshuaTaylor дійсно, пропустив це повністю :) видалить коментар ...
Євген

1

Зокрема, як я можу сказати spring-data-jpa, що користувачі, які мають однакове ім'я користувача та ім’я, насправді EQUAL і що він повинен оновлювати об'єкт. Перевищення рівнянь не спрацювало.

Для цієї конкретної мети можна ввести такий складений ключ:

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 (), який повинен розібрати все для вас.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.