ฉันจะอัพเดตเอนทิตีโดยใช้ spring-data-jpa ได้อย่างไร


196

คำถามก็บอกทุกอย่าง การใช้ JPAR ที่เก็บฉันจะอัพเดตเอนทิตีได้อย่างไร

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

จนถึงตอนนี้ดีมาก ตอนนี้ฉันต้องการอัปเดตผู้ใช้รายนี้บอกว่าเปลี่ยนอายุของเขา เพื่อจุดประสงค์นี้ฉันสามารถใช้ Query ได้ทั้ง QueryDSL หรือ NamedQuery ไม่ว่าจะเป็นอะไรก็ตาม แต่เมื่อพิจารณาว่าฉันต้องการใช้ spring-data-jpa และ JPARepository ฉันจะบอกได้อย่างไรว่าแทนที่จะใช้ตัวแทรกฉันต้องการอัปเดต

โดยเฉพาะฉันจะบอก spring-data-jpa ได้อย่างไรว่าผู้ใช้ที่มีชื่อผู้ใช้และชื่อเดียวกันเป็นจริงเท่ากับและควรจะอัพเดตเอนทิตีที่มีอยู่เดิม การเอาชนะอย่างเท่าเทียมกันไม่ได้แก้ปัญหานี้


1
คุณแน่ใจหรือไม่ว่า ID ได้รับการเขียนใหม่เมื่อคุณบันทึกวัตถุที่มีอยู่ในฐานข้อมูลหรือไม่? ไม่เคยมีในโครงการ tbh ของฉัน
Byron Voorbach

@ByronVoorbach yup คุณถูกต้องเพียงแค่ทดสอบสิ่งนี้ อัปเดตคำถามเช่นกันขอบคุณ
ยูจีน

2
สวัสดีเพื่อนคุณสามารถดูลิงค์นี้stackoverflow.com/questions/24420572/ ......คุณสามารถเป็นแนวทางได้เช่น saveOrUpdate ()
ibrahimKiraz


ฉันคิดว่าเรามีทางออกที่สวยงามที่นี่: ป้อนคำอธิบายลิงก์ที่นี่
Clebio Vieira

คำตอบ:


208

เอกลักษณ์ของเอนทิตีถูกกำหนดโดยคีย์หลัก เนื่องจากfirstnameและlastnameไม่ได้เป็นส่วนหนึ่งของคีย์หลักคุณไม่สามารถบอก JPA ให้ปฏิบัติต่อUsers ด้วยfirstnames และlastnames เหมือนกันได้หากพวกเขามีuserIds ที่แตกต่างกัน

ดังนั้นหากคุณต้องการอัปเดตสิ่งที่Userระบุfirstnameและlastnameคุณจำเป็นต้องค้นหามันUserโดยใช้แบบสอบถามแล้วเปลี่ยนฟิลด์ที่เหมาะสมของวัตถุที่คุณพบ การเปลี่ยนแปลงเหล่านี้จะถูกล้างไปยังฐานข้อมูลโดยอัตโนมัติเมื่อสิ้นสุดการทำธุรกรรมดังนั้นคุณไม่จำเป็นต้องทำอะไรเพื่อบันทึกการเปลี่ยนแปลงเหล่านี้อย่างชัดเจน

แก้ไข:

บางทีฉันควรอธิบายรายละเอียดเกี่ยวกับความหมายโดยรวมของ JPA มีสองวิธีหลักในการออกแบบ API การคงอยู่:

  • วิธีการแทรก / อัปเดตแทรกวิธีการปรับปรุงเมื่อคุณต้องการแก้ไขฐานข้อมูลคุณควรเรียกใช้เมธอด persistence API อย่างชัดเจน: คุณโทรinsertเพื่อแทรกวัตถุหรือupdateบันทึกสถานะใหม่ของวัตถุลงในฐานข้อมูล

  • หน่วยของวิธีการทำงาน ในกรณีนี้คุณมีชุดของวัตถุที่จัดการโดยห้องสมุดการคงอยู่ การเปลี่ยนแปลงทั้งหมดที่คุณทำกับวัตถุเหล่านี้จะถูกล้างไปยังฐานข้อมูลโดยอัตโนมัติในตอนท้ายของหน่วยงาน (เช่นในตอนท้ายของธุรกรรมปัจจุบันในกรณีทั่วไป) เมื่อคุณต้องใส่ระเบียนใหม่ลงในฐานข้อมูลที่คุณให้วัตถุที่สอดคล้องกันการจัดการ วัตถุที่มีการจัดการจะถูกระบุด้วยคีย์หลักของพวกเขาดังนั้นหากคุณสร้างวัตถุที่มีคีย์หลักที่กำหนดไว้ล่วงหน้าจัดการมันจะถูกเชื่อมโยงกับบันทึกฐานข้อมูลของ id เดียวกันและสถานะของวัตถุนี้จะถูกเผยแพร่ไปยังระเบียนนั้นโดยอัตโนมัติ

JPA ปฏิบัติตามแนวทางหลัง save()ใน Spring Data JPA ได้รับการสนับสนุนโดยmerge()JPA ธรรมดาดังนั้นจึงทำให้เอนทิตีของคุณจัดการตามที่อธิบายไว้ข้างต้น ก็หมายความว่าการเรียกsave()บนวัตถุที่มีรหัสที่กำหนดไว้ล่วงหน้าจะปรับปรุงบันทึกฐานข้อมูลที่สอดคล้องกันมากกว่าแทรกขึ้นมาใหม่และยังอธิบายว่าทำไมไม่ได้เรียกว่าsave()create()


ใช่ฉันคิดว่าฉันรู้เรื่องนี้ ฉันหมายถึง spring-data-jpa อย่างเคร่งครัด ฉันมีสองปัญหากับคำตอบนี้ในขณะนี้: 1) คุณค่าทางธุรกิจไม่ควรเป็นส่วนหนึ่งของคีย์หลัก - นั่นคือสิ่งที่รู้ใช่มั้ย ดังนั้นการมีชื่อและนามสกุลเป็นคีย์หลักจึงไม่ดี และ 2) ทำไมวิธีนี้จึงไม่เรียกว่าสร้าง แต่บันทึกแทนใน spring-data-jpa
ยูจีน

1
"บันทึก () ใน Spring Data JPA ได้รับการสนับสนุนโดยผสาน () ใน JPA ธรรมดา" คุณดูรหัสจริงหรือไม่ ฉันเพิ่งทำและมันทั้งสองสำรองโดยคงอยู่หรือผสาน มันจะคงอยู่หรืออัปเดตตามการมีอยู่ของรหัส (คีย์หลัก) ฉันคิดว่าควรบันทึกไว้ในวิธีการบันทึก ดังนั้นบันทึกเป็นจริงทั้งผสานหรือคงอยู่
ยูจีน

ฉันคิดว่ามันก็เรียกว่าบันทึกเพราะมันควรจะบันทึกวัตถุไม่ว่าสถานะมันอยู่ใน - มันจะทำการปรับปรุงหรือแทรกซึ่งเท่ากับการบันทึกสถานะ
ยูจีน

1
สิ่งนี้จะไม่ได้ผลสำหรับฉัน ฉันลองบันทึกในเอนทิตีที่มีคีย์หลักที่ถูกต้อง ฉันเข้าสู่หน้าเว็บด้วย "คำสั่ง / แก้ไข /: id" และจริง ๆ แล้วมันให้วัตถุที่ถูกต้องโดยฉัน ฉันไม่ลองทำอะไรเพราะความรักของพระเจ้าจะอัพเดทสิ่งต่างๆ มันโพสต์เอนทิตีใหม่เสมอฉันพยายามสร้างบริการที่กำหนดเองและใช้ "ผสาน" กับ EntityManager ของฉันและมันก็ยังไม่ทำงาน มันจะโพสต์นิติบุคคลใหม่เสมอ
DtechNet

1
@DTechNet ฉันพบปัญหาที่คล้ายกันกับคุณ DtechNet และปรากฏว่าปัญหาของฉันคือฉันมีคีย์หลักผิดประเภทที่ระบุในอินเทอร์เฟซที่เก็บข้อมูล Spring Data ของฉัน มันบอกว่าextends CrudRepository<MyEntity, Integer>แทนที่จะextends CrudRepository<MyEntity, String>ชอบมันควรจะมี มันช่วยได้ไหม ฉันรู้ว่านี่เกือบหนึ่งปีต่อมา หวังว่ามันจะช่วยคนอื่น
Kent Bull

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
เฮ้! ขอบคุณกำลังใช้ spring data beans ดังนั้นมันจะดูแลอัพเดตของฉันโดยอัตโนมัติ <S ขยาย T> S บันทึก (เอนทิตี S); ดูแลอัพเดตโดยอัตโนมัติ ฉันไม่จำเป็นต้องใช้วิธีการของคุณ! ขอบคุณทุกคน!
bks4line

3
ทุกที่ทุกเวลา :) วิธีการบันทึกใช้งานได้หากคุณต้องการบันทึกเอนทิตี (มันจะมอบหมายการเรียกไปยัง em.persist () หรือ em.merge () ที่อยู่เบื้องหลัง อย่างไรก็ตามแบบสอบถามแบบกำหนดเองมีประโยชน์เมื่อคุณต้องการอัปเดตบางฟิลด์ในฐานข้อมูล
hussachai

วิธีการเกี่ยวกับเมื่อหนึ่งในพารามิเตอร์ของคุณเป็น id ของหน่วยงานย่อย (manyToOne) ว่าควรจะปรับปรุงอย่างไร (หนังสือมีผู้แต่งและคุณผ่านรหัสหนังสือและรหัสผู้แต่งเพื่ออัปเดตผู้แต่งหนังสือ)
มาห์

1
To update an entity by querying then saving is not efficientนี่ไม่ใช่ทางเลือกเพียงสองทางเท่านั้น มีวิธีการระบุ id และรับวัตถุแถวโดยไม่ต้องสอบถาม หากคุณทำrow = repo.getOne(id)และจากนั้นrow.attr = 42; repo.save(row);และดูบันทึกคุณจะเห็นเฉพาะแบบสอบถามแบบใช้ปรับปรุงข้อมูล
nurettin

21

คุณสามารถใช้ฟังก์ชั่นนี้กับ save () JPAfunction แต่วัตถุที่ส่งเป็นพารามิเตอร์จะต้องมี id ที่มีอยู่ในฐานข้อมูลมิฉะนั้นมันจะไม่ทำงานเพราะ save () เมื่อเราส่งวัตถุโดยไม่มี id มันจะเพิ่มแถวโดยตรงใน ฐานข้อมูล แต่ถ้าเราส่งอ็อบเจกต์ด้วย id ที่มีอยู่มันจะเปลี่ยนคอลัมน์ที่พบในฐานข้อมูลแล้ว

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
ประสิทธิภาพไม่เป็นปัญหาหรือไม่ถ้าคุณต้องโหลดวัตถุจากฐานข้อมูลก่อนทำการอัพเดท (ขออภัยสำหรับภาษาอังกฤษของฉัน)
สิงหาคม 2390

3
มีสองแบบสอบถามแทนหนึ่งซึ่งไม่ได้รับการ
กล่าวล่วงหน้า

ฉันรู้ว่าฉันเพิ่งปรากฏตัวขึ้นอีกวิธีที่จะทำ! แต่ทำไม jpa ใช้ฟังก์ชั่นอัพเดทเมื่อ id เหมือนกัน?
Kalifornium

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

ดังที่คุณเห็นมันจะตรวจสอบว่ามี ID อยู่หรือไม่ก่อนถ้ามีเอนทิตีอยู่แล้วจะมีเพียงการอัปเดตเท่านั้นที่จะเกิดขึ้นตาม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วิธีการดังกล่าวข้างต้นสำหรับการร้องขอฐานข้อมูลหลาย ในกรณีนี้ไม่จำเป็นต้องuserRepository.save(inbound);เปลี่ยนแปลงล้างโดยอัตโนมัติ
กริกอ Kislin

5

ข้อมูลฤดูใบไม้ผลิ save()วิธีการจะช่วยให้คุณดำเนินการทั้งสอง: เพิ่มรายการใหม่และปรับปรุงรายการที่มีอยู่

เพียงแค่เรียกsave()และสนุกกับชีวิต :))


ด้วยวิธีนี้ถ้าฉันส่งคนละคนIdจะบันทึกไว้ฉันจะหลีกเลี่ยงการบันทึกใหม่ได้อย่างไร
Abd Abughazaleh

1
@AbdAbughazaleh ตรวจสอบว่ามี ID ขาเข้าอยู่ในที่เก็บของคุณหรือไม่ คุณสามารถใช้ 'repository.findById (id) .map (เอนทิตี้ -> {// ทำบางสิ่งกลับ repository.save (เอนทิตี)}) .orElseGet () -> {// ทำบางสิ่งบางอย่างกลับ;}); '
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));
}

@JoshuaTaylor พลาดแน่นอนไป :) จะลบความคิดเห็น ...
Eugene

1

โดยเฉพาะฉันจะบอก 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 () ซึ่งควรเรียงลำดับทุกอย่างให้คุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.