15 - แก้ไขข้อมูลผู้ใช้
เป้าหมายของบท
Section titled “เป้าหมายของบท”บทนี้จะเพิ่ม endpoint สำหรับแก้ไขข้อมูลผู้ใช้:
PUT /api/v1/users/{id}หลังจบบทนี้ผู้อ่านควรเข้าใจ:
- update flow ที่ถูกต้องควรค้นหาข้อมูลเดิมก่อน
- ทำไมไม่ควร save object ใหม่โดยไม่เช็ก id
- field ไหนควรแก้ได้ และ field ไหนไม่ควรแก้ผ่าน endpoint ทั่วไป
- ความต่างระหว่าง
PUTและPATCH
Update flow ที่ควรใช้
Section titled “Update flow ที่ควรใช้”การแก้ไข user ควรมีขั้นตอน:
1. รับ id จาก URL2. ค้นหา user เดิมจาก database3. ถ้าไม่เจอ ให้ตอบ error4. อัปเดตเฉพาะ field ที่อนุญาต5. save entity เดิมกลับลง database6. ส่ง 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 เรื่อง:
- หา user เดิมก่อน
- เช็กว่า username หรือ email ใหม่ไปซ้ำกับ user คนอื่นไหม
- แก้เฉพาะ
usernameและemail
ใน endpoint นี้เรายังไม่ให้แก้ password, role หรือ status เพราะ field เหล่านี้ควรมี flow แยก:
| Field | ควรแก้ผ่าน flow ไหน |
|---|---|
password | change password หรือ reset password |
role | admin role management |
status | admin suspend/activate user |
createdAt | system จัดการเอง |
updatedAt | system จัดการเอง |
เพิ่ม 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/usersGET /api/v1/users/{id}POST /api/v1/usersPUT /api/v1/users/{id}DELETE /api/v1/users/{id}ทดสอบด้วย request
Section titled “ทดสอบด้วย request”สร้าง user ก่อน:
POST /api/v1/usersContent-Type: application/json{ "username": "john", "email": "john@example.com", "password": "password123"}แก้ไข user:
PUT /api/v1/users/1Content-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/statusPATCH /api/v1/users/1/rolePATCH /api/v1/users/1/passwordในระบบ admin เราจะใช้แนวคิดนี้อีกครั้งตอนแยก endpoint สำหรับเปลี่ยน role และ status
ปัญหาที่พบบ่อย
Section titled “ปัญหาที่พบบ่อย”| อาการ | สาเหตุที่เป็นไปได้ |
|---|---|
| update แล้วกลายเป็น insert | save 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 “แบบฝึกหัดท้ายบท”- สร้าง user 2 คน
- แก้ email ของ user คนแรก
- ลองแก้ email ของ user คนแรกให้ซ้ำกับ user คนที่สอง
- ลอง update id ที่ไม่มีอยู่
- ตรวจค่า
updatedAtใน database หลัง update