Russian Qt Forum
Ноябрь 24, 2024, 09:44 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Совместное использование БД и уведомление о ее изменении  (Прочитано 15590 раз)
Odyssey
Гость
« : Февраль 16, 2011, 13:57 »

Здравствуйте!
Я новичок, потому заранее прошу извинить, если вопрос не слишком умный  Улыбающийся

Есть БД на сервере и два приложения на двух разных компьютерах, совместно использующие эту БД.

Как приложение на одном компьютере может понять, что другое приложение изменило эту БД (поменяло значение поля записи таблицы и т.д.)?

Конечно, можно создать сетевые компоненты и при любом изменении посылать через них сообщение "соседу", но, может, есть какой-нибудь более изящный способ? Скажем, какой-нибудь сигнал от самой БД и т.п. 
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #1 : Февраль 16, 2011, 14:17 »

Смотря что за БД. В некоторых есть notify, на которые можно подписаться.
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
Odyssey
Гость
« Ответ #2 : Февраль 16, 2011, 15:10 »

Спасибо, Пантер Улыбающийся

У меня PostgreSQL версии 7.4.2. Судя по мануалу, команды LISTEN и NOTIFY он понимает.
Однако как мне "поднять" сигнал, пришедший на уровне базы данных, на уровень Qt-приложения?
Как я понимаю, для этого необходимо предварительно подключить и использовать специальные библиотеки libpq или libpgtcl. А можно ли обойтись стандартными библиотеками Qt?

(Кстати, а если приложения на разных компах однотипные? Получается, они всё равно должны каждое выполнять команду NOTIFY name с различным аргументом name? А то ведь само БД изменит и себе же (в том числе) сигнал отправит - и как отличить, само себе приложение сигнал послало и ничего не делать, или всё же приложение на другом компе и нужно новую информацию как-то обработать?)
« Последнее редактирование: Февраль 16, 2011, 15:14 от Odyssey » Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #3 : Февраль 16, 2011, 16:32 »

На уровне приложения читай bool QSqlDriver::subscribeToNotification ( const QString & name ).
На уровне базы данных, сделай триггера на обновление/добавление/удаление и в них делай NOTIFY.
Сам использовал с PostgreSQL, работает отлично.
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
Odyssey
Гость
« Ответ #4 : Февраль 17, 2011, 12:56 »

Спасибо, сенсей!

Но еще несколько вопросов появилось ))

I. Читал оригинальный мануал для версии 7.4.2 с "родного" сайта. Там в 35-й главе описываются триггерные функции (trigger functions). Это оно? Если да, то там указано, что функции могут быть написаны только на языке С, а на языке SQL они пока быть не могут. Как же тогда выполнить команду NOTIFY? (Есть там также пример, но в нем опять подключение дополнительных библиотек. Это ведь не то, о чём ты говорил?).

II. Попытался сделать обмен сигналами без отслеживания изменений таблиц на уровне баз данных. Что-то получилось, но не всё так, как я ожидал. Привожу простейший пример, где в конструкторе использована функция subscribeToNotification и привязка слота формы к сигналу драйвера Notification, а по нажатию кнопки исполняется запрос "NOTIFY n1" .

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

Код:
main.cpp

#include <QApplication>
#include "qmywidget.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QMyWidget wgt(0);
    return app.exec();
}

qmywidget.h

#ifndef QMYWIDGET_H
#define QMYWIDGET_H

#include <QWidget>

class QPlainTextEdit;
class QPushButton;
class QVBoxLayout;

class QMyWidget : public QWidget
{
    Q_OBJECT

protected:
    virtual void closeEvent(QCloseEvent*);

public:
    QMyWidget(QWidget * = 0);
    QPlainTextEdit *pTE;
    QPushButton *cmdNotify;
    QVBoxLayout *layout;

public slots:
    void slotNotify();
    void slotNotification(const QString &);

};

#endif // QMYWIDGET_H


qmywidget.cpp

#include <QPlainTextEdit>
#include <QPushButton>
#include <QtSql>
#include <QVBoxLayout>
#include "qmywidget.h"

QMyWidget::QMyWidget(QWidget *parent) : QWidget(parent)
{
    pTE = new QPlainTextEdit(this);
    cmdNotify = new QPushButton("Notify", this);
    layout = new QVBoxLayout();
    layout->addWidget(pTE);
    layout->addWidget(cmdNotify);
    setLayout(layout);

    QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL7");
    db.setDatabaseName("test");
    db.setHostName("localhost");
    db.setPort(***);
    db.setUserName(***);
    db.setPassword(***);
    if (!db.open()) {
      qDebug() << "Database opened: " << db.lastError();
    }
    else { qDebug() << "Database error";}

    db.driver()->subscribeToNotification("n1");
    connect(db.driver(), SIGNAL(notification(const QString&)), this, SLOT(slotNotification(const QString&)) );
    connect(cmdNotify, SIGNAL(clicked()), this, SLOT(slotNotify()));
    show();
}

void QMyWidget::slotNotification(const QString &Notif) {
    pTE->appendPlainText(Notif);
}

void QMyWidget::slotNotify() {
    pTE->appendPlainText("Notify button pressed");
    QSqlQuery queryNotify("NOTIFY n1;");
}

void QMyWidget::closeEvent(QCloseEvent *event) {
    QSqlDatabase db = QSqlDatabase::database();
    if (db.isOpen()) { db.close(); }
}

Однако получается, что сигнал notify иногда проходит несколько раз при нажатии кнопки. Подозреваю, что я как-то некорректно обошелся с запросом, и каким-то образом он исполняется несколько раз. Но я, вроде бы, делаю экземпляр запроса в стеке и при выходе из функции он должен быть уничтожен?

III. К этой же небольшой программке.
Почему в строке "QMyWidget wgt(0);" мне непременно нужно указывать аргумент? Если не указать, то виджет невидим, но ведь я вроде бы и так задаю это нулевое значение по умолчанию?
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #5 : Февраль 17, 2011, 13:19 »

1. http://www.postgresql.org/docs/8.1/static/sql-createtrigger.html
2. Уведомление ловит как отправляющий экземпляр, так и второй.
3. Странно. Вот так вот не работает:
Код
C++ (Qt)
QMyWidget wgt;
?
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
Odyssey
Гость
« Ответ #6 : Февраль 17, 2011, 14:29 »

Пантер еще раз и бесконечно - большое спасибо! Улыбающийся))  Разрешишь попытать тебя еще немножко? )

1. Итак, функцию, вызываемую при спуске триггера всё равно придётся писать на С? Это я понимаю из приведенного примера. Сам-то триггер создается командой SQL и функция объявляется тоже командой SQL:

CREATE FUNCTION trigf() RETURNS trigger
    AS 'filename'
    LANGUAGE C;

Но перед этим функция должна быть предварительно написана на С и скомпилирована в файл filename, верно? Да и возвращать она обязана некий тип trigger, определенный в одной из сишных подключаемых библиотек...

Если всё "так и задумано" - буду изучать Улыбающийся

2. В том-то и дело, что количество этих сигналов может быть самым разным. Если честно, я ожидал, что по нажатию любой из кнопок каждому приложению будет приходить по одному сигналу. Однако в то приложение, где была нажата кнопка сигнал никогда не идет. А в другое может приходить 1, 2, да любое количество сигналов.

Пример:
В экземпляре А кнопка нажимается 5 раз. В экземпляре Б при каждом нажатии появляется ровно один сигнал (т.е. надпись "n1"). Вроде, так и должно быть. Потом я нажимаю кнопку экземпляра Б. В А появляется разом аж 6 сигналов. При повторном нажатии Б сигнал в А опять только 1. При нажатии А в Б приходит уже три сигнала и т.д.
В общем, ничего не приходит в голову, кроме как ошибок с констуированием и уничтожением экземпляров объекта запроса... (пытался его методом тыка и в динамической памяти делать - тот же эффект).

3. А вот так работает! ("Чёрт побери, Холмс... но КАК?")  Подмигивающий
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #7 : Февраль 17, 2011, 14:41 »

Ух, заставил покопаться в давно забытых знаниях по sql. Улыбающийся
Код:
CREATE TRIGGER name_of_trigger FOR some_table
AFTER UPDATE AS
BEGIN
  NOTIFY n1;
END
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
Odyssey
Гость
« Ответ #8 : Февраль 17, 2011, 17:09 »

Эх, не прокатило... выдавало ошибку уже на FOR.

Чуть поправил вот так:

Код:
CREATE TRIGER trigg AFTER UPDATE ON table1 AS
BEGIN
     NOTIFY n1;
END

...но все равно выдает ошибку "в районе AS".  Плачущий  
Должно быть, версия PSQL не та... (данную sql-команду пытался выполнить в программе pgAdmin III)
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #9 : Февраль 17, 2011, 17:15 »

В pgAdmin на таблице в контекстном меню должно быть Создать триггер.
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
vlad-mal
Гость
« Ответ #10 : Февраль 17, 2011, 20:27 »

Odyssey, в 99,99 % случаев это не нужно. Я синхронное обновление данных на клиентах.
Ну, представь себе, что ты узнал об изменениях - ну и что? Хочешь, чтобы данные у разных клиентов мелькали туда-сюда? А если на клиенте данных много? А если каскадные обновления?
Ну и как отображать новые данные? Предположим - это список. С какого место его отображать? А если это "место" удалено? ...(и т.д.)

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

Добавь в тулбар кнопку "обновить", и по нажатию перечитывай набор данных. Пользователи будут счастливы.
~~~~~~~~~~~~~~~
Записан
Odyssey
Гость
« Ответ #11 : Февраль 21, 2011, 11:36 »

Пантер

Create trigger не наблюдается - есть только Create trigger function. В хелпе, привинченном к пгАдмину, повторяется всё та же вещь - писать триггерные функции на простом языке функций Sql ("plain SQl function language") невозможно. Когда я пытаюсь создать функцию через команду Create trigger function, выбираю язык internal и прописываю в окне Parameter строчку NOTIFY n1, мне выдают, что, к сожалению, нет у них такой встроенной функции NOTIFY Грустный

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

vlad-mal

Спасибо за подсказку. )) Дело в том, что клиентов там мало, обновления на экране достаточно редкие. Один из них находится в ждущем режиме и должен реагировать на ввод даных другим. Потому кнопка "Обновить" не вполне подходит. Возможно, стоит сделать регулярные автоматические обновления по таймеру? Можно еще передать сообщение по сети, но хотелось использовать средства БД, если уж они есть, и доразобраться всё же с триггерами и уведомлениями. 
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #12 : Февраль 21, 2011, 12:10 »

Покопался в старом своем проекте. Вот как нужно:
Код
SQL
CREATE RULE notify_some_table_update_rule AS ON UPDATE TO some_table DO NOTIFY some_table_updated;
 
Удачи. Подмигивающий
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
vlad-mal
Гость
« Ответ #13 : Февраль 21, 2011, 13:44 »

Есть опыт использования подобных извещений, правда, с FireBird.
Все заинтересованные клиенты подписываются на определенное сообщение.
При модификации данных (триггер на удаление/изменение/добавление), в случае подтверждения транзакции, подписчики получают уведомление. Ну и могут, к примеру, перечитать данные.
Записан
Odyssey
Гость
« Ответ #14 : Февраль 21, 2011, 14:02 »

Спасибо, сенсей, сигнал при изменении таблицы посылается и ловится! ))

Только осталась всё та же непонятная фигня, что и с исполняемыми запросами "NOTIFY n1;" (см. выше).
Т.е. количество уведомляющих сигналов, приходящихся на одно изменение, "плавает" от 1 до сколько угодно (по тому же алгоритму). (Глюк базы, значит?)
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.145 секунд. Запросов: 22.