← Назад к вопросам
Как работает transactional read-only?
2.3 Middle🔥 161 комментариев
#ORM и Hibernate#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как работает @Transactional(readOnly = true)
Аннотация @Transactional(readOnly = true) — это оптимизация для методов, которые только читают данные без модификации. Это дает различные преимущества на уровне БД и приложения.
1. Основная концепция
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// Обычная транзакция
@Transactional
public User createUser(String name) {
User user = new User(name);
userRepository.save(user); // INSERT в БД
return user;
}
// Read-only транзакция
@Transactional(readOnly = true)
public User findUser(Long id) {
return userRepository.findById(id).orElse(null); // SELECT из БД
}
}
2. Что происходит с readOnly = true
2.1 На уровне БД
// При readOnly = true Spring/Hibernate:
// - Устанавливает Connection в READ_ONLY режим
// - Начинает READ_ONLY транзакцию на БД
// - Оптимизирует блокировки (нет ненужных блокировок)
@Transactional(readOnly = true)
public List<User> getAllUsers() {
// Эквивалент SQL:
// SET TRANSACTION READ ONLY; -- на некоторых БД
return userRepository.findAll();
}
2.2 На уровне Hibernate
// Hibernate отключает некоторые оптимизации для write операций
@Transactional(readOnly = true)
public User getUser(Long id) {
// Hibernate:
// - Не отслеживает изменения сущности
// - Не создает снимки объектов для dirty checking
// - Использует другой режим загрузки (READ режим)
// - Может использовать различные стратегии кэширования
return userRepository.findById(id).orElse(null);
}
3. Различие: readOnly = true vs readOnly = false
@Service
public class UserService {
@Autowired
private UserRepository repository;
// readOnly = false (по умолчанию)
@Transactional
public User updateUser(Long id, String newName) {
User user = repository.findById(id).orElse(null);
if (user != null) {
user.setName(newName);
// UPDATE automatically
}
return user; // Hibernate update
}
// readOnly = true
@Transactional(readOnly = true)
public User getUser(Long id) {
User user = repository.findById(id).orElse(null);
if (user != null) {
user.setName("New Name"); // Изменение НЕ сохранится
}
return user; // Никакого UPDATE
}
}
// Тест
public void demonstrateDifference() {
User user = getUser(1L); // readOnly = true
System.out.println(user.getName()); // "Original Name"
// изменение было потеряно!
user = updateUser(1L, "New Name"); // readOnly = false
System.out.println(user.getName()); // "New Name"
// изменение было сохранено
}
4. Производительность: readOnly = true
// ПЛОХО: readOnly = false для чтения
@Transactional(readOnly = false)
public List<User> getAllUsers() {
// Spring создает объекты для отслеживания изменений
// Hibernate создает снимки всех объектов
// Больше памяти и CPU
// Время: 500ms для 10000 записей
return userRepository.findAll();
}
// ХОРОШО: readOnly = true для чтения
@Transactional(readOnly = true)
public List<User> getAllUsers() {
// Spring оптимизирует под читаемые операции
// Hibernate не отслеживает изменения
// Меньше памяти и CPU
// Время: 50ms для 10000 записей (в 10 раз быстрее!)
return userRepository.findAll();
}
5. Примеры использования
5.1 DAO/Repository слой
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Явно помечаем read-only методы
@Transactional(readOnly = true)
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
@Transactional(readOnly = true)
@Query("SELECT u FROM User u WHERE u.role = :role")
List<User> findByRole(@Param("role") String role);
}
5.2 Service слой
@Service
public class ReportService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
// Генерация отчёта (только чтение)
@Transactional(readOnly = true)
public ReportDTO generateMonthlyReport(int month) {
List<User> users = userRepository.findAll();
List<Order> orders = orderRepository.findByMonth(month);
return new ReportDTO(
users.size(),
orders.size(),
orders.stream()
.mapToDouble(Order::getAmount)
.sum()
);
}
// Поиск с фильтрацией
@Transactional(readOnly = true)
public List<UserDTO> searchUsers(String query) {
return userRepository.findAll().stream()
.filter(u -> u.getName().contains(query))
.map(u -> new UserDTO(u.getId(), u.getName(), u.getEmail()))
.collect(Collectors.toList());
}
}
6. Транзакции с разными изоляциями
import org.springframework.transaction.annotation.Isolation;
@Service
public class UserService {
// Чтение с наименьшей изоляцией (быстро, но небезопасно)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public User getUserFast(Long id) {
// Может прочитать грязные данные (dirty read)
return userRepository.findById(id).orElse(null);
}
// Чтение с средней изоляцией
@Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED)
public User getUser(Long id) {
// Стандартный уровень
return userRepository.findById(id).orElse(null);
}
// Чтение с высокой изоляцией (безопасно, но медленно)
@Transactional(readOnly = true, isolation = Isolation.SERIALIZABLE)
public User getUserSafe(Long id) {
// Максимальная консистентность
return userRepository.findById(id).orElse(null);
}
}
7. Timeout для read-only транзакций
@Service
public class LongRunningReportService {
@Autowired
private ReportRepository reportRepository;
// Долгая операция чтения
@Transactional(readOnly = true, timeout = 300) // 5 минут
public ReportDTO generateComplexReport() {
// Если операция занимает > 5 минут, будет TimeoutException
List<Report> data = reportRepository.findAllWithDetails();
return buildReport(data);
}
}
8. Отключение изменения отслеживания
import org.hibernate.Session;
import javax.persistence.EntityManager;
@Service
public class PerformanceOptimizedService {
@Autowired
private EntityManager entityManager;
@Transactional(readOnly = true)
public List<User> getUsers() {
Session session = entityManager.unwrap(Session.class);
// Явно отключаем отслеживание изменений
session.setDefaultReadOnly(true);
List<User> users = session.createQuery("FROM User", User.class)
.setReadOnly(true) // еще более агрессивная оптимизация
.getResultList();
return users;
}
}
9. Распространённые ошибки
Ошибка 1: Забыли поставить readOnly = true
// ПЛОХО: читаем данные, но потребляем ресурсы как для write
@Transactional // readOnly = false по умолчанию
public List<User> getAllUsers() {
return userRepository.findAll();
}
// ХОРОШО
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return userRepository.findAll();
}
Ошибка 2: Попытка модифицировать в readOnly транзакции
@Transactional(readOnly = true)
public void updateUserInReadOnly(Long id) {
User user = userRepository.findById(id).orElse(null);
if (user != null) {
user.setName("New Name");
// TransactionRequiredException: No EntityManager with actual transaction
userRepository.save(user); // ОШИБКА!
}
}
10. Лучшие практики
@Service
public class BestPracticesService {
// ✓ 1. Помечайте read-only методы явно
@Transactional(readOnly = true)
public User getUser(Long id) {
return repository.findById(id).orElse(null);
}
// ✓ 2. Используйте readOnly на уровне Repository
// interface UserRepository extends JpaRepository<User, Long> {
// @Transactional(readOnly = true)
// List<User> findAll();
// }
// ✓ 3. Используйте правильный isolation level
@Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED)
public List<User> getActiveUsers() {
return repository.findByActiveTrue();
}
// ✓ 4. Не смешивайте чтение и запись в одном методе
// ПЛОХО:
// @Transactional(readOnly = true)
// public void processAndSave() {
// List<Data> data = read();
// save(data); // Ошибка!
// }
// ХОРОШО: разделите на два метода
@Transactional(readOnly = true)
public List<Data> readData() {
return repository.findAll();
}
@Transactional(readOnly = false)
public void saveData(List<Data> data) {
repository.saveAll(data);
}
// ✓ 5. Используйте @Transactional для batch операций
@Transactional(readOnly = true, batchSize = 50)
public void processBatch() {
// обработка большого объема данных
}
}
Резюме
readOnly = true:
- Оптимизирует операции чтения
- Отключает отслеживание изменений в Hibernate
- Устанавливает Connection в READ-ONLY на некоторых БД
- Быстрее и требует меньше памяти
- Предотвращает случайные модификации
Когда использовать:
- Запросы SELECT
- Отчёты и аналитика
- Поиск и фильтрация
- Любые операции только для чтения
Производительность: read-only может быть в 5-10 раз быстрее для больших наборов данных благодаря отключению overhead отслеживания изменений.