Jak zaktualizować encję za pomocą Spring-Data-JPA?

194

Cóż, pytanie w zasadzie mówi wszystko. Korzystanie z JPARepository jak zaktualizować encję?

JPARepository ma tylko metodę zapisywania , która nie mówi mi, czy faktycznie jest tworzona czy aktualizowana. Na przykład wstawić prosty obiekt do użytkownika bazy danych, który ma trzy pola: firstname, lastnamei 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;
  }
}

Następnie po prostu wywołuję save(), która w tym momencie jest właściwie wstawką do bazy danych:

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

Na razie w porządku. Teraz chcę zaktualizować tego użytkownika, powiedzmy zmień jego wiek. W tym celu mógłbym użyć Query, QueryDSL lub NamedQuery, cokolwiek. Ale biorąc pod uwagę, że chcę po prostu użyć spring-data-jpa i JPARepository, jak mam to powiedzieć, że zamiast wstawki chcę wykonać aktualizację?

W szczególności, jak mam powiedzieć spring-data-jpa, że ​​użytkownicy z tą samą nazwą użytkownika i imieniem są w rzeczywistości RÓWNI i że istniejący byt powinien zostać zaktualizowany? Zastąpienie równości nie rozwiązało tego problemu.

Eugene
źródło
1
Czy na pewno identyfikator zostanie przepisany po zapisaniu istniejącego obiektu w bazie danych? Nigdy nie miałem tego w moim projekcie tbh
Byron Voorbach,
@ByronVoorbach, masz rację, właśnie to przetestowałem. zaktualizuj pytanie również, dzięki
Eugene
2
Witaj przyjacielu, możesz przejrzeć ten link stackoverflow.com/questions/24420572/ ... możesz być podejściem takim jak saveOrUpdate ()
ibrahimKiraz
Myślę, że mamy tutaj piękne rozwiązanie: wpisz opis linku tutaj
Clebio Vieira,

Odpowiedzi:

207

Tożsamość jednostek jest definiowana przez ich klucze podstawowe. Ponieważ firstnamei lastnamenie są częściami klucza podstawowego, nie można nakazać JPA, aby traktowały Userte same firstnamesi lastnamesi równe, jeśli mają różne userIds.

Tak więc, jeśli chcesz zaktualizować Userzidentyfikowany przez niego firstnamei lastname, musisz znaleźć to Userprzez zapytanie, a następnie zmienić odpowiednie pola znalezionego obiektu. Zmiany te zostaną automatycznie wprowadzone do bazy danych pod koniec transakcji, dzięki czemu nie trzeba nic robić, aby jawnie zapisać te zmiany.

EDYTOWAĆ:

Być może powinienem rozwinąć ogólną semantykę WZP. Istnieją dwa główne podejścia do projektowania interfejsów API trwałości:

  • podejście do wstawiania / aktualizacji . Gdy musisz zmodyfikować bazę danych, powinieneś jawnie wywołać metody API trwałości: wywołujesz, insertaby wstawić obiekt lub updatezapisać nowy stan obiektu w bazie danych.

  • Podejście jednostki pracy . W tym przypadku masz zestaw obiektów zarządzanych przez bibliotekę trwałości. Wszystkie zmiany, które wprowadzisz w tych obiektach, zostaną automatycznie usunięte do bazy danych na końcu Jednostki Pracy (tj. Na koniec bieżącej transakcji w typowym przypadku). Kiedy trzeba wstawić nowy rekord do bazy danych, zarządza się odpowiednim obiektem . Zarządzane obiekty są identyfikowane przez ich klucze podstawowe, więc jeśli utworzysz obiekt z zarządzanym predefiniowanym kluczem podstawowym , zostanie on powiązany z rekordem bazy danych o tym samym identyfikatorze, a stan tego obiektu zostanie automatycznie propagowany do tego rekordu.

WZP stosuje to drugie podejście. save()w Spring Data JPA jest zabezpieczony merge()zwykłym JPA, dlatego zarządza twoją jednostką w sposób opisany powyżej. Oznacza to, że wywołanie save()obiektu o zdefiniowanym identyfikatorze spowoduje zaktualizowanie odpowiedniego rekordu bazy danych zamiast wstawienia nowego, a także wyjaśni, dlaczego save()nie jest wywoływany create().

axtavt
źródło
no tak, myślę, że to wiem. Miałem na myśli wyłącznie wiosenne dane-jpa. Mam teraz dwa problemy z tą odpowiedzią: 1) wartości biznesowe nie powinny być częścią klucza podstawowego - to znana sprawa, prawda? Zatem posiadanie imienia i nazwiska jako klucza podstawowego nie jest dobre. I 2) Dlaczego ta metoda nie jest następnie nazywana create, ale zamiast tego zapisuje się w spring-data-jpa?
Eugene
1
„save () w Spring Data JPA jest wspierane przez merge () w zwykłym JPA” czy rzeczywiście spojrzałeś na kod? Właśnie to zrobiłem i oba zostały poparte albo wytrwałością, albo scaleniem. Będzie się utrzymywać lub aktualizować w oparciu o obecność identyfikatora (klucz podstawowy). Myślę, że powinno to zostać udokumentowane metodą zapisywania. Tak więc save jest faktycznie ŁĄCZENIEM lub utrzymywaniem.
Eugene
Myślę, że jest również nazywany zapisz, ponieważ powinien zapisać obiekt bez względu na to, w jakim jest stanie - wykona aktualizację lub wstawkę, która jest równa zapisaniu.
Eugene
1
To mi nie zadziała. Próbowałem zapisać na obiekcie z ważnym kluczem podstawowym. Uzyskuję dostęp do strony za pomocą „order / edit /: id” i faktycznie daje mi właściwy obiekt według Id. Nic, czego staram się na miłość boską, nie zaktualizuje bytu. Zawsze publikuje nowy obiekt. Próbowałem nawet utworzyć niestandardową usługę i użyć „scalenia” z moim EntityManager i nadal nie będzie działać. Zawsze będzie publikować nowy podmiot.
DtechNet,
1
@DTechNet Miałem podobny problem jak DtechNet i okazało się, że mój problem polegał na tym, że w interfejsie repozytorium Spring Data podano nieprawidłowy typ klucza podstawowego. Powiedział extends CrudRepository<MyEntity, Integer>zamiast tego, extends CrudRepository<MyEntity, String>jak powinien. To pomaga? Wiem, że to prawie rok później. Mam nadzieję, że pomoże to komuś innemu.
Kent Bull
140

Ponieważ odpowiedź @axtavt koncentruje się na JPAniespring-data-jpa

Aby zaktualizować jednostkę za pomocą zapytania, zapisywanie nie jest wydajne, ponieważ wymaga dwóch zapytań i być może zapytanie może być dość drogie, ponieważ może łączyć się z innymi tabelami i ładować dowolne kolekcje, które mają fetchType=FetchType.EAGER

Spring-data-jpaobsługuje operację aktualizacji.
Musisz zdefiniować metodę w interfejsie repozytorium. I opatrzyć ją adnotacjami za pomocą @Queryi @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);

@Queryjest do zdefiniowania niestandardowego zapytania i @Modifyingjest za mówienie spring-data-jpa, że ta kwerenda jest operacja aktualizacji i wymaga executeUpdate()nie executeQuery().

Możesz określić inne typy zwrotów:
int- liczbę aktualizowanych rekordów.
boolean- prawda, jeśli aktualizowany jest rekord. W przeciwnym razie fałszywe.


Uwaga : uruchom ten kod w transakcji .

hussachai
źródło
10
Upewnij się, że uruchamiasz go w transakcji
hussachai,
1
Hej! Dzięki używam wiosennych komponentów bean danych. Więc automatycznie zajmie się moją aktualizacją. <S rozszerza T> S save (jednostka S); automatycznie zajmuje się aktualizacją. nie musiałem używać twojej metody! W każdym razie dzięki!
bks4line,
3
Anytime :) Metoda save działa, jeśli chcesz zapisać encję (deleguje wywołanie do em.persist () lub em.merge () za sceną). W każdym razie niestandardowe zapytanie jest przydatne, gdy chcesz zaktualizować tylko niektóre pola w bazie danych.
hussachai
co powiesz na to, kiedy jeden z Twoich parametrów jest identyfikatorem podelementu (manyToOne)? (książka ma autora i przekazałeś identyfikator książki i identyfikator autora, aby zaktualizować autora książki)
Mahdi
1
To update an entity by querying then saving is not efficientto nie jedyne dwie możliwości. Istnieje sposób na określenie id i uzyskanie obiektu wiersza bez zapytania go. Jeśli wykonasz a, row = repo.getOne(id)a następnie row.attr = 42; repo.save(row);obejrzysz dzienniki, zobaczysz tylko zapytanie o aktualizację.
nurettin
21

Możesz po prostu użyć tej funkcji z funkcją JPA save (), ale obiekt wysłany jako parametr musi zawierać istniejący identyfikator w bazie danych, inaczej nie zadziała, ponieważ save (), gdy wysyłamy obiekt bez identyfikatora, dodaje bezpośrednio wiersz w baza danych, ale jeśli wyślemy obiekt z istniejącym identyfikatorem, zmieni to kolumny już znalezione w bazie danych.

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
źródło
4
czy wydajność nie stanowi problemu, jeśli przed aktualizacją trzeba załadować obiekt z bazy danych? (przepraszam za mój angielski)
sierpień 0490
3
są dwa zapytania zamiast jednego, które nie są
zalecane
wiem, że właśnie pokazałem inną metodę! ale dlaczego jpa zaimplementowało funkcję aktualizacji, skoro identyfikator jest taki sam?
Kalifornium
17

Jak już wspomnieli inni, save()sam zawiera operacje tworzenia i aktualizacji.

Chcę tylko dodać suplement dotyczący tego, co kryje się za tą save()metodą.

Po pierwsze, zobaczmy hierarchię rozszerzania / implementowania CrudRepository<T,ID>, wprowadź opis zdjęcia tutaj

Ok, niech sprawdzić save()wdrażania na 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);
    }
}

Jak widać, sprawdzi najpierw, czy identyfikator istnieje, czy nie, jeśli jednostka już tam jest, tylko aktualizacja zostanie wykonana merge(entity)metodą, a jeśli nie, nowy rekord zostanie wstawiony persist(entity)metodą.

Eugene
źródło
7

Korzystając z spring-data-jpa save(), miałem taki sam problem jak @DtechNet. Mam na myśli, że każdy save()tworzył nowy obiekt zamiast aktualizacji. Aby rozwiązać ten problem, musiałem dodać versionpole do encji i powiązanej tabeli.

uśmiech
źródło
co rozumiesz przez dodanie pola wersji do encji i powiązanej tabeli.
jcrshankar
5

Oto jak rozwiązałem problem:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Adam
źródło
Użyj @Transactionpowyższej metody dla kilku żądań db. W tym przypadku nie ma potrzeby userRepository.save(inbound);, zmiany są automatycznie usuwane.
Grigory Kislin
5

save()metoda danych wiosennych pomoże Ci wykonać zarówno: dodanie nowego elementu, jak i aktualizację istniejącego elementu.

Wystarczy zadzwonić save()i cieszyć się życiem :))

Amir Mhp
źródło
W ten sposób, jeśli wysłałem inny Id, zapisze go, jak mogę uniknąć zapisania nowego rekordu.
Abd Abughazaleh
1
@AbdAbughazaleh sprawdź, czy identyfikator przychodzący istnieje w twoim repozytorium, czy nie. możesz użyć 'repository.findById (id) .map (encja -> {// zrób coś zwróć repository.save (encja)}) .orElseGet (() -> {// zrób coś zwrócić;}); „
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
źródło
@JoshuaTaylor rzeczywiście, przegapiłem to całkowicie :) usunie komentarz ...
Eugene
1

W szczególności, jak mam powiedzieć spring-data-jpa, że ​​użytkownicy, którzy mają tę samą nazwę użytkownika i imię, są w rzeczywistości RÓWNI i że ma to aktualizować byt. Zastąpienie równości nie działało.

W tym konkretnym celu można wprowadzić klucz złożony taki jak ten:

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`))

Mapowanie:

@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
}

Oto jak z niego korzystać:

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

    private String description;

    //...
}

JpaRepository wyglądałby tak:

public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>

Następnie możesz użyć następującego idiomu: zaakceptuj DTO z informacjami o użytkowniku, wyodrębnij nazwę i imię i utwórz UserKey, następnie utwórz UserEntity za pomocą tego klucza złożonego, a następnie wywołaj Spring Data save (), który powinien wszystko dla ciebie rozwiązać.

Andreas Gelever
źródło