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

Какие знаешь случаи, когда Transactional метод может не сработать в Spring?

2.4 Senior🔥 131 комментариев
#Spring Boot и Spring Data#Spring Framework

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

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

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

Когда @Transactional не сработает в Spring

@Transactional — это мощный инструмент для управления транзакциями, но есть множество "подводных камней", которые приводят к тому, что транзакция не активируется. Это одна из самых частых ошибок в Spring приложениях.

1. Вызов через self-reference (внутри одного класса)

Это главная причина проблем. Spring создаёт прокси над бином, но если вызывать метод через this, прокси не применяется:

@Service
public class UserService {
    @Transactional
    public void createUser(String name) {
        saveUser(name);
    }
    
    @Transactional
    private void saveUser(String name) {
        // Эта транзакция не сработает!
        // Потому что createUser вызвал её через this
    }
}

Решение: извлечь в отдельный сервис или использовать ApplicationContext:

@Service
public class UserService {
    @Autowired
    private ApplicationContext context;
    
    @Transactional
    public void createUser(String name) {
        UserService proxy = context.getBean(UserService.class);
        proxy.saveUser(name); // Теперь работает через прокси
    }
    
    @Transactional
    public void saveUser(String name) {
        // Теперь сработает
    }
}

2. Метод вызывается из другого треда

Транзакция привязана к текущему потоку (ThreadLocal). Если запустить async операцию, транзакция не будет доступна в новом потоке:

@Service
public class UserService {
    @Transactional
    public void createUser(String name) {
        new Thread(() -> {
            saveToDatabase(name); // Нет транзакции в этом потоке!
        }).start();
    }
}

Решение: использовать @Async с правильной конфигурацией:

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public TaskExecutor taskExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
}

@Service
public class UserService {
    @Transactional
    @Async
    public void createUserAsync(String name) {
        saveToDatabase(name); // Теперь есть транзакция
    }
}

3. Метод не public

Spring создаёт прокси только для public методов. Private, protected и package-private методы не перехватываются:

@Service
public class UserService {
    @Transactional
    private void saveUser(String name) {
        // Транзакция НЕ активируется!
        database.insert(name);
    }
    
    @Transactional
    protected void updateUser(String name) {
        // Транзакция тоже не сработает
    }
}

4. Класс не является Spring бином

Если класс не управляется Spring контейнером (нет @Service, @Component и т.д.), прокси не будет создан:

public class UserService { // Ошибка! Нет аннотации
    @Transactional
    public void createUser(String name) {
        // Транзакция не сработает
    }
}

// Правильно:
@Service
public class UserService {
    @Transactional
    public void createUser(String name) {}
}

5. Использование CGLib вместо JDK прокси

Если класс не реализует интерфейс, Spring использует CGLib прокси, который может не перехватить вызовы:

// CGLib прокси может быть нестабильным
@Service
public class UserService { // Класс, а не интерфейс
    @Transactional
    public void createUser(String name) {}
}

// Лучше:
public interface IUserService {
    void createUser(String name);
}

@Service
public class UserService implements IUserService {
    @Transactional
    public void createUser(String name) {}
}

6. Неправильная конфигурация TransactionManager

Если TransactionManager не настроен или настроен неправильно, транзакции просто не будут работать:

@Configuration
public class DataSourceConfig {
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

7. Exception не вызывает rollback

По умолчанию @Transactional откатывает только RuntimeException. Если выбросить checked exception, коммит произойдёт:

@Transactional
public void createUser() throws IOException {
    saveUser();
    throw new IOException(); // Не откатит транзакцию!
}

// Правильно:
@Transactional(rollbackFor = Exception.class)
public void createUser() throws IOException {
    saveUser();
    throw new IOException(); // Теперь откатит
}

8. Использование @Transactional с readOnly=true

Если установить readOnly=true, попытка написать в БД может быть проигнорирована или вызвать ошибку:

@Transactional(readOnly = true)
public void createUser(String name) {
    database.insert(name); // Может быть проигнорировано!
}

Чек-лист перед использованием @Transactional

  • ✅ Метод public и находится в Spring бине
  • ✅ Вызов происходит через прокси (не через this)
  • ✅ TransactionManager правильно настроен
  • ✅ Exception правильно настроен (RuntimeException или rollbackFor)
  • ✅ Вызов в одном потоке (или @Async настроена)
  • ✅ БД поддерживает транзакции

Понимание этих тонкостей критично для надежной работы приложения.

Какие знаешь случаи, когда Transactional метод может не сработать в Spring? | PrepBro