← Назад к вопросам
Для чего нужен репозиторий?
1.7 Middle🔥 251 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Repository Паттерн: Назначение и Применение
Определение
Repository — это паттерн проектирования, который создаёт абстракцию между бизнес-логикой приложения и слоем персистентности (доступом к данным), скрывая детали взаимодействия с БД.
Проблема Без Repository
❌ Неправильно: Прямой доступ к БД
@Service
public class UserService {
@Autowired
private DataSource dataSource;
public User getUserById(Long id) {
// ПЛОХО: SQL код прямо в сервис
try (Connection conn = dataSource.getConnection()) {
String sql = "SELECT id, name, email FROM users WHERE id = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setLong(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return new User(rs.getLong(1), rs.getString(2), rs.getString(3));
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public void saveUser(User user) {
// ПЛОХО: Смешивание бизнес-логики с SQL
try (Connection conn = dataSource.getConnection()) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, user.getName());
stmt.setString(2, user.getEmail());
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public List<User> searchByEmail(String email) {
// ПЛОХО: Ещё больше SQL кода
// ...
}
}
// Проблемы:
// - UserService смешивает бизнес-логику и доступ к БД
// - Тяжело тестировать (нужна реальная БД)
// - Сложно менять реализацию (например, с MySQL на PostgreSQL)
// - SQL код дублируется в разных местах
// - Нарушен принцип Single Responsibility
Решение: Repository Паттерн
✅ Правильно: Repository абстракция
// 1. Интерфейс Repository (контракт)
public interface UserRepository {
User findById(Long id);
void save(User user);
void update(User user);
void delete(Long id);
List<User> findAll();
List<User> findByEmail(String email);
}
// 2. Реализация Repository (детали БД)
@Repository
public class UserRepositoryImpl implements UserRepository {
@Autowired
private DataSource dataSource;
@Override
public User findById(Long id) {
// SQL код ТОЛЬКО здесь
try (Connection conn = dataSource.getConnection()) {
String sql = "SELECT id, name, email FROM users WHERE id = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setLong(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return new User(rs.getLong(1), rs.getString(2), rs.getString(3));
}
} catch (SQLException e) {
throw new RuntimeException("Failed to find user", e);
}
return null;
}
@Override
public void save(User user) {
try (Connection conn = dataSource.getConnection()) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, user.getName());
stmt.setString(2, user.getEmail());
stmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException("Failed to save user", e);
}
}
@Override
public List<User> findByEmail(String email) {
List<User> users = new ArrayList<>();
try (Connection conn = dataSource.getConnection()) {
String sql = "SELECT id, name, email FROM users WHERE email = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, email);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
users.add(new User(rs.getLong(1), rs.getString(2), rs.getString(3)));
}
} catch (SQLException e) {
throw new RuntimeException("Failed to find users by email", e);
}
return users;
}
}
// 3. Сервис использует Repository (чистая бизнес-логика)
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUserById(Long id) {
// ХОРОШО: Бизнес-логика без SQL
return userRepository.findById(id);
}
public void registerUser(String name, String email) {
// ХОРОШО: Валидация и бизнес-логика
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Name cannot be empty");
}
List<User> existing = userRepository.findByEmail(email);
if (!existing.isEmpty()) {
throw new UserAlreadyExistsException("User with email already exists");
}
User user = new User(name, email);
userRepository.save(user);
}
public void updateUserEmail(Long userId, String newEmail) {
User user = userRepository.findById(userId);
if (user == null) {
throw new UserNotFoundException("User not found");
}
// Валидация
if (!isValidEmail(newEmail)) {
throw new InvalidEmailException("Invalid email format");
}
user.setEmail(newEmail);
userRepository.update(user);
}
private boolean isValidEmail(String email) {
return email.contains("@") && email.contains(".");
}
}
// 4. Контроллер использует Сервис
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(new UserDTO(user));
}
@PostMapping
public ResponseEntity<UserDTO> registerUser(@RequestBody RegisterRequest request) {
try {
userService.registerUser(request.getName(), request.getEmail());
return ResponseEntity.status(HttpStatus.CREATED).build();
} catch (UserAlreadyExistsException e) {
return ResponseEntity.badRequest().build();
}
}
}
Spring Data JPA Repository (Упрощение)
JPA Repository (БЕЗ реализации)
// Spring Data автоматически реализует Repository!
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// CRUD операции встроены:
// findById(), save(), delete(), findAll()
// Кастомные методы (Spring Data сам создаст SQL)
List<User> findByEmail(String email);
List<User> findByNameContaining(String name);
Optional<User> findByEmailAndName(String email, String name);
}
// Сервис использует JPA Repository
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow();
}
public List<User> searchByEmail(String email) {
return userRepository.findByEmail(email);
}
}
Преимущества Repository Паттерна
1. Разделение Слоёв (Separation of Concerns)
┌─────────────────────┐
│ Presentation │ (Контроллеры, Views)
├─────────────────────┤
│ Business Logic │ (Сервисы) — использует Repository
├─────────────────────┤
│ Data Access │ (Repository) — скрывает как получаются данные
├─────────────────────┤
│ Persistence │ (БД)
└─────────────────────┘
Каждый слой отвечает за свою задачу
2. Тестируемость
@RunWith(SpringRunner.class)
public class UserServiceTest {
@InjectMocks
private UserService userService;
@Mock
private UserRepository userRepository; // Mock вместо реальной БД
@Test
public void testGetUserById() {
// Arrange
User mockUser = new User(1L, "John", "john@mail.com");
when(userRepository.findById(1L)).thenReturn(mockUser);
// Act
User result = userService.getUserById(1L);
// Assert
assertEquals("John", result.getName());
// Тест БЕЗ реальной БД!
}
@Test
public void testRegisterUserDuplicate() {
// Arrange
List<User> existingUsers = new ArrayList<>();
existingUsers.add(new User(1L, "John", "john@mail.com"));
when(userRepository.findByEmail("john@mail.com")).thenReturn(existingUsers);
// Act & Assert
assertThrows(UserAlreadyExistsException.class,
() -> userService.registerUser("John Doe", "john@mail.com"));
}
}
3. Простота Изменения Реализации
// Если менять БД с MySQL на MongoDB:
// Было (JDBC)
@Repository
public class UserRepositoryJdbc implements UserRepository { }
// Стало (MongoDB)
@Repository
public class UserRepositoryMongo implements UserRepository {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public User findById(Long id) {
return mongoTemplate.findById(id, User.class);
}
}
// Сервис НЕ меняется! Используется один интерфейс
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // Работает с любой реализацией
}
4. Переиспользование Кода
// Один Repository используется всеми сервисами
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
@Service
public class AuthService {
@Autowired
private UserRepository userRepository; // Переиспользуем
}
@Service
public class NotificationService {
@Autowired
private UserRepository userRepository; // Переиспользуем
}
5. Аудит и Логирование
@Repository
public class UserRepositoryAudited implements UserRepository {
@Autowired
private UserRepository delegate;
@Autowired
private AuditLogger auditLogger;
@Override
public User findById(Long id) {
auditLogger.log("SELECT", "users", "id=" + id);
return delegate.findById(id);
}
@Override
public void save(User user) {
auditLogger.log("INSERT", "users", user.toString());
delegate.save(user);
}
}
Repository vs DAO
Repository:
- Работает с агрегатами (Domain Driven Design)
- Коллекционная семантика
- Высокоуровневая абстракция
- Предпочитаемый в современных приложениях
DAO (Data Access Object):
- Работает с данными (CRUD)
- Низкоуровневая абстракция
- Более старый паттерн
- Всё ещё используется, но Repository предпочтительнее
Best Practices
public class RepositoryBestPractices {
// ✅ Repository использует БИЗНЕС язык
// ❌ Не repository.findByColumnName("value");
// ✅ repository.findActiveUsersInCity("New York");
// ✅ Repository возвращает доменные объекты (сущности)
// ❌ Не repository.findUserDTO();
// ✅ User user = repository.findById(1L);
// ✅ Repository содержит методы ПОИСКА и СОХРАНЕНИЯ
// ❌ Не repository.sendEmail();
// ❌ Не repository.performComplexCalculation();
// ✅ Используй Optional для методов, которые могут вернуть null
Optional<User> findById(Long id);
// ✅ Используй Collections для множественных результатов
List<User> findAll();
// ✅ Кешируй результаты если часто используются
@Cacheable("users")
List<User> findAll();
}
Repository с Pagination и Sorting
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
// Spring Data автоматически добавит поддержку Page и Sort
Page<User> findAll(Pageable pageable);
Page<User> findByNameContaining(String name, Pageable pageable);
}
// Использование
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public Page<User> getUsers(int page, int size, String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return userRepository.findAll(pageable);
}
public Page<User> searchUsers(String query, int page, int size) {
Pageable pageable = PageRequest.of(page, size);
return userRepository.findByNameContaining(query, pageable);
}
}
Заключение
Repository паттерн нужен для:
- Абстракции слоя персистентности (отделение БД от бизнес-логики)
- Упрощения тестирования (легко создавать mock'и)
- Переиспользования кода (один Repository для многих сервисов)
- Гибкости реализации (легко менять БД)
- Читаемости кода (сервисы содержат только бизнес-логику)
Правило: Всегда используй Repository между бизнес-логикой и слоем БД. Это проверенный паттерн, который делает код чище и тестируемее.