← Назад к вопросам

Для чего нужен PUT?

1.0 Junior🔥 171 комментариев
#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

HTTP метод PUT

PUT — это HTTP метод для полной замены ресурса на сервере. Это одна из фундаментальных операций REST API, определённая в RFC 7231.

Основная назначение

PUT используется для:

  1. Замены всего содержимого ресурса — отправляются все поля
  2. Создания ресурса с конкретным ID — если ресурса нет, он создаётся
  3. Полного обновления — старые данные полностью заменяются на новые

Отличие от PATCH

Это часто путают, поэтому важно понять разницу:

// Исходный ресурс (GET /users/1)
{
  "id": 1,
  "name": "John Doe",
  "email": "john@example.com",
  "phone": "+1234567890"
}

// PUT — полная замена
// PUT /users/1
{
  "name": "Jane Smith",
  "email": "jane@example.com",
  "phone": "+0987654321"
}
// Результат: полностью заменился, имя изменилось на Jane

// PATCH — частичное обновление
// PATCH /users/1
{
  "email": "newemail@example.com"
}
// Результат: только email изменился, остальное осталось

Идемпотентность PUT

PUT идемпотентен — много одинаковых PUT запросов дают тот же результат, что один запрос:

// Первый PUT
PUT /users/1
{
  "name": "Alice",
  "email": "alice@example.com"
}
Ответ: 200 OK (или 201 Created если был создан)

// Второй PUT (тот же)
PUT /users/1
{
  "name": "Alice",
  "email": "alice@example.com"
}
Ответ: 200 OK
// Данные остались точно такими же

// Третий PUT (тот же)
Получаем точно то же состояние

Практические примеры

Пример 1: Обновление профиля пользователя

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // PUT — полная замена профиля
    @PutMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(
            @PathVariable Long id,
            @RequestBody UpdateUserRequest request) {
        
        // Берём ВСЕ поля из request и заменяем ВСЕ поля в БД
        User user = userService.updateUser(id, request);
        return ResponseEntity.ok(UserDTO.from(user));
    }
}

public class UpdateUserRequest {
    private String name;           // Обязательно
    private String email;          // Обязательно
    private String phone;          // Обязательно
    private String bio;            // Обязательно
    private String profileImage;   // Обязательно
    
    // Все поля должны быть заполнены для PUT
    // Если клиент не хочет менять что-то, он отправляет старое значение
}

@Service
public class UserService {
    
    @Transactional
    public User updateUser(Long id, UpdateUserRequest request) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
        
        // ПОЛНАЯ ЗАМЕНА всех полей
        user.setName(request.getName());
        user.setEmail(request.getEmail());
        user.setPhone(request.getPhone());
        user.setBio(request.getBio());
        user.setProfileImage(request.getProfileImage());
        
        return userRepository.save(user);
    }
}

// REST запрос
PUT /api/v1/users/123 HTTP/1.1
Content-Type: application/json

{
  "name": "John Smith",
  "email": "john.smith@example.com",
  "phone": "+1234567890",
  "bio": "Software Engineer",
  "profileImage": "https://example.com/john.jpg"
}

// Ответ
200 OK
{
  "id": 123,
  "name": "John Smith",
  "email": "john.smith@example.com",
  "phone": "+1234567890",
  "bio": "Software Engineer",
  "profileImage": "https://example.com/john.jpg"
}

Пример 2: Обновление конфигурации

@RestController
@RequestMapping("/api/v1/settings")
public class SettingsController {
    
    @Autowired
    private SettingsService settingsService;
    
    // PUT — заменяем все настройки приложения
    @PutMapping("/app-config")
    public ResponseEntity<AppConfigDTO> updateAppConfig(
            @RequestBody AppConfigRequest request) {
        
        AppConfig config = settingsService.updateConfig(request);
        return ResponseEntity.ok(AppConfigDTO.from(config));
    }
}

public class AppConfigRequest {
    private boolean notificationsEnabled;  // Все флаги должны быть в запросе
    private int sessionTimeout;
    private String theme;
    private String language;
    private boolean maintenanceMode;
}

// Клиент отправляет все параметры (включая те, которые не меняет)
PUT /api/v1/settings/app-config HTTP/1.1
Content-Type: application/json

{
  "notificationsEnabled": true,
  "sessionTimeout": 3600,
  "theme": "dark",
  "language": "en",
  "maintenanceMode": false
}

Пример 3: Создание ресурса с конкретным ID

@RestController
@RequestMapping("/api/v1/documents")
public class DocumentController {
    
    @Autowired
    private DocumentService documentService;
    
    // PUT может создать ресурс, если его нет
    @PutMapping("/{documentId}")
    public ResponseEntity<?> upsertDocument(
            @PathVariable String documentId,
            @RequestBody DocumentRequest request) {
        
        Document document = documentService.createOrUpdate(documentId, request);
        return ResponseEntity.ok(document);
    }
}

@Service
public class DocumentService {
    
    @Transactional
    public Document createOrUpdate(String documentId, DocumentRequest request) {
        // Ищем документ
        Document document = documentRepository.findById(documentId)
            .orElse(null);
        
        if (document == null) {
            // Нет — создаём новый с конкретным ID
            document = new Document();
            document.setId(documentId);  // Устанавливаем ID из URL
            // Ответ будет 201 Created
        }
        // else — обновляем существующий (200 OK)
        
        document.setTitle(request.getTitle());
        document.setContent(request.getContent());
        document.setAuthor(request.getAuthor());
        
        return documentRepository.save(document);
    }
}

// Запрос
PUT /api/v1/documents/doc-12345 HTTP/1.1
Content-Type: application/json

{
  "title": "My Document",
  "content": "Lorem ipsum...",
  "author": "John Doe"
}

// Ответ (если был создан)
201 Created
Location: /api/v1/documents/doc-12345

// Ответ (если обновлён)
200 OK

PUT vs POST vs PATCH

ОперацияМетодИспользованиеИдемпотентенСоздаёт новый
СозданиеPOSTКогда сервер генерирует IDНетДа
Полное обновлениеPUTЗамена всего ресурсаДаМожет
Частичное обновлениеPATCHОбновление отдельных полейНет*Нет
ПолучениеGETЧтениеДаНет
УдалениеDELETEУдалениеДаНет

Правильное использование PUT

✅ Правильно:

// Все поля отправляются
PUT /api/v1/users/1
{
  "name": "Alice",
  "email": "alice@example.com",
  "age": 28,
  "address": "123 Main St"
}

// Или использование вложенного JSON
PUT /api/v1/users/1
{
  "profile": {
    "name": "Alice",
    "email": "alice@example.com"
  },
  "settings": {
    "notifications": true,
    "language": "en"
  }
}

❌ Неправильно:

// Отправляем только изменённые поля (это PATCH, не PUT)
PUT /api/v1/users/1
{
  "email": "newemail@example.com"
}
// Правильно использовать PATCH для этого

// Отправляем ID в теле (уже в URL)
PUT /api/v1/users/1
{
  "id": 1,  // Лишнее
  "name": "Alice"
}

// Отправляем null для удаления полей (может привести к ошибкам)
PUT /api/v1/users/1
{
  "name": "Alice",
  "phone": null  // Лучше явно указать пустое значение
}

HTTP коды ответа для PUT

@PutMapping("/{id}")
public ResponseEntity<?> updateResource(@PathVariable Long id, @RequestBody UpdateRequest request) {
    
    Resource existing = resourceRepository.findById(id).orElse(null);
    
    if (existing == null) {
        // Ресурса нет, создаём новый
        Resource newResource = new Resource();
        newResource.setId(id);
        newResource.setData(request.getData());
        resourceRepository.save(newResource);
        
        // 201 Created — ресурс был создан
        return ResponseEntity
            .status(HttpStatus.CREATED)
            .location(URI.create("/api/v1/resources/" + id))
            .body(newResource);
    } else {
        // Ресурс существует, обновляем
        existing.setData(request.getData());
        resourceRepository.save(existing);
        
        // 200 OK — ресурс был обновлён
        return ResponseEntity.ok(existing);
    }
}

Важное замечание: Null-safety

// Проблема: если клиент не отправил поле, что делать?
public class UserUpdateRequest {
    private String name;
    private String email;
    // Если клиент отправит {"name": "Alice"}, email будет null
}

// Решение 1: Используйте Required аннотации
public class UserUpdateRequest {
    @NotBlank(message = "Name is required")
    private String name;
    
    @NotBlank(message = "Email is required")
    private String email;
}

// Решение 2: Используйте Optional
public class UserUpdateRequest {
    private Optional<String> name;
    private Optional<String> email;
}

// Решение 3: Используйте PATCH для частичных обновлений
// PUT для полных, PATCH для частичных — это чище

Реальный пример: REST API для конфигурации

@RestController
@RequestMapping("/api/v1/config")
public class ConfigController {
    
    @Autowired
    private ConfigService configService;
    
    // GET — получить текущую конфигурацию
    @GetMapping("/database")
    public ResponseEntity<DatabaseConfig> getDbConfig() {
        return ResponseEntity.ok(configService.getDatabaseConfig());
    }
    
    // PUT — полностью заменить конфигурацию
    @PutMapping("/database")
    @Transactional
    public ResponseEntity<DatabaseConfig> updateDbConfig(
            @RequestBody DatabaseConfigRequest request) {
        
        DatabaseConfig config = new DatabaseConfig();
        config.setHost(request.getHost());              // Все поля обновляются
        config.setPort(request.getPort());
        config.setDatabase(request.getDatabase());
        config.setUsername(request.getUsername());
        config.setPassword(request.getPassword());
        config.setMaxPoolSize(request.getMaxPoolSize());
        config.setConnectionTimeout(request.getConnectionTimeout());
        
        configService.updateDatabaseConfig(config);
        
        return ResponseEntity.ok(config);
    }
}

public class DatabaseConfigRequest {
    @NotBlank
    private String host;
    
    @Min(1)
    @Max(65535)
    private int port;
    
    @NotBlank
    private String database;
    
    @NotBlank
    private String username;
    
    @NotBlank
    private String password;
    
    private int maxPoolSize;
    private int connectionTimeout;
    
    // Getters and setters
}

Итог

PUT используется для:

  1. Полной замены ресурса — все поля должны быть отправлены
  2. Создания ресурса с конкретным ID — если ресурса нет, он создаётся
  3. Идемпотентных операций — многократное выполнение = один результат

Не путай с:

  • POST — создание новых ресурсов (ID генерирует сервер)
  • PATCH — частичное обновление (только изменённые поля)
  • DELETE — удаление

PUT — это стандартный метод для REST API обновления полных ресурсов и должен быть идемпотентным для корректной работы распределённых систем.

Для чего нужен PUT? | PrepBro