Spring @Transactional только для чтения - PullRequest
54 голосов
/ 23 октября 2009

Я экспериментирую с использованием шаблона команды, чтобы мой веб-слой мог работать с сущностями Hibernate в контексте одной транзакции (таким образом избегая исключений при отложенной загрузке). Однако сейчас я не совсем понимаю, как мне поступать с транзакциями.

Мои команды вызывают методы уровня обслуживания, которые снабжены аннотациями @Transactional. Некоторые из этих методов уровня обслуживания доступны только для чтения, например @Transactional(readOnly=true) - и некоторые из них для чтения / записи.

Мой уровень службы предоставляет обработчик команд, который выполняет команды, переданные ему от имени веб-уровня.

@Transactional
public Command handle( Command cmd ) throws CommandException

Полагаю, я прав, сделав метод обработчика команд handle() транзакционным. Именно здесь возникает путаница. Если реализация команды вызывает несколько методов уровня обслуживания, обработчик команд не может узнать, будут ли операции, вызываемые внутри команды, доступны только для чтения, чтения / записи или комбинации из двух.

Я не понимаю, как в этом примере работает распространение. Если бы я должен был создать метод handle() readOnly=true, что произойдет, если команда затем вызовет метод уровня обслуживания, который помечен @Transactional(realOnly=false)?

Буду признателен за лучшее понимание этого и приветствую ваши комментарии ...

Andrew

Ответы [ 4 ]

65 голосов
/ 11 ноября 2009

Прежде всего, поскольку Spring сам по себе не выполняет сохранение, он не может указать, что именно должно означать readOnly. Этот атрибут является только подсказкой для провайдера, поведение в данном случае зависит от Hibernate.

Если вы укажете readOnly как true, режим сброса будет установлен как FlushMode.NEVER в текущем сеансе Hibernate, чтобы сеанс не завершал транзакцию.

Кроме того, setReadOnly (true) будет вызываться при соединении JDBC, что также является подсказкой для базовой базы данных. Если ваша база данных поддерживает это (скорее всего, так и есть), это в основном имеет тот же эффект, что и FlushMode.NEVER, но оно сильнее, поскольку вы даже не можете выполнить сброс вручную.

Теперь посмотрим, как работает распространение транзакций.

Если вы явно не установите readOnly на true, у вас будут транзакции чтения / записи. В зависимости от атрибутов транзакции (например, REQUIRES_NEW) иногда ваша транзакция в какой-то момент приостанавливается, запускается новая и в конечном итоге фиксируется, а после этого первая транзакция возобновляется.

Хорошо, мы почти у цели. Давайте посмотрим, что приводит readOnly в этот сценарий.

Если метод в транзакции read / write вызывает метод, требующий транзакции readOnly , первая должна быть приостановлена, так как в противном случае сброс / фиксация произойдет в конец второго метода.

И наоборот, если вы вызываете метод из транзакции readOnly , которая требует чтение / запись , снова первая будет приостановлена, поскольку она не может быть очищена / зафиксирована, и второй метод нуждается в этом.

В случаях readOnly-to-readOnly и read / write-to-read / write внешняя транзакция не должна быть приостановлена ​​(если не указано распространение в противном случае, очевидно).

18 голосов
/ 31 октября 2013

Вызов readOnly = false из readOnly = true не работает, поскольку предыдущая транзакция продолжается.

В вашем примере метод handle () на вашем сервисном уровне запускает новую транзакцию чтения-записи. Если метод handle в свою очередь вызывает сервисные методы, аннотируемые только для чтения, только для чтения не будет действовать, поскольку вместо этого они будут участвовать в существующей транзакции чтения-записи.

Если важно, чтобы эти методы были доступны только для чтения, вы можете аннотировать их с помощью Propagation.REQUIRES_NEW, и они затем начнут новую транзакцию только для чтения, а не будут участвовать в существующей транзакции чтения-записи.

Вот рабочий пример, CircuitStateRepository - это хранилище данных Spring-JPA.

BeanS вызывает транзакцию = только для чтения Bean1, которая выполняет поиск, и вызывает транзакцию = чтение-запись, Bean2, которая сохраняет новый объект.

  • Bean1 начинает передачу только для чтения.

31 09: 39: 44.199 [pool-1-thread-1] DEBUG osorm.jpa.JpaTransactionManager - Создание новой транзакции с именем [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT , только для чтения; ''

  • В нем участвует Бин 2.

    31 09: 39: 44.230 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Участие в существующей транзакции

    Ничего не передано в базу данных.

Теперь измените аннотацию Bean2 @Transactional, добавив propagation=Propagation.REQUIRES_NEW

  • Bean1 запускает передачу только для чтения.

    31 09: 31: 36.418 [pool-1-thread-1] DEBUG osorm.jpa.JpaTransactionManager - Создание новой транзакции с именем [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT , только для чтения; ''

  • Bean2 начинает новую операцию чтения-записи

    31 09: 31: 36.449 [pool-1-thread-1] DEBUG osorm.jpa.JpaTransactionManager - Приостановка текущей транзакции, создание новой транзакции с именем [nz.co.vodafone.wcim.business.Bean2.createSomething]

И изменения, внесенные Bean2, теперь сохраняются в базе данных.

Вот пример, протестированный с использованием данных spring, hibernate и oracle.

@Named
public class BeanS {    
    @Inject
    Bean1 bean1;

    @Scheduled(fixedRate = 20000)
    public void runSomething() {
        bean1.startSomething();
    }
}

@Named
@Transactional(readOnly = true)
public class Bean1 {    
    Logger log = LoggerFactory.getLogger(Bean1.class);

    @Inject
    private CircuitStateRepository csr;

    @Inject
    private Bean2 bean2;

    public void startSomething() {    
        Iterable<CircuitState> s = csr.findAll();
        CircuitState c = s.iterator().next();
        log.info("GOT CIRCUIT {}", c.getCircuitId());
        bean2.createSomething(c.getCircuitId());    
    }
}

@Named
@Transactional(readOnly = false)
public class Bean2 {    
    @Inject
    CircuitStateRepository csr;

    public void createSomething(String circuitId) {
        CircuitState c = new CircuitState(circuitId + "-New-" + new DateTime().toString("hhmmss"), new DateTime());

        csr.save(c);
     }
}
11 голосов
/ 03 января 2010

По умолчанию распространение транзакции ТРЕБУЕТСЯ, что означает, что та же транзакция будет распространяться от транзакционного вызывающего абонента до транзакционного вызываемого абонента. В этом случае также будет доступен статус только для чтения. Например. если транзакция только для чтения вызовет транзакцию чтения-записи, вся транзакция будет доступна только для чтения.

Не могли бы вы использовать шаблон Open Session in View, чтобы разрешить отложенную загрузку? Таким образом, ваш метод дескриптора вообще не должен быть транзакционным.

5 голосов
/ 22 ноября 2013

Кажется, что игнорируются настройки для текущей активной транзакции, настройки применяются только к новой транзакции:

org.springframework.transaction.PlatformTransactionManager
TransactionStatus getTransaction(TransactionDefinition definition)
                         throws TransactionException
Return a currently active transaction or create a new one, according to the specified propagation behavior.
Note that parameters like isolation level or timeout will only be applied to new transactions, and thus be ignored when participating in active ones.
Furthermore, not all transaction definition settings will be supported by every transaction manager: A proper transaction manager implementation should throw an exception when unsupported settings are encountered.
An exception to the above rule is the read-only flag, which should be ignored if no explicit read-only mode is supported. Essentially, the read-only flag is just a hint for potential optimization.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...