Как получить соединение внутри транзакции Spring?

Представьте себе этот код:

foo() {
     Connection conn = ...;
}

foo() был вызван из метода с аннотацией @Transactional. Как получить текущее соединение JDBC? Обратите внимание, что foo() находится в bean-компоненте (поэтому он может иметь @Autowired полей), но foo() не может иметь параметров (поэтому я не могу откуда-то передать соединение).

[EDIT] Я использую jOOQ, для которого требуется либо источник данных, либо соединение. Моя проблема: я не знаю, какой менеджер транзакций настроен. Это может быть что угодно; Java EE, основанный на DataSource, что-то, что получает источник данных через JNDI. Мой код не приложение, это библиотека. Мне нужно проглотить то, что другие положили на мою тарелку. Точно так же я не могу запросить фабрику сеансов Hibernate, потому что приложение, использующее меня, может не использовать Hibernate.

Но я знаю, что другой код, например интеграция Spring Hibernate, каким-то образом может получить текущее соединение от диспетчера транзакций. Я имею в виду, что Hibernate не поддерживает диспетчер транзакций Spring, поэтому связующий код должен адаптировать Spring API к тому, что ожидает Hibernate. Мне нужно сделать то же самое, но я не мог понять, как это работает.

[EDIT2] Я знаю, что есть активная транзакция (т.е. у Spring где-то есть экземпляр Connection или, по крайней мере, диспетчер транзакций, который может его создать), но мой метод не @Transactional. Мне нужно вызвать конструктор, который принимает java.sql.Connection в качестве параметра. Что я должен делать?


person Aaron Digulla    schedule 02.08.2012    source источник


Ответы (6)


(полностью переписано на основе ветки комментариев; не знаю, почему мой первоначальный ответ был сосредоточен на Hibernate, кроме того, с чем я сейчас работаю)

Менеджер транзакций полностью ортогонален источникам данных. Некоторые менеджеры транзакций взаимодействуют непосредственно с источниками данных, некоторые взаимодействуют через промежуточный уровень (например, Hibernate), а некоторые взаимодействуют через службы, предоставляемые контейнером (например, JTA).

Когда вы помечаете метод как @Transactional, все это означает, что Spring будет генерировать прокси при загрузке вашего компонента, и этот прокси будет передан любому другому классу, который захочет использовать ваш компонент. Когда вызывается метод прокси, он (прокси) просит диспетчера транзакций либо дать ему незавершенную транзакцию, либо создать новую. Затем он вызывает ваш фактический метод компонента. Когда ваш метод bean возвращается, прокси-сервер снова взаимодействует с менеджером транзакций, чтобы либо сказать «я могу зафиксировать», либо «я должен откатиться». В этом процессе есть повороты; например, транзакционный метод может вызывать другой транзакционный метод и совместно использовать ту же транзакцию.

Хотя менеджер транзакций взаимодействует с DataSource, он не владеет DataSource. Вы не можете попросить менеджера транзакций дать вам соединение. Вместо этого вы должны внедрить специфичный для фрейма объект, который будет возвращать соединения (например, Hibernate SessionFactory). В качестве альтернативы вы можете использовать статические служебные классы с поддержкой транзакций, но они снова привязаны к определенной структуре.

person parsifal    schedule 02.08.2012
comment
Но что мне делать, когда я пишу библиотеку, которую хочет использовать какое-то неизвестное приложение? Я хочу написать свою собственную SessionFactory. Как это работает, когда вам нужно реализовать это самостоятельно? - person Aaron Digulla; 02.08.2012
comment
Прочитав некоторые из ваших комментариев, я собираюсь повторить то, что сказали другие авторы: попытка получить ваш источник данных из менеджера транзакций — это неправильный способ сделать что-то, и он просто запутает любого, кто читает ваш код. Чтобы процитировать документы Spring: как правило, приложения будут работать либо с TransactionTemplate, либо с декларативным разграничением транзакций через AOP. Отмечая свой метод @Transactional, вы делаете последнее. - person parsifal; 02.08.2012
comment
Если вы планируете написать свою собственную SessionFactory — что не было задано в вашем вопросе — вам необходимо внимательно изучить исходный код Hibernate и Spring и понять, как они взаимодействуют. На самом деле, начните с разделов справочного руководства Spring по управлению транзакциями, потому что я думаю, что у вас неправильное представление о том, как работают транзакции Spring. - person parsifal; 02.08.2012
comment
Ну, то, что мне нужно, очень просто, но Spring просто не позволит мне это сделать. Я знаю, что есть активная транзакция, но мой метод не @Transactional. Мне нужно создать объект, который принимает Connection в качестве параметра конструктора. Что я должен делать? - person Aaron Digulla; 02.08.2012
comment
После прочтения редактирования вашего вопроса я убежден, что у вас неправильное представление о том, как работают транзакции Spring. Менеджер транзакций любого типа работает с источником данных. Он не владеет источником данных. Если вы не используете Hibernate (не знаю, почему я так думал), следуйте совету Nandkumar и внедрите DataSource. - person parsifal; 02.08.2012
comment
Звучит хорошо, но что я буду делать, когда пользователь использует JtaTransactionManager? Кажется, что это не использует источник данных (хотя я не могу представить, как это работает...) - person Aaron Digulla; 02.08.2012
comment
Прошу прощения за прямоту, но вам нужно кое-что понять: Пометка метода как @Transactional не означает, что вы получаете диспетчер транзакций. Это означает, что Spring создаст прокси-класс, взаимодействующий с менеджером транзакций. Если вы хотите получить что-нибудь, вам нужно явно внедрить это. - person parsifal; 02.08.2012
comment
А в справочном руководстве Spring есть пример, в котором используется менеджер транзакций JTA. Обратите внимание, что в примере определяется источник данных. Этот источник данных необходимо внедрить во все, что хочет получить доступ к данным. - person parsifal; 02.08.2012
comment
Хорошо, пример с JTA облегчил понимание. У меня есть TxManager, и он общается с фабрикой сеансов, которая, в свою очередь, знает об источнике данных. Таким образом, все, что когда-либо делает TxManager, — это сообщает всем фабрикам сеансов, когда создавать/сбрасывать соединения, потому что новая транзакция была запущена. Это более-менее правильно? - person Aaron Digulla; 02.08.2012
comment
Это правильно. И он решает, когда это сделать, в зависимости от того, где размещены ваши аннотации Transactional. Когда метод вызывается с помощью Transactional, вы фактически вызываете прокси-класс, который проверяет вызываемый метод на наличие аннотаций и выполняет соответствующее управление транзакциями до/после вызова метода. - person Matt; 02.08.2012
comment
@parsifal действительно отличное объяснение ... :) - person Nandkumar Tekale; 20.09.2013

Вероятно, вы можете попробовать DataSourceUtils.getConnection(dataSource) в соответствии с API, он должен вернуть вам текущее соединение с источником данных.

Обновление: на основе ваших комментариев и исходного кода для org.springframework.transaction.support.TransactionSynchronizationManager :

Как я уже сказал, ключом к получению соединения является имя источника данных, если его невозможно получить, один из выходов, просмотрев исходный код, — попробовать это:

TransactionSynchronizationManager.getResourceMap() вернет карту источника данных в ConnectionHolder в текущем потоке, предполагая, что у вас есть только 1 ресурс, участвующий в транзакции, вы, вероятно, можете сделать map.values().get(0), чтобы получить первый ConnectionHolder, из которого вы можете получить соединение, вызвав .getConnection()

Итак, по существу, вызывая следующее:

TransactionSynchronizationManager.getResourceMap().values().get(0).getConnection()

Хотя, наверное, должен быть лучший способ :-)

person Biju Kunjummen    schedule 02.08.2012
comment
Поскольку в транзакции может быть несколько источников данных, источник данных является ключом к извлечению соединения из локального потока, содержащего детали транзакции - вы должны иметь возможность внедрить соответствующий источник данных в этот класс? - person Biju Kunjummen; 02.08.2012
comment
Пожалуйста, смотрите мои правки к моему вопросу. Я пишу библиотеку, а не приложение. Я получаю txManager и больше ничего. - person Aaron Digulla; 02.08.2012
comment
Убедитесь, что вы используете DataSourceUtils.getConnection(dataSource) внутри метода @Transactional, иначе Spring предоставит вам новое соединение, которое необходимо закрыть вручную, в этом случае Spring не сделает этого для вас. - person deFreitas; 23.03.2021

Я предполагаю, что вы используете Plain Jdbc, вам нужно сделать следующее:

BaseDao {
    @Autowired
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Connection getConnection() {
        // ....use dataSource to create connection
        return DataSourceUtils.getConnection(dataSource);
    }
}

FooDao extends BaseDao {
    // your foo() method
    void foo() {
       Connection conn = getConnection();
       //....
    }
}
person Nandkumar Tekale    schedule 02.08.2012
comment
Нет, я использую Spring TransactionManager. Где-то может быть источник данных, но я не могу на него рассчитывать. - person Aaron Digulla; 02.08.2012
comment
Где-то может быть источник данных, но я не могу на него рассчитывать... как же мы можем его сосчитать? Пожалуйста, предоставьте информацию о вашей конфигурации - person Nandkumar Tekale; 02.08.2012
comment
Глубоко внутри диспетчера транзакций должен быть способ установить соединение. Я не думаю, что, например, фабрика сеансов Spring Hibernate имеет специальную реализацию для каждого типа TransactionManager, но я не могу понять, как они работают вместе :-( - person Aaron Digulla; 02.08.2012
comment
вы можете получить источник данных из диспетчера транзакций, но не можете проверить API static.springsource.org/spring/docs/3.0.x/javadoc-api/org/. Совершенно неверная идея, я думаю. - person Nandkumar Tekale; 02.08.2012
comment
даже если вы используете hibernateTransactionManager. - person Nandkumar Tekale; 02.08.2012
comment
Пожалуйста, смотрите мои правки к моему вопросу. Я пишу библиотеку, а не приложение. Я получаю txManager и больше ничего. - person Aaron Digulla; 02.08.2012

Что немного раздражает во всем этом, так это то, что документы Spring написаны в основном на маркетинговом языке, который скрывает уродство за кулисами.

Ясными словами:

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

Итак, то, что Spring делает довольно сложным способом, - это хранить ваши данные локально в вашем текущем потоке выполнения, что является тривиальной задачей, но недостаточно четко повторяется в документации Spring. Spring в основном помещает ваши вещи в «глобальный контекст», чтобы не тянуть их через все ваши интерфейсы и определения методов. Сначала это выглядит немного волшебно, но на самом деле это просто макияж.

Таким образом, вы получаете статический вызов метода DataSourceUtils для извлечения данных.

person user1050755    schedule 07.08.2012
comment
Хранение таких объектов, как источник данных, в переменной threadlocal кажется уродливым, но это, вероятно, единственный способ передать информацию в конкретном потоке выполнения. В типичной двухфазной фиксации, включающей несколько источников данных, способ работы диспетчера транзакций (обычно предоставляемый серверами приложений, но может быть и автономным) заключается в связывании объекта javax.transaction.Transaction с потоком, а затем каждый источник данных зачисляется с помощью enlistresource. метод объекта транзакции. Таким образом, эта модель использования довольно распространена. - person Shailendra; 02.04.2013

Если вы используете Spring Transaction с JDBC, настройте JdbcTemplate и получите доступ к текущей транзакции, используя JdbcTemplate.execute(ConnectionCallback). Это стандартный способ доступа к соединению, настроенному Spring.

person Amir Pashazadeh    schedule 02.08.2012
comment
Как получить JdbcTemplate из Spring TransactionManager? - person Aaron Digulla; 02.08.2012
comment
новый JdbcTemplate (источник данных); Менеджер транзакций Spring не заботится о JdbcTemplate. Однако JdbcTemplate подключится к DataSourceUtils выше, чтобы использовать соединение, соответствующее источнику данных, указанному в конструкторе. - person Matt; 02.08.2012

3 других способа:

@Component
public class MyServiceNonTransactional {

    @Autowired
    private TransactionTemplate transactionTemplate;
    
    @Autowired
    private DataSource dataSource;
    
    public void doStuff() {
        transactionTemplate.executeWithoutResult(status -> {
            Connection connection = DataSourceUtils.getConnection(dataSource);
            // here we go...
        });
    }

}

@Service
public class MyServiceTransactional {

    @Autowired
    private DataSource dataSource;
    
    @Transactional
    public void doStuff() {
        Connection connection = DataSourceUtils.getConnection(dataSource);
        // here we go...
    }

}

@Service
public class MyServiceViaJdbc {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void doStuff() {
        jdbcTemplate.execute((ConnectionCallback<Void>) conn -> {
            // "conn" here we go!
            return null;
        });
    }
        
}
person Beto Neto    schedule 22.07.2020
comment
Это должен быть принятый ответ. - person sakra; 22.02.2021