Skip to content

15 - แก้ไขข้อมูลผู้ใช้

บทนี้จะเพิ่ม endpoint สำหรับแก้ไขข้อมูลผู้ใช้:

PUT /api/v1/users/{id}

หลังจบบทนี้ผู้อ่านควรเข้าใจ:

  • update flow ที่ถูกต้องควรค้นหาข้อมูลเดิมก่อน
  • ทำไมไม่ควร save object ใหม่โดยไม่เช็ก id
  • field ไหนควรแก้ได้ และ field ไหนไม่ควรแก้ผ่าน endpoint ทั่วไป
  • ความต่างระหว่าง PUT และ PATCH

การแก้ไข user ควรมีขั้นตอน:

1. รับ id จาก URL
2. ค้นหา user เดิมจาก database
3. ถ้าไม่เจอ ให้ตอบ error
4. อัปเดตเฉพาะ field ที่อนุญาต
5. save entity เดิมกลับลง database
6. ส่ง response ที่ไม่มี password กลับไป

จุดสำคัญคือเราแก้ entity เดิม ไม่ใช่สร้าง object ใหม่แล้ว save ทันที

เพิ่ม method update ใน UserService

Section titled “เพิ่ม method update ใน UserService”

เปิดไฟล์:

src/main/java/com/example/backendapi/service/UserService.java

เพิ่ม method นี้:

public User update(Long id, User request) {
User user = findById(id);
userRepository.findByUsername(request.getUsername())
.filter(existing -> !existing.getId().equals(id))
.ifPresent(existing -> {
throw new RuntimeException("Username already exists");
});
userRepository.findByEmail(request.getEmail())
.filter(existing -> !existing.getId().equals(id))
.ifPresent(existing -> {
throw new RuntimeException("Email already exists");
});
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
return userRepository.save(user);
}

method นี้ทำ 3 เรื่อง:

  1. หา user เดิมก่อน
  2. เช็กว่า username หรือ email ใหม่ไปซ้ำกับ user คนอื่นไหม
  3. แก้เฉพาะ username และ email

ใน endpoint นี้เรายังไม่ให้แก้ password, role หรือ status เพราะ field เหล่านี้ควรมี flow แยก:

Fieldควรแก้ผ่าน flow ไหน
passwordchange password หรือ reset password
roleadmin role management
statusadmin suspend/activate user
createdAtsystem จัดการเอง
updatedAtsystem จัดการเอง

เพิ่ม endpoint ใน UserController

Section titled “เพิ่ม endpoint ใน UserController”

เปิดไฟล์:

src/main/java/com/example/backendapi/controller/UserController.java

เพิ่ม import:

import org.springframework.web.bind.annotation.PutMapping;

แล้วเพิ่ม endpoint:

@PutMapping("/{id}")
public UserResponse update(@PathVariable Long id, @RequestBody User request) {
return toResponse(userService.update(id, request));
}

ตอนนี้ controller จะมี endpoint ครบ:

GET /api/v1/users
GET /api/v1/users/{id}
POST /api/v1/users
PUT /api/v1/users/{id}
DELETE /api/v1/users/{id}

สร้าง user ก่อน:

POST /api/v1/users
Content-Type: application/json
{
"username": "john",
"email": "john@example.com",
"password": "password123"
}

แก้ไข user:

PUT /api/v1/users/1
Content-Type: application/json
{
"username": "johnny",
"email": "johnny@example.com"
}

response:

{
"id": 1,
"username": "johnny",
"email": "johnny@example.com",
"role": "USER",
"status": "ACTIVE"
}

PUT กับ PATCH ต่างกันอย่างไร

Section titled “PUT กับ PATCH ต่างกันอย่างไร”

PUT มักใช้เมื่อ client ส่งข้อมูลที่ต้องการแทน resource หลักในภาพรวม เช่น update profile ด้วย username และ email พร้อมกัน

PATCH มักใช้เมื่อ client แก้เฉพาะบาง field เช่น:

PATCH /api/v1/users/1/status
PATCH /api/v1/users/1/role
PATCH /api/v1/users/1/password

ในระบบ admin เราจะใช้แนวคิดนี้อีกครั้งตอนแยก endpoint สำหรับเปลี่ยน role และ status

ปัญหาที่พบบ่อย

Section titled “ปัญหาที่พบบ่อย”
อาการสาเหตุที่เป็นไปได้
update แล้วกลายเป็น insertsave object ใหม่โดยไม่ค้นหา entity เดิมก่อน
username ซ้ำแล้วได้ error จาก databaseไม่ได้เช็ก duplicate ใน service
response มี passwordส่ง entity กลับตรง ๆ
id ใน body กับ path ไม่ตรงกันไม่ควรใช้ id จาก body ให้ยึด id จาก path

Checkpoint ปิดภาคฐานข้อมูล

Section titled “Checkpoint ปิดภาคฐานข้อมูล”

หลังจบบทนี้ ระบบควรทำได้:

  • เชื่อมต่อ PostgreSQL
  • สร้าง table จาก entity
  • อ่าน user จาก database
  • สร้าง user ลง database
  • แก้ไข username/email
  • ลบ user
  • ไม่ส่ง password กลับใน response

แบบฝึกหัดท้ายบท

Section titled “แบบฝึกหัดท้ายบท”
  1. สร้าง user 2 คน
  2. แก้ email ของ user คนแรก
  3. ลองแก้ email ของ user คนแรกให้ซ้ำกับ user คนที่สอง
  4. ลอง update id ที่ไม่มีอยู่
  5. ตรวจค่า updatedAt ใน database หลัง update