Bagaimana cara memperbarui entitas menggunakan spring-data-jpa?

194

Yah pertanyaannya cukup banyak mengatakan segalanya. Menggunakan JPARepository, bagaimana cara saya memperbarui suatu entitas?

JPARepository hanya memiliki metode penyimpanan , yang tidak memberi tahu saya apakah itu benar-benar membuat atau memperbarui. Sebagai contoh, saya memasukkan Obyek sederhana untuk Pengguna database, yang memiliki tiga bidang: firstname, lastnamedan 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;
  }
}

Maka saya cukup menelepon save(), yang pada saat ini sebenarnya adalah memasukkan ke dalam basis data:

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

Sejauh ini baik. Sekarang saya ingin memperbarui pengguna ini, katakan ubah usianya. Untuk tujuan ini saya bisa menggunakan Query, baik QueryDSL atau NamedQuery, apa pun. Tetapi, mengingat saya hanya ingin menggunakan pegas-data-jpa dan JPARepository, bagaimana saya mengatakannya bahwa alih-alih memasukkan saya ingin melakukan pembaruan?

Secara khusus, bagaimana cara memberi tahu pegas-data-jpa bahwa pengguna dengan nama pengguna dan nama depan yang sama sebenarnya EQUAL dan bahwa entitas yang ada seharusnya diperbarui? Override sama dengan tidak menyelesaikan masalah ini.

Eugene
sumber
1
Apakah Anda yakin ID akan ditulis ulang ketika Anda menyimpan objek yang ada di database ?? Tidak pernah memiliki ini di proyek saya tbh
Byron Voorbach
@ByronVoorbach ya Anda benar, baru saja menguji ini. perbarui pertanyaannya juga, thx
Eugene
2
Halo teman, Anda dapat melihat tautan ini stackoverflow.com/questions/24420572/... Anda bisa menjadi pendekatan seperti saveOrUpdate ()
ibrahimKiraz
Saya pikir kami punya solusi yang bagus di sini: masukkan uraian tautan di sini
Clebio Vieira

Jawaban:

207

Identitas entitas didefinisikan oleh kunci utama mereka. Karena firstnamedan lastnamebukan bagian dari kunci utama, Anda tidak dapat memberi tahu JPA untuk memperlakukan Users dengan firstnames dan lastnames yang sama jika mereka memiliki userIds yang berbeda .

Jadi, jika Anda ingin memperbarui yang Userdiidentifikasi oleh firstnamedan lastname, Anda perlu menemukannya Userdengan kueri, lalu ubah bidang yang sesuai dari objek yang Anda temukan. Perubahan-perubahan ini akan dihapus ke database secara otomatis pada akhir transaksi, sehingga Anda tidak perlu melakukan apa pun untuk menyimpan perubahan ini secara eksplisit.

EDIT:

Mungkin saya harus menguraikan semantik keseluruhan JPA. Ada dua pendekatan utama untuk merancang API persistensi:

  • masukkan / perbarui pendekatan . Ketika Anda perlu memodifikasi database Anda harus memanggil metode API kegigihan secara eksplisit: Anda menelepon insertuntuk menyisipkan objek, atau updateuntuk menyimpan keadaan objek yang baru ke database.

  • Pendekatan Unit Kerja . Dalam hal ini Anda memiliki satu set objek yang dikelola oleh perpustakaan kegigihan. Semua perubahan yang Anda lakukan pada objek-objek ini akan disiram ke database secara otomatis di akhir Unit Kerja (yaitu pada akhir transaksi saat ini dalam kasus khusus). Saat Anda perlu menyisipkan catatan baru ke database, Anda membuat objek terkait dikelola . Objek yang dikelola diidentifikasi oleh kunci utama mereka, sehingga jika Anda membuat objek dengan kunci utama yang telah ditentukan sebelumnya dikelola , itu akan dikaitkan dengan catatan basis data dari id yang sama, dan keadaan objek ini akan disebarkan ke catatan itu secara otomatis.

JPA mengikuti pendekatan yang terakhir. save()di Spring Data JPA didukung oleh merge()dalam JPA biasa, karena itu membuat entitas Anda dikelola seperti dijelaskan di atas. Ini berarti bahwa memanggil save()objek dengan id yang telah ditentukan akan memperbarui catatan database yang sesuai daripada memasukkan yang baru, dan juga menjelaskan mengapa save()tidak dipanggil create().

axtavt
sumber
baik ya saya pikir saya tahu ini. Saya benar-benar merujuk pada pegas-data-jpa. Saya memiliki dua masalah dengan jawaban ini sekarang: 1) nilai bisnis tidak seharusnya menjadi bagian dari kunci utama - itu adalah hal yang diketahui, bukan? Jadi memiliki nama depan dan nama belakang sebagai kunci utama tidak baik. Dan 2) Mengapa metode ini tidak disebut create, tetapi simpan sebagai gantinya di spring-data-jpa?
Eugene
1
"save () di Spring Data JPA didukung oleh gabungan () di JPA biasa" apakah Anda benar-benar melihat kode? Saya baru saja melakukannya dan keduanya didukung oleh baik bertahan atau bergabung. Ini akan tetap ada atau diperbarui berdasarkan keberadaan id (kunci utama). Ini, saya pikir, harus didokumentasikan dalam metode simpan. Jadi save sebenarnya BAIK bergabung atau bertahan.
Eugene
Saya pikir itu juga disebut save, karena itu seharusnya menyimpan objek tidak peduli apa pun keadaannya - ia akan melakukan pembaruan atau menyisipkan yang sama dengan menyimpan keadaan.
Eugene
1
Ini tidak akan bekerja untuk saya. Saya telah mencoba menyimpan pada suatu entitas dengan kunci primer yang valid. Saya mengakses halaman dengan "order / edit /: id" dan itu benar-benar memberi saya objek yang benar oleh Id. Tidak ada yang saya coba karena cinta Tuhan akan memperbarui entitas. Itu selalu memposting entitas baru .. Saya bahkan mencoba membuat layanan kustom dan menggunakan "menggabungkan" dengan EntityManager saya dan itu masih tidak akan berfungsi. Itu akan selalu memposting entitas baru.
DtechNet
1
@DTechNet Saya mengalami masalah yang serupa dengan Anda, DtechNet, dan ternyata masalah saya adalah bahwa saya memiliki tipe kunci primer yang salah yang ditentukan dalam antarmuka repositori Spring Data saya. Dikatakan extends CrudRepository<MyEntity, Integer>bukannya extends CrudRepository<MyEntity, String>seperti seharusnya. Apakah itu membantu? Saya tahu ini hampir setahun kemudian. Semoga ini bisa membantu orang lain.
Kent Bull
139

Karena jawaban oleh @axtavt berfokus pada JPAtidakspring-data-jpa

Untuk memperbarui entitas dengan kueri, maka menyimpan tidak efisien karena memerlukan dua kueri dan mungkin kueri bisa sangat mahal karena dapat bergabung dengan tabel lain dan memuat koleksi apa pun yang memiliki fetchType=FetchType.EAGER

Spring-data-jpamendukung operasi pembaruan.
Anda harus mendefinisikan metode dalam antarmuka Repositori. Dan menjelaskannya dengan @Querydan @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);

@Queryadalah untuk mendefinisikan permintaan kustom dan @Modifyingbagi mengatakan spring-data-jpabahwa query ini adalah operasi update dan membutuhkan executeUpdate()tidak executeQuery().

Anda dapat menentukan jenis pengembalian lainnya:
int- jumlah catatan yang diperbarui.
boolean- true jika ada catatan yang diperbarui. Kalau tidak, salah.


Catatan : Jalankan kode ini dalam Transaksi .

hachachai
sumber
10
Pastikan Anda menjalankannya dalam transaksi
hussachai
1
Hei! Terima kasih saya menggunakan data kacang musim semi. Jadi secara otomatis akan mengurus pembaruan saya. <S extends T> S save (S entitas); secara otomatis menangani pembaruan. saya tidak perlu menggunakan metode Anda! Bagaimanapun, terima kasih!
bks4line
3
Kapan saja :) Metode simpan tidak berfungsi jika Anda ingin menyimpan entitas (Ini akan mendelegasikan panggilan baik ke em.persist () atau em.merge () di belakang layar). Bagaimanapun, kueri khusus berguna ketika Anda ingin memperbarui hanya beberapa bidang dalam database.
hussachai
bagaimana ketika salah satu parameter Anda adalah id dari sub entitas (manyToOne) bagaimana seharusnya memperbarui itu? (buku memiliki penulis dan Anda melewati id buku dan id penulis untuk memperbarui penulis buku)
Mahdi
1
To update an entity by querying then saving is not efficientini bukan hanya dua pilihan. Ada cara untuk menentukan id dan mendapatkan objek baris tanpa menanyakannya. Jika Anda melakukan row = repo.getOne(id)dan kemudian row.attr = 42; repo.save(row);dan menonton log, Anda hanya akan melihat permintaan pembaruan.
nurettin
21

Anda cukup menggunakan fungsi ini dengan fungsi save () JPA, tetapi objek yang dikirim sebagai parameter harus berisi id yang ada dalam database jika tidak maka tidak akan berfungsi, karena save () ketika kami mengirim objek tanpa id, ia menambahkan langsung satu baris dalam database, tetapi jika kami mengirim objek dengan id yang ada, itu mengubah kolom yang sudah ditemukan dalam database.

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
sumber
4
Bukankah kinerja masalah jika Anda harus memuat objek dari database sebelum melakukan pembaruan? (maaf untuk bahasa Inggris saya)
Agustus0490
3
ada dua permintaan, bukan satu, yang sangat tidak
disukai
saya tahu saya baru saja muncul metode lain untuk dilakukan! tetapi mengapa jpa menerapkan fungsi pembaruan ketika id sama?
Kalifornium
17

Seperti apa yang telah disebutkan oleh orang lain, save()itu sendiri berisi operasi buat dan perbarui.

Saya hanya ingin menambahkan suplemen tentang apa di balik save()metode ini.

Pertama, mari kita lihat hierarki extended / implement dari CrudRepository<T,ID>, masukkan deskripsi gambar di sini

Ok, mari kita periksa save()implementasinya di 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);
    }
}

Seperti yang Anda lihat, itu akan memeriksa apakah ID ada atau tidak pertama, jika entitas sudah ada, hanya pembaruan yang akan terjadi dengan merge(entity)metode dan jika lain, catatan baru dimasukkan dengan persist(entity)metode.

Eugene
sumber
7

Menggunakan spring-data-jpa save(), saya mengalami masalah yang sama dengan @DtechNet. Maksud saya setiap save()membuat objek baru alih-alih pembaruan. Untuk mengatasi ini saya harus menambahkan versionbidang ke entitas dan tabel terkait.

tersenyum
sumber
apa yang Anda maksud dengan menambahkan bidang versi ke entitas dan tabel terkait.
jcrshankar
5

Inilah cara saya memecahkan masalah:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Adam
sumber
Gunakan @Transactionmetode di atas untuk beberapa permintaan db. Dan dalam hal ini tidak perlu userRepository.save(inbound);, perubahan memerah secara otomatis.
Grigory Kislin
5

save()metode data pegas akan membantu Anda melakukan keduanya: menambahkan item baru dan memperbarui item yang ada.

Panggil saja save()dan nikmati hidup :))

Amir Mhp
sumber
Dengan cara ini jika saya mengirim yang berbeda Idakan menyimpannya, bagaimana saya bisa menghindari menyimpan catatan baru.
Abd Abughazaleh
1
@AbdAbughazaleh memeriksa apakah ID yang masuk ada di repositori Anda atau tidak. Anda dapat menggunakan 'repository.findById (id) .map (entitas -> {// lakukan sesuatu untuk mengembalikan repository.save (entitas)}) .orElseGet (() -> {// lakukan sesuatu kembali;}); '
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));
}
Binee
sumber
@ JoshuaTaylor memang, melewatkan seluruhnya :) akan menghapus komentar ...
Eugene
1

Khususnya bagaimana saya memberi tahu spring-data-jpa bahwa pengguna yang memiliki nama pengguna dan nama depan yang sama sebenarnya EQUAL dan bahwa itu seharusnya memperbarui entitas. Override sama dengan tidak bekerja.

Untuk tujuan khusus ini orang dapat memperkenalkan kunci komposit seperti ini:

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

Pemetaan:

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

Berikut cara menggunakannya:

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

    private String description;

    //...
}

JpaRepository akan terlihat seperti ini:

public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>

Kemudian, Anda dapat menggunakan idiom berikut : terima DTO dengan info pengguna, ekstrak nama dan nama depan dan buat UserKey, lalu buat UserEntity dengan kunci komposit ini dan aktifkan Spring Data save () yang seharusnya memilah semuanya untuk Anda.

Andreas Gelever
sumber