← Назад к вопросам
Как добавить поле в сущность в Hibernate
1.2 Junior🔥 111 комментариев
#ORM и Hibernate#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ
Добавление поля в сущность Hibernate: полный процесс
Добавление нового столбца в БД и соответствующего поля в Hibernate сущность требует синхронизации на нескольких уровнях. Расскажу о проверенном подходе.
Шаг 1: Добавляем столбец в БД через миграцию
-- V001__create_users_table.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- V002__add_phone_field.sql (позже добавляем новое поле)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
Шаг 2: Обновляем Hibernate сущность
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "email", unique = true, nullable = false)
private String email;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
// ★ НОВОЕ ПОЛЕ
@Column(name = "phone", length = 20, nullable = true)
private String phone;
// Конструкторы
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
this.createdAt = LocalDateTime.now(ZoneId.of("UTC"));
}
// Getters и Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
// ★ Getter и Setter для нового поля
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", createdAt=" + createdAt +
'}';
}
}
Шаг 3: Используем поле в репозитории
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Поиск по email (существующее поле)
Optional<User> findByEmail(String email);
// ★ Поиск по новому полю (phone)
Optional<User> findByPhone(String phone);
// ★ Поиск по обоим полям
List<User> findByNameAndPhone(String name, String phone);
// ★ Кастомный запрос с новым полем
@Query("SELECT u FROM User u WHERE u.phone IS NOT NULL")
List<User> findUsersWithPhone();
// ★ Обновление нового поля
@Query("UPDATE User u SET u.phone = :phone WHERE u.id = :id")
void updatePhone(Long id, String phone);
}
Шаг 4: Используем в Service слое
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// Получить пользователя с новым полем
public User getUserWithPhone(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
// ★ Использовать новое поле
@Transactional
public void addPhoneToUser(Long userId, String phone) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("User not found"));
// Валидация
if (!isValidPhoneNumber(phone)) {
throw new IllegalArgumentException("Invalid phone format");
}
// Устанавливаем поле
user.setPhone(phone);
// Сохраняем (Hibernate обнаружит изменение и выполнит UPDATE)
userRepository.save(user);
}
// Поиск по телефону
@Transactional(readOnly = true)
public User findByPhone(String phone) {
return userRepository.findByPhone(phone)
.orElseThrow(() -> new UserNotFoundException("User with phone " + phone + " not found"));
}
// Получить всех пользователей с телефоном
@Transactional(readOnly = true)
public List<User> getUsersWithPhone() {
return userRepository.findUsersWithPhone();
}
private boolean isValidPhoneNumber(String phone) {
// Простая валидация
return phone != null && phone.matches("\\d{10,20}");
}
}
Шаг 5: DTO для передачи данных
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private Long id;
private String name;
private String email;
private String phone; // ★ Новое поле
private LocalDateTime createdAt;
// Конвертер из сущности в DTO
public static UserDto fromEntity(User user) {
return new UserDto(
user.getId(),
user.getName(),
user.getEmail(),
user.getPhone(), // ★ Новое поле
user.getCreatedAt()
);
}
// Конвертер из DTO в сущность
public User toEntity() {
User user = new User();
user.setId(this.id);
user.setName(this.name);
user.setEmail(this.email);
user.setPhone(this.phone); // ★ Новое поле
user.setCreatedAt(this.createdAt);
return user;
}
}
Шаг 6: API контроллер
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
User user = userService.getUserWithPhone(id);
return ResponseEntity.ok(UserDto.fromEntity(user));
}
// ★ Новый endpoint для установки телефона
@PutMapping("/{id}/phone")
public ResponseEntity<UserDto> updatePhone(
@PathVariable Long id,
@RequestBody PhoneUpdateRequest request) {
userService.addPhoneToUser(id, request.getPhone());
User user = userService.getUserWithPhone(id);
return ResponseEntity.ok(UserDto.fromEntity(user));
}
// ★ Поиск по телефону
@GetMapping("/by-phone/{phone}")
public ResponseEntity<UserDto> getUserByPhone(@PathVariable String phone) {
User user = userService.findByPhone(phone);
return ResponseEntity.ok(UserDto.fromEntity(user));
}
// ★ Получить всех пользователей с телефоном
@GetMapping("/with-phone")
public ResponseEntity<List<UserDto>> getUsersWithPhone() {
List<UserDto> users = userService.getUsersWithPhone()
.stream()
.map(UserDto::fromEntity)
.toList();
return ResponseEntity.ok(users);
}
}
public class PhoneUpdateRequest {
private String phone;
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}
Шаг 7: Тесты
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
@TestPropertySource("classpath:application-test.properties")
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void testAddPhoneToUser() {
// Arrange
User user = new User("John Doe", "john@example.com");
user.setPhone("1234567890"); // ★ Используем новое поле
// Act
User saved = userRepository.save(user);
// Assert
assertThat(saved.getId()).isNotNull();
assertThat(saved.getPhone()).isEqualTo("1234567890"); // ★ Проверяем
}
@Test
public void testFindByPhone() {
// Arrange
User user = new User("Jane Doe", "jane@example.com");
user.setPhone("9876543210");
userRepository.save(user);
// Act
User found = userRepository.findByPhone("9876543210").orElse(null);
// Assert
assertThat(found).isNotNull();
assertThat(found.getName()).isEqualTo("Jane Doe");
assertThat(found.getPhone()).isEqualTo("9876543210");
}
@Test
public void testFindUsersWithPhone() {
// Arrange
User user1 = new User("User1", "user1@example.com");
user1.setPhone("1111111111");
User user2 = new User("User2", "user2@example.com");
// user2 без телефона
userRepository.saveAll(List.of(user1, user2));
// Act
List<User> usersWithPhone = userRepository.findUsersWithPhone();
// Assert
assertThat(usersWithPhone).hasSize(1);
assertThat(usersWithPhone.get(0).getPhone()).isNotNull();
}
}
Чек-лист для добавления поля
✓ 1. Миграция БД
↳ ALTER TABLE add column
↳ Прогнать миграцию на dev
✓ 2. Hibernate Entity
↳ @Column аннотация
↳ Getter и Setter
↳ toString() обновлен
↳ equals() и hashCode() если нужны
✓ 3. Repository
↳ Новые методы поиска (findByPhone)
↳ Кастомные запросы (@Query)
✓ 4. Service
↳ Валидация данных
↳ Бизнес логика
↳ @Transactional если нужно
✓ 5. DTO
↳ Новое поле в DTO
↳ Маппинг entity ↔ DTO
✓ 6. API Controller
↳ Новые endpoints
↳ Валидация input'ов
↳ Документация (Swagger)
✓ 7. Тесты
↳ Unit тесты
↳ Integration тесты
↳ E2E тесты
Частые ошибки
// ✗ НЕПРАВИЛЬНО: забыли @Column
@Entity
public class User {
private String phone; // Hibernate не знает, какой столбец использовать!
}
// ✓ ПРАВИЛЬНО: явно указываем столбец
@Entity
public class User {
@Column(name = "phone", nullable = true)
private String phone; // Hibernate знает, что это столбец phone
}
// ✗ НЕПРАВИЛЬНО: забыли миграцию
// Добавили поле в сущность, но БД не обновлена!
// ✓ ПРАВИЛЬНО: сначала миграция, потом код
// 1. Миграция ALTER TABLE
// 2. Обновить Entity
// 3. Обновить Repository
// 4. Обновить Service, DTO, Controller
Вывод
Для добавления поля в Hibernate сущность:
- Миграция БД первой — ALTER TABLE добавить столбец
- Entity — @Column, getter, setter
- Repository — новые методы поиска
- Service — бизнес логика и валидация
- DTO — для API контрактов
- Controller — новые endpoints
- Тесты — unit + integration
Синхронизация между БД и кодом критична. Помните: Source of Truth — миграции, Hibernate модели — просто описание.