Как вы реорганизуете метод @Transactional, чтобы разделить нетранзакционные части

У меня есть класс доступа к данным, который работает как часть автономного java-приложения. В настоящее время он работает, что означает, что диспетчер транзакций определен, но я хочу реорганизовать класс, чтобы уменьшить объем транзакции, но если я это сделаю, я получаю org.hibernate.HibernateException: No Hibernate Session привязан к потоку и конфигурации не позволяет создавать нетранзакционные здесь, что подразумевает, что перемещение @Transactional каким-то образом остановило его распознавание.

В моей исходной версии методы рефакторинга были закрытыми, но я нашел рекомендацию изменить их на общедоступные, поскольку в некоторых случаях аннотация не будет отображаться.

public class DoStuff {
    @Transactional
    public void originalMethod() {
        // do database stuff
        ...

        // do non-database stuff that is time consuming
        ...
    }
}

То, что я хочу сделать, это реорганизовать следующее

public class DoStuff {
    public void originalMethod() {
        doDatabaseStuff()

        doNonDatabaseStuff()
    }

    @Transactional
    public void doDatabaseStuff() {
        ...
    }

    public void doNonDatabaseStuff() {
        ...
    }
}

person Michael Rutherfurd    schedule 23.08.2012    source источник
comment
Где ваш менеджер по сделкам? Можете ли вы добавить больше деталей?   -  person Chris    schedule 23.08.2012
comment
Реализует ли ваш класс DoStuff какие-либо интерфейсы?   -  person Alex Barnes    schedule 23.08.2012
comment
Никаких интерфейсов, и менеджер транзакций определяется так, как он работает для исходного класса.   -  person Michael Rutherfurd    schedule 24.08.2012


Ответы (2)


Изменить:

Вам необходимо понимать, как работает проксирование Spring понять, почему ваш рефакторинг не работает.

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

@Transactional использует Spring AOP, Spring использует прокси. Это означает, что когда вы вызываете метод @Transactional из другого класса, Spring будет использовать прокси, поэтому будут применяться рекомендации по транзакциям. Однако, если вы вызовете метод из того же класса, Spring будет использовать ссылку «this» вместо прокси-сервера, поэтому рекомендации по транзакциям не будут применяться.

Исходный ответ:

Вот что сработало для меня в подобном сценарии.

public class DoStuff implement ApplicationContextAware {    
private ApplicationContext CONTEXT;
public void setApplicationContext(ApplicationContext context) throws BeansException {
    CONTEXT = context;
}

    public void originalMethod() {           
        getSpringProxy().doDatabaseStuff()              
        doNonDatabaseStuff()       
    }

    private DoStuff getSpringProxy() {
        return context.getBean(this.getClass());     
    } 
    @Transactional       
    public void doDatabaseStuff() {           
        ...       
    }          

    public void doNonDatabaseStuff() {           
        ...       
    }   
} 

Объяснение:

  1. Сделайте класс ApplicationContextAware, чтобы он имел ссылку на контекст
  2. Когда вам нужно вызвать транзакционный метод, извлеките фактический прокси-сервер Spring из контекста.
  3. Используйте этот прокси для вызова вашего метода, чтобы @Transactional действительно применялся.
person gresdiplitude    schedule 23.08.2012
comment
Исходный класс уже распознает @Transactional и работает корректно. Он перестает работать только после рефакторинга. - person Michael Rutherfurd; 24.08.2012
comment
Кажется, это моя проблема. Спасибо - person Michael Rutherfurd; 24.08.2012

Похоже, ваш подход должен работать нормально, я полагаю, что проблема связана с прокси-серверами Spring.

Причина, по которой я спросил об интерфейсах, связана с методом по умолчанию, с помощью которого Spring применяет транзакционное поведение — динамические прокси JDK.

Если фактическое определение вашего класса:

public class DoStuff implements Doable {
  public void originalMethod() {

  }
}

public interface Doable {
  public void originalMethod();
}

Если это действительно структура, при переходе на новую структуру Spring не сможет проксировать новый метод doDatabaseStuff.

Ваши варианты, чтобы исправить это:

  • Добавьте новые методы в свой интерфейс, чтобы гарантировать, что Spring может их проксировать.
  • Перейти к использованию прокси на основе CGLIB (они не зависят от интерфейсов)
person Alex Barnes    schedule 23.08.2012
comment
Нет, класс не является POJO, интерфейсы вообще не реализованы. - person Michael Rutherfurd; 24.08.2012
comment
Ну что ж. Все еще. Все верно :) - person Alex Barnes; 24.08.2012
comment
Я не возражал, я (просто сказал, что в данном случае это неприменимо :-) - person Michael Rutherfurd; 24.08.2012