Подключение к базе данных - должны ли они быть переданы в качестве параметра?

11

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

Я знаю, что есть некоторые инструменты ORM, чтобы сделать постоянство, но мы не можем вдаваться в подробности, пока ...

Любые отзывы приветствуются, спасибо.

ipohfly
источник
Какие проблемы вы имеете в виду? У кого есть эти сомнения? (Не вы, я полагаю.)
Грег Хьюгилл
1
Такие проблемы, как то, что разработчик забыл закрыть соединение, обычно я просто пытаюсь понять, является ли хорошей практикой передача соединения с базой данных различным методам в качестве параметра. Я сомневаюсь, что другой разработчик.
ipohfly

Ответы:

8

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

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

В C ++ вы защищены RAII, если вы размещаете в стеке или используете умные указатели. В C # сформулируйте жесткое правило, согласно которому все одноразовые объекты (например, соединения) должны быть объявлены в блоке «using». В Java очистите с помощью логики try-finally. Имейте обзоры кода на весь код уровня данных, чтобы гарантировать это.

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

Предположим, у нас есть много действий foobar (), которые можно комбинировать различными способами как атомарные транзакции.

//example in C#
//outer controlling block handles clean up via scoping with "using" blocks.
using (IDbConnection conn = getConn())
{
    conn.Open();
    using (IDbTransaction tran = conn.BeginTransaction())
    {
        try
        {//inner foobar actions just do their thing. They don't need to clean up.
            foobar1(tran);
            foobar2(tran);
            foobar3(tran);
            tran.Commit();
        }
        catch (Exception ex)
        { tran.Rollback(); }
    }
}//connection is returned to the pool automatically

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

КСТАТИ. В зависимости от того, поддерживает ли ваш язык функции первого класса, вы можете выполнить список действий foobar (). Таким образом, одна функция может обрабатывать все варианты действий. Устранение дублирования внешнего управляющего блока для каждой перестановки.

mike30
источник
помечая это как ответ, поскольку это дает мне больше представления о том, какова ситуация
ipohfly
6

Похоже, вы после инъекции зависимости . То есть пулное соединение создается один раз и вводится везде, где это необходимо. Конечно, передача соединения через параметр метода является одним из способов внедрения зависимости, но контейнер IoC, такой как Guice, PicoContainer или Spring, является другим (более безопасным) способом сделать это.

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

Spring JDBC и др. Являются другими примерами выполнения такого поведения для вас

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

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

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

Все это происходит из-за несоблюдения базы данных, соединения, оператора, набора результатов и их жизненных циклов.

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


источник
1
Соединения +1 дБ должны иметь максимально короткий промежуток времени. Откройте его, используйте его, закройте как можно быстрее. В настоящее время существует множество реализаций пула соединений, поэтому использование соединения для нескольких операций является ложной экономией. И приглашение для ошибок или проблем производительности (удержание блокировок на таблицах, использование ресурсов соединения)
jqa
Как называются некоторые из этих существующих моделей и структур?
Даниэль Каплан
@tieTYT Основными объектами, которые выходят на первый план, является объект доступа к данным, который служит для скрытия базы данных от остальной части приложения. См. Также Уровень доступа к данным и объектно-реляционное отображение
Когда я думаю об этих шаблонах, я чувствую, что они находятся на более высоком уровне абстракции, чем то, о чем он спрашивает. Допустим, у вас есть способ получить Rootот Дао. Но затем вы понимаете, что вы также хотите получить способ, Nodeне вытаскивая с собой весь Rootобъект. Как вы заставляете RootДао Nodeвызывать код Дао (то есть: повторное использование), но убедитесь, что NodeДао закрывает соединение только при Nodeпрямом вызове Дао, и сохраняет соединение открытым при Rootвызове Дао?
Даниэль Каплан
1
Просто хотел добавить, что если вы не находитесь в режиме автоматической фиксации, передача соединения может привести к ситуации, когда один объект обновляет базу данных, тогда другой (возможно, не связанный) объект получает соединение, имеет ошибку и завершает работу. откат изменений первого объекта. Эти типы ошибок могут быть очень сложными для отладки.
TMN
2

Передача Connectionэкземпляров обычно не является проблемой, хотя в большинстве случаев только реализации DAO должны иметь к ним какое-либо отношение. Теперь, когда ваша проблема связана с тем, что соединения не закрываются после использования, на самом деле это легко исправить: Connectionобъект должен быть закрыт на том же уровне, на котором он открыт, то есть тем же способом. Я лично использую следующий шаблон кода:

final Connection cnx = dataSource.getConnection();
try {
    // Operations using the instance
} finally {
    cnx.close();
}

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

Изменить 2018-03-29: как указано пользователем 1156544 в комментариях ниже, начиная с Java 7, следует использовать конструкцию try-with-resources. Используя его, шаблон кода, который я предоставил в своем первоначальном ответе, можно упростить так:

try (final Connection cnx = dataSource.getConnection()) {
    // Operations using the instance
}
KevinLH
источник
1
Я использую что-то подобное. У меня есть функция doInTransaction (задача DbTask), где DbTask - это мой интерфейс с методом с параметром соединения. doInTransaction получает соединение, вызывает задачу и фиксирует (или выполняет откат, если было исключение) и закрывает это соединение.
user470365
Судя по вашему примеру, это будет означать, что объект DataSource является одиночным?
ipohfly
@ipohfly На самом деле я должен был назвать этот объект, dataSourceа не DataSource(я исправлю свой ответ относительно этого пункта). Точный тип этого объекта будет javax.sql.DataSource. В старом коде я использовал одноэлементное управление всеми доступными источниками данных в моих приложениях. Моим DAO не нужно было знать об этом, так как DataSourceэкземпляр предоставляется путем внедрения зависимости.
KevinLH
Если вы используете эту схему, лучше используйте try-with-resources
user1156544
Когда я отвечал, я еще не использовал Java 7. Но вы правы, что это должно быть предпочтительным способом в наши дни. Я обновлю свой ответ, чтобы включить ваше предложение.
KevinLH
0

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

В общем, вам нужно подумать о последствиях управления соединениями с базой данных, и это может или не может быть ортогонально использованию запросов к базе данных. Например, если у вас есть одно соединение базы данных для данного экземпляра приложения, и оно закрывается, когда оно не используется, это будет ортогонально. Поместите управление в класс синглтона и не передавайте его. Это позволяет вам управлять подключением к БД по мере необходимости. Например, если вы хотите закрывать соединение при каждом коммите (и повторно открывать при следующем вызове), это проще сделать на одиночном, потому что API для этого может быть централизованным.

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

Крис Траверс
источник