Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Некорректное закрытие QSqlDatabase
Форум на CrossPlatform.RU > Библиотеки > Qt > Qt Разработка баз данных
silver
Здравствуйте!

Использую в простеньком приложении QSqlTableModel для отображения и редактирования таблицы.
При закрытии программы выдаёт предупреждение

QSqlDatabasePrivate::removeDatabase: connection 'qt_sql_default_connection' is still in use, all queries will cease to work.


Пробовал в деструкторе окна, где используется модель, делать вещи вроде
model->clear();
delete model;


не помогает :(

Оно, вроде бы, и нестрашно, но как-то неправильно. Кто-нибудь сталкивался с таким?
Litkevich Yuriy
Цитата(Гость_silver_* @ 4.8.2008, 22:27) *
Кто-нибудь сталкивался с таким?

ты БД закрываешь?
принципиально соеденение удалять нужды нет
silver
Всё, разобрался!
Дело было в том, что я использовал не только QSqlTableModel, но и фильтрующую QSortFilterProxyModel. Так вот последняя при удалении не освобождает модель-источник! Что в общем-то и логично. Всё решается либо явным удалением модели-источника, либо указанием для неё в качестве родителя прокси-модели:

    modelFilter = new QSortFilterProxyModel(this);
    modelSource = new QSqlTableModel(modelFilter);
    modelSource->select();
    modelFilter->setSourceModel(modelSource);
    view = createView(modelFilter);


Причём именно в таком порядке, т.к. удаление модели-источника раньше прокси-модели недопустимо!

Пишу так много, потому что полдня потратил на такую глупость, может кому пригодится...
Litkevich Yuriy
вообще это известное предупреждение и видно его только в консоли.

Удалять соединение нет нужды - вот от правная точка!
silver
Цитата(Litkevich Yuriy @ 4.8.2008, 22:18) *
вообще это известное предупреждение и видно его только в консоли.


Я думаю, теоретически, может произойти потеря данных, поэтому лучше правильно закрывать все активные запросы ДО завершения соединения.
ViGOur
Цитата(silver @ 4.8.2008, 23:30) *
поэтому лучше правильно закрывать все активные запросы ДО завершения соединения.
Это правильная практика, ИМХО, что сам создал, то и удалил (открыл -> закрыл).
acen83
QSqlDataBase* bd=QSqlDataBase::addConnection("QMYSQL");
// настройка базы, логина и т.п.
bd->open();
// запросы
bd->close();


затем в другом месте программы повторяя этот код появляется предупреждение

QSqlDatabasePrivate::removeDatabase: connection 'qt_sql_default_connection' is still in use, all queries will cease to work.


Что делать?
ViGOur
Читаем описание QSqlDataBase::addConnection:
Цитата
Adds a database to the list of database connections using the driver type and the connection name connectionName. If there already exists a database connection called connectionName, that connection is removed.
По идее QSqlDataBase* bd=QSqlDataBase::addConnection("QMYSQL"); нужно вызывать один раз и bd желательно сделать глобальным или синглтоном.

Если же ты хочешь создать две копии QSqlDataBase, то используй второй параметр addConnection для их именования...
Litkevich Yuriy
Цитата(acen83 @ 6.8.2008, 12:28) *
Что делать?

если тебе надо использовать уже созданое тобой соединение то используй так:
QSqlDatabase app_db = QSqlDatabase::database();

Перевод из SVN-хранилища:
Цитата
Подробное описание
Класс QSqlDatabase предоставляет подключение к базе данных.
Класс QSqlDatabase предоставляет абстрактный интерфейс для доступа к базе данных. Он использует конкретный QSqlDriver базы данных для доступа и манипуляции данными.
В следующем коде показано как установить соединение:
     QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL");
     db.setHostName("acidalia");
     db.setDatabaseName("customdb");
     db.setUserName("mojito");
     db.setPassword("J0a1m8");
     bool ok = db.open();

После создания объекта QSqlDatabase, устанавливаем параметры соединения с помощью setDatabaseName(), setUserName(), setPassword(), setHostName(), setPort(), и setConnectOptions(). Только после установки параметров вызывается open() для открытия соединения.
Соединение определеное выше — безымянное соединение. Это соединение по умолчанию и доступ к нему может быть получен позже используя database():
     QSqlDatabase db = QSqlDatabase::database();

Чтобы сделать програмирование более удобным, QSqlDatabase реализован как класс-значение (англ. "value class"). Любые изменения сделаные в соединении с базой данных через один объект QSqlDatabase будут влиять на другие объекты QSqlDatabase представляющие это же соединение. Вызовите cloneDatabase(), если вы хотите создать независимое соединение с базой данных на основе существующего.
Если вы нуждаетесь во множестве соединений с базами данных одновременно, определите произвольное имя в addDatabase() и database(). Вызовите removeDatabase(), чтобы удалить соединение. QSqlDatabase выведет предупреждение, если вы попытаетесь удалить соединение, указанное в других объектах QSqlDatabase. Используйте contains(), чтобы видеть если заданное имя соединения есть в списке соединений.
Как только соединение установлено вы можете посмотреть, какие таблицы база данных предоставляет с помощью tables(), найти первичный индекс для таблицы с помощью primaryIndex(), получить мета-информацию о полях таблицы (например, их имена) с помощью record() и выполнить запрос с помощью exec().
Если транзакции поддерживаются, вы можете использовать transaction(),чтобы начать транзакцию, и затем commit() или rollback(), чтобы завершить ее. Вы можете узнать поддерживается ли транзакция используя QSqlDriver::hasFeature(). При использовании транзакции вы должны начать транзакцию, прежде чем создадите свой запрос.
Если произошла ошибка, она может быть получена с помощью lastError().
Имена SQL драйверов доступны из drivers(), вы можете проверить доступность драйвера с помощью isDriverAvailable(). Если вы создали свой собственный драйвер, вы можете зарегистрировать его с помощью registerSqlDriver().
Смотрите также QSqlDriver, QSqlQuery, Модуль QtSql и Потоки и Модуль QtSql.


Цитата
QSqlDatabase QSqlDatabase::addDatabase ( const QString & type, const QString & connectionName = QLatin1String( defaultConnection ) ) [static]
Добавляет базу данных в список соединений баз данных используя драйвер type и имя соединения connectionName. Если уже существует соединение с базой данных называющееся connectionName, этосоединение удаляется.
Соединения с базой данных в дальнейшем именуется connectionName. Вновь добавленое соединение будет возвращено.
Если параметр connectionName не определен, внови добавленное соединение становится для приложения соединением поумолчанию, и последующие вызовы database() без параметра вернут ссылку на него. Если connectionName задан, используйте database(connectionName), чтобы вернуть указатель на соединение.
Предупреждение: Если вы добавите базу данных с тем же именем как и у существующей базы данных, старая база данных будет заменена новой. Это происходит автоматически, если вы вызываете эту функцию больше, чем один раз без указания connectionName.
Чтобы использовать соединение, вам нужно настроить его, например, вызвав некоторые или все из фунций setDatabaseName(), setUserName(), setPassword(), setHostName(), setPort(), и setConnectOptions(), а затем вам нужно открыть соединение с помощью open().
Замечание: Эта функция потокобезопасная.
Смотрите также database(), removeDatabase(), и Драйвера баз данных SQL.


acen83, silver, почитайте вот эту тему Базы данных
silver
Цитата(acen83 @ 6.8.2008, 8:28) *
Что делать?


Скорее всего, у тебя остаются висеть незакрытые запросы. Причём не обязательно именно QSqlQuery, но и в виде QSqlQueryModel, QSqlTableModel и так далее по иерархии. Убедись, что к моменту закрытия соединения, все твои модели и запросы либо выходять из области видимости (если создавались на стеке):
 {
     QSqlDatabase db = QSqlDatabase::database("sales");
     QSqlQuery query("SELECT NAME, DOB FROM EMPLOYEES", db);
}
// Both "db" and "query" are destroyed because they are out of scope
QSqlDatabase::removeDatabase("sales"); // correct


либо удаляются оператором delete или родителем (если создал их как QSqlQuery *query = new QSqlQuery). В моём случае проблема была именно в этом.
acen83
Цитата(silver @ 6.8.2008, 15:43) *
Скорее всего, у тебя остаются висеть незакрытые запросы

Remove database у меня есть, но он закрывает только ругательства о незакрытых запросах, а сообщения о закрытии старой БД все равно остаются :unsure:

Плохо что нет какой-нибудь функции наподобие clear() и соответственно isNull(), для того чтобы определять была инициализирована база или нет. Так получить базу через QSqlDatabase::database() конечно можно, но как проверить была она уже настроена или нет :huh:
Litkevich Yuriy
во-первых, в программе работай по порядку, и давай имена соединениям. И по этим именам обращайся к соединению.
во-вторых, в Qt'ях работа идет с именоваными соединениями,
Цитата
Используйте contains(), чтобы видеть если заданное имя соединения в списке соединений

если нет то надо создать, а вообще удалять соединение, все-таки нужды нет.

Цитата(acen83 @ 6.8.2008, 22:41) *
функции наподобие clear() и соответственно isNull()

что должны были бы делать эти функции, еслиб они были?
acen83
2 Litkevich Yuriy:

clear() - возвращало бы все в начальное состояние, как при запуске программы.

Дело в том что после QSqlDatabase::removeDatabase("qt_default_connection_что_то_типа_этого") в списке соединений ничего нет. Т.е. базу мы закрыли, удалили соединение, а при запуске новой базы ругательство все равно остается - непорядок.

У меня при нажатии на кнопку происходит инициализация БД, настройка БД, выполнение запросов, закрытие всего этого хозяйства. Получается в разных частях программы надо создавать именованные соединения, причем вынесенные в конструктор? че то некрасиво как то, тем более вероятность нажатия кнопки - 1 к 100.
Litkevich Yuriy
у меня в классе главного окна создается экземпляр "одиночки", а внем соединение с БД настроек приложения (это я так решил вместо всяких ини-конфигов сделать). далее после конструктора, т.е. после того как окно программы отобразится создается соединение основной БД.
исходник

MainWindow::MainWindow()
{
    mdiArea = new QMdiArea;
    setCentralWidget(mdiArea);
    connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow *)), this, SLOT(updateMenus()));
    
    windowMapper = new QSignalMapper(this);
    connect(windowMapper, SIGNAL(mapped(QWidget *)), this, SLOT(setActiveSubWindow(QWidget *)));

    createActions();    // Создаем действия
    createMenus();        // Создаем меню
    createToolBars();    // Создаем панель инструментов
    createStatusBar();    // Создаем панель статуса
    readWindowSettings();// Читаем настройки окна
    // Создаем экземпляр класса сесии
    auth = Session::InitInstance();    

    
    connect(this, SIGNAL(SigShow()), SLOT(SlotOnShow()));
    
    
}

MainWindow::~MainWindow()
{
    auth->FreeInstance();
}

bool MainWindow::event(QEvent *p_event)
{
    if( p_event->type() == QEvent::ShowToParent)
        emit SigShow();

    return QWidget::event(p_event);
}

/*=====================================================================
                Постинициализация
  ===================================================================*/
void MainWindow::SlotOnShow()
{
  QString    errmsg;
  QString    title = tr("Ошибка программы");

qDebug() << "SlotOnShow: " << QDateTime::currentDateTime() << "\n\r";
  
    // Вход от имени анонимного пользователя
    auth->LoginAnonymous();
    
    // Обновляем меню
    updateMenus();
    
    // Проверяем все ли хорошо с настройками
    if (!auth->isGood(errmsg))
    {
        QMessageBox::critical(this, title, tr("Ошибка настроек, обратитесь к специалисту. Сообщение: \n\r") + errmsg);
        return;
    }
    
    // Здесь проверяется и устанавливается соединение с БД.
  bool ok;
  QSqlDatabase app_db = QSqlDatabase::database("appdb");    // создано в Session::InitInstance();
  QSqlQuery query(app_db);
  QString    querystr;
  QString    qerr;
  
  QString    db_driver;
  QString    db_name;
  QString    db_user_name;
  QString    db_user_paswd;
  
  
    querystr="SELECT F_VALUE FROM APPSETTINGS \
    WHERE ((F_TYPE='db_driver') OR (F_TYPE='db_name') OR \
    (F_TYPE='db_user_name') OR (F_TYPE='db_user_paswd')) \
    ORDER BY F_TYPE ASC";
    ok = query.exec(querystr);
qDebug() << "select DB settings - " << ok << "\n\r";
    
    if (!ok)
    {
        qerr = query.lastError().text();
        QMessageBox::critical(0, title,
                                 tr("Невозможно прочитать настройки БД.\n\r Причина: ")+qerr,
                                QMessageBox::Ok);
    }
    
    query.first();
    if (!query.isValid())
    {
        qerr = query.lastError().text();
        QMessageBox::critical(0, title,
                                 tr("Невозможно прочитать настройки БД.\n\r Причина: ")+qerr,
                                QMessageBox::Ok);
    }
    
    db_driver = query.value(0).toString();
    query.next();
    db_name = query.value(0).toString();
    query.next();
    db_user_name = query.value(0).toString();
    query.next();
    db_user_paswd = query.value(0).toString();
    
    db_main = QSqlDatabase::addDatabase(db_driver,"maindb");
    db_main.setDatabaseName(db_name);
    db_main.setUserName(db_user_name);
    db_main.setPassword(db_user_paswd);
qDebug() << "db_main.open: " << QDateTime::currentDateTime() << "\n\r";
    ok = db_main.open();
    
    if (!ok)
    {
        qerr = db_main.lastError().text();
        QMessageBox::critical(0, title,
                                 QObject::tr("Невозможно установить соединение c БД.\n\r")+qerr,
                                QMessageBox::Ok);
    }
qDebug() << "db_main.open: " << QDateTime::currentDateTime() << "\n\r";
qDebug() << db_main << "\n\r";
}

Tonal
2 Litkevich Yuriy
Нет нужды делать соединение с БД одиночкой, т.к. оно само только удобный интерфейс к реальному соединению, которое уже одиночка.
Вроде с тобой же обсуждали в "переводах" значение value type в данном контексте. :)
Litkevich Yuriy
Цитата(Litkevich Yuriy @ 7.8.2008, 4:09) *
у меня в классе главного окна создается экземпляр "одиночки"

Да, надо было полнее прокоментировать. Это у меня для авторизации пользователей программы, но там же и соединение с БД хранящих настройки пользователей.

А соединение с основной БД так:
Цитата(Litkevich Yuriy @ 7.8.2008, 4:09) *
далее после конструктора, т.е. после того как окно программы отобразится создается соединение основной БД.
silver
Цитата(acen83 @ 6.8.2008, 18:41) *
Remove database у меня есть, но он закрывает только ругательства о незакрытых запросах, а сообщения о закрытии старой БД все равно остаются :unsure:

Может я чего-то пропустил? Что ещё за сообщение о закрытии старой БД? В первом посте ты указал только одно сообщение: о том, что на момент закрытия соединения остались запросы, которые это соединение используют.

Цитата(acen83 @ 6.8.2008, 18:41) *
Плохо что нет какой-нибудь функции наподобие clear() и соответственно isNull(), для того чтобы определять была инициализирована база или нет. Так получить базу через QSqlDatabase::database() конечно можно, но как проверить была она уже настроена или нет :huh:


Есть функции QSqlDatabase::isOpen() и QSqlDatabase::isValid(), которые именно это и делают.
Litkevich Yuriy
Цитата(silver @ 7.8.2008, 20:01) *
Что ещё за сообщение о закрытии старой БД?

да он дважды одно и тоже соединение создает:
Цитата(acen83 @ 6.8.2008, 12:28) *
QSqlDataBase* bd=QSqlDataBase::addConnection("QMYSQL");
// настройка базы, логина и т.п.
bd->open();
// запросы
bd->close();

затем в другом месте программы повторяя этот код появляется предупреждение

QSqlDatabasePrivate::removeDatabase: connection 'qt_sql_default_connection' is still in use, all queries will cease to work.
acen83
Цитата(silver @ 7.8.2008, 17:01) *
Есть функции QSqlDatabase::isOpen() и QSqlDatabase::isValid(), которые именно это и делают.


Так чтоли?

QSqlDatabase db=QSqlDatabase::database();
if (!db.isValid())
{
db.addConnection("QMYSQL");
// добавление логинов, пассов и т.п.
}
return db;
silver
Цитата(acen83 @ 7.8.2008, 18:45) *
Так чтоли?


Ну, как один из вариантов:
    QSqlDatabase db = QSqlDatabase::database("my_very_rarely_used_db");
    if (!db.isValid()) {
            // выполнится только в первый раз, когда соединения ещё нет
        db = QSqlDatabase::addDatabase("QMYSQL", "my_very_rarely_used_db");
        // Заполняем параметры соединения
    }
    db.open();
    // работаем
    db.close();
    // всё, никаких removeDatabase() делать не надо


Хотя я бы всё-таки создал именованное соединение в конструкторе, и не открывал его. Эта операция практически не займёт ни времени, ни ресурсов. Зато когда БД понадобится, можно сразу открывать её и работать, без всяких дополнительных проверок.
fortero
Цитата(silver @ 6.8.2008, 14:43) *
Цитата(acen83 @ 6.8.2008, 8:28) *
Что делать?


Скорее всего, у тебя остаются висеть незакрытые запросы. Причём не обязательно именно QSqlQuery, но и в виде QSqlQueryModel, QSqlTableModel и так далее по иерархии. Убедись, что к моменту закрытия соединения, все твои модели и запросы либо выходять из области видимости (если создавались на стеке):
 {
     QSqlDatabase db = QSqlDatabase::database("sales");
     QSqlQuery query("SELECT NAME, DOB FROM EMPLOYEES", db);
}
// Both "db" and "query" are destroyed because they are out of scope
QSqlDatabase::removeDatabase("sales"); // correct


либо удаляются оператором delete или родителем (если создал их как QSqlQuery *query = new QSqlQuery). В моём случае проблема была именно в этом.


А Вы можите написать пример как правельно это сделать с передачей в конструктор?
Буду очень признателен!
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2024 IPS, Inc.