Cached 테이블은 사용자가 Submit 버튼을 누르기 이전까지 테이블의 변경사항을 적용하여 데이터베이스에 Access하는 방법을 보여주는 예제입니다. 데이터베이스를 관리하는 파일과 UI를 관리하는 파일로 구성되었고 실행시 사용자가 데이터를 조작할 수 있는 UI는 아래와 같습니다. 


이 예제는 사용자가 데이터베이스에 저장된 데이터를 수정할 수 있는 사용자 정의 대화 상자 위젯인 단일 클래스 TableEditor로 구성됩니다. 
먼저 클래스 정의와 클래스 사용 방법을 확인한 다음 구현 과정을 살펴보겠습니다.

■ TableEditor 클래스 정의
TableEditor 클래스는 QWidget을 상속하여 테이블 편집기 위젯을 최상위 대화 상자 창으로 만듭니다.

tableeditor.h 파일

class TableEditor : public QWidget
{
    Q_OBJECT

public:
    explicit TableEditor(const QString &tableName, QWidget *parent = nullptr);

private slots:
    void submit();

private:
    QPushButton *submitButton;
    QPushButton *revertButton;
    QPushButton *quitButton;
    QDialogButtonBox *buttonBox;
    QSqlTableModel *model;

};


TableEditor 생성자는 두 개의 인수를 사용합니다. 
첫 번째는 TableEditor 객체가 작동할 데이터베이스 테이블에 대한 참조입니다. 
두 번째는 부모 위젯에 대한 포인터이며 기본 클래스 생성자로 전달됩니다.

예제에서 볼 수 있듯이 QSqlTableModel 클래스는 QTableView와 같은 클래스로 볼 수 있는 데이터를 제공하고,
QSqlTableModel 클래스는 단일 테이블에서 데이터베이스를 읽고 쓸 수있는 편집 가능한 데이터 모델을 제공합니다. 
SQL문을 실행하고 조작하는 수단을 제공하는 하위 레벨 QSqlQuery 클래스 위에 빌드됩니다.

또한, 사용자가 명시적으로 Submit을 요청할 때까지 테이블에서 데이터 변경사항에 대해 캐시하는 방법을 보여줍니다.
Table에 자유롭게 데이터 입력이 가능하고, Quit 버튼 입력 시 종료, Revert 버튼 입력 시 초기화, Submit 버튼 입력 시 데이터 변경사항을 저장 기능을 가집니다. 
따라서 테이블의 변경 사항을 적용하여 데이터베이스에 Access하기 위해 모델과 편집기 버튼에 submit() 슬롯을 추가해야합니다.

■ 데이터베이스에 연결
TableEditor 클래스를 사용하려면 먼저 편집하려는 테이블이 포함된 데이터베이스에 연결을 해주어야 합니다.
createConnection() 함수 내에 데이터베이스에 대한 정보가 있습니다.

main.cpp 파일

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    if (!createConnection())
        return 1;

    TableEditor editor("person");
    editor.show();
    return app.exec();
}


createConnection() 함수는 Qt Example에서 제공하는 함수입니다.
설치한 환경에 따라 다르지만, 예제의 경우 /opt/Qt5.8.0/Examples/Qt-5.8/sql/connection.h 위치에 있다는 가정하에 진행하였습니다. 
sql 예제 디렉토리에 있는 connection.h 파일에 정의되어 있고, sql 디렉토리의 모든 예제는이 함수를 사용하여 데이터베이스에 연결합니다.

connection.h 파일

static bool createConnection()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(":memory:");
    if (!db.open()) {
        QMessageBox::critical(nullptr, QObject::tr("Cannot open database"),
            QObject::tr("Unable to establish a database connection.\n"
                        "This example needs SQLite support. Please read "
                        "the Qt SQL driver documentation for information how "
                        "to build it.\n\n"
                        "Click Cancel to exit."), QMessageBox::Cancel);
        return false;
    }

    QSqlQuery query;
    query.exec("create table person (id int primary key, "
               "firstname varchar(20), lastname varchar(20))");
    query.exec("insert into person values(101, 'Danny', 'Young')");
    query.exec("insert into person values(102, 'Christine', 'Holand')");
    query.exec("insert into person values(103, 'Lars', 'Gordon')");
    query.exec("insert into person values(104, 'Roberto', 'Robitaille')");
    query.exec("insert into person values(105, 'Maria', 'Papadopoulos')");

    query.exec("create table items (id int primary key,"
                                             "imagefile int,"
                                             "itemtype varchar(20),"
                                             "description varchar(100))");
    query.exec("insert into items "
               "values(0, 0, 'Qt',"
               "'Qt is a full development framework with tools designed to "
               "streamline the creation of stunning applications and  "
               "amazing user interfaces for desktop, embedded and mobile "
               "platforms.')");
    query.exec("insert into items "
               "values(1, 1, 'Qt Quick',"
               "'Qt Quick is a collection of techniques designed to help "
               "developers create intuitive, modern-looking, and fluid "
               "user interfaces using a CSS & JavaScript like language.')");
    query.exec("insert into items "
               "values(2, 2, 'Qt Creator',"
               "'Qt Creator is a powerful cross-platform integrated "
               "development environment (IDE), including UI design tools "
               "and on-device debugging.')");
    query.exec("insert into items "
               "values(3, 3, 'Qt Project',"
               "'The Qt Project governs the open source development of Qt, "
               "allowing anyone wanting to contribute to join the effort "
               "through a meritocratic structure of approvers and "
               "maintainers.')");

    query.exec("create table images (itemid int, file varchar(20))");
    query.exec("insert into images values(0, 'images/qt-logo.png')");
    query.exec("insert into images values(1, 'images/qt-quick.png')");
    query.exec("insert into images values(2, 'images/qt-creator.png')");
    query.exec("insert into images values(3, 'images/qt-project.png')");

    return true;
}


createConnection() 함수는 메모리 내 SQLITE 데이터베이스에 대한 연결을 열고 테스트 테이블을 작성합니다. 
다른 데이터베이스를 사용하려면 createConnection() 함수 내의 코드를 수정하며 사용가능합니다.

■ TableEditor 클래스 실행
클래스 구현은 생성자와 submit() 슬롯이라는 두 가지 함수로만 구성됩니다. 
생성자에서 데이터 모델과 다양한 창 요소들을 만들 수 있습니다.

TableEditor::TableEditor(const QString &tableName, QWidget *parent)
    : QWidget(parent)
{
    model = new QSqlTableModel(this);
    model->setTable(tableName);
    model->setEditStrategy(QSqlTableModel::OnManualSubmit);
    model->select();

    model->setHeaderData(0, Qt::Horizontal, tr("ID"));
    model->setHeaderData(1, Qt::Horizontal, tr("First name"));
    model->setHeaderData(2, Qt::Horizontal, tr("Last name"));


먼저 데이터 모델을 만들고 모델이 작동 할 SQL 데이터베이스 테이블을 설정합니다. 
QSqlTableModel::setTable() 함수는 테이블에서 데이터를 선택하지 않고, 필드 정보만 가져옵니다.
따라서 QSqlTableModel::select() 함수를 나중에 호출하여 테이블의 데이터로 모델을 채웁니다. 
QSqlTableModel 클래스의 필터 및 정렬 조건을 지정하여 목록에 대해 사용자가 정의할 수 있습니다.

또한, 모델의 편집 방법을 설정하였는데 편집 방법은 뷰에서 사용자가 수행한 변경사항이 실제로 데이터베이스에 적용되는 시점을 나타냅니다. 
사용자가 명시적으로 제출할 때까지 테이블 뷰의 변경 사항을 캐시하려고 하기때문에 QSqlTableModel::OnManualSubmit 방법을 선택합니다. 
대안은 QSqlTableModel::OnFieldChange 및 QSqlTableModel::OnRowChange입니다.

마지막으로 모델이 QSqlQueryModel 클래스에서 상속한 setHeaderData() 함수를 사용하여 뷰 헤더에 표시되는 레이블을 설정합니다.
이후 테이블 뷰를 만듭니다.

    QTableView *view = new QTableView;
    view->setModel(model);
    view->resizeColumnsToContents();


QTableView 클래스는 테이블 뷰의 기본 모델 / 뷰 구현을 제공합니다.  
또한 모델의 변경 사항을 저장하여 항목을 편집 할 수 있습니다. 
읽기 전용보기를 작성하려면 QAbstractItemView 클래스에서보기가 상속하는 editTriggers 특성을 사용하여 적절한 플래그를 설정할 수 있습니다.

뷰에 데이터를 표시하기 위해 setModel() 함수를 사용하여 모델을 뷰에 전달합니다.

    submitButton = new QPushButton(tr("Submit"));
    submitButton->setDefault(true);
    revertButton = new QPushButton(tr("&Revert"));
    quitButton = new QPushButton(tr("Quit"));

    buttonBox = new QDialogButtonBox(Qt::Vertical);
    buttonBox->addButton(submitButton, QDialogButtonBox::ActionRole);
    buttonBox->addButton(revertButton, QDialogButtonBox::ActionRole);
    buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);


TableEditor의 버튼은 일반적인 QPushButton 객체입니다. 
버튼을 버튼 상자에 추가하여 버튼이 현재 위젯 스타일에 적합한 레이아웃으로 표시되도록 합니다. 
QDialogButtonBox를 통해 개발자는 버튼을 추가 할 수 있으며, 사용자의 데스크탑 환경에 적합한 레이아웃을 자동으로 사용합니다.

addButton() 함수를 사용하여 버튼 상자에 버튼을 추가 할 때 QDialogButtonBox::ButtonRole 열거 형을 사용하여 버튼의 역할을 지정해야 합니다. 
혹은, QDialogButtonBox를 사용하여 몇 가지 표준 버튼을 만들 수 있습니다. 

    connect(submitButton, &QPushButton::clicked, this, &TableEditor::submit);
    connect(revertButton, &QPushButton::clicked,  model, &QSqlTableModel::revertAll);
    connect(quitButton, &QPushButton::clicked, this, &TableEditor::close);


Close 버튼을 테이블 편집기의 close() 슬롯에 연결하고, Submit 버튼을 submit() 슬롯에 연결합니다. 
후자의 슬롯은 데이터 트랜잭션을 처리합니다. 

Revert 버튼은 모델의 revertAll() 슬롯에 연결하여 보류중인 모든 변경 사항을 되돌립니다.
마지막으로 버튼 상자와 테이블 뷰를 레이아웃에 추가하고 테이블 편집기 위젯에 레이아웃을 설치하고 편집기의 창 제목을 설정하면 됩니다.

    QHBoxLayout *mainLayout = new QHBoxLayout;
    mainLayout->addWidget(view);
    mainLayout->addWidget(buttonBox);
    setLayout(mainLayout);

    setWindowTitle(tr("Cached Table"));
}


submit() 슬롯은 사용자가 Submit 버튼을 눌러 변경사항을 저장할 때마다 호출됩니다.

void TableEditor::submit()
{
    model->database().transaction();
    if (model->submitAll()) {
        model->database().commit();
    } else {
        model->database().rollback();
        QMessageBox::warning(this, tr("Cached Table"),
                             tr("The database reported an error: %1")
                             .arg(model->lastError().text()));
    }
}


먼저 QSqlDatabase::transaction() 함수를 사용하여 데이터베이스에서 트랜잭션을 시작합니다. 
트랜잭션은 데이터베이스 관리 시스템 또는 이와 유사한 시스템과의 상호 작용 단위로 다른 트랜잭션과 독립적으로 일관되고 안정적인 방식으로 처리됩니다. 
사용된 데이터베이스에 대한 포인터는 QSqlTableModel::database() 함수를 사용하여 얻을 수 있습니다.

보류중인 모든 변경 사항, 즉 모델의 수정 된 항목을 Submit 하는데,  

오류가 발생하지 않으면 QSqlDatabase::commit() 함수를 사용하여 트랜잭션을 데이터베이스에 Commit합니다. 


도중에 오류가 발생하면 QSqlDatabase::rollback() 함수를 사용하여 트랜잭션 롤백을 수행하고 사용자에게 경고를 표시합니다.
(일부 데이터베이스에서는 데이터베이스에 활성 QSqlQuery가 있는 경우 commit() 함수가 동작하지 않습니다.) 

 

■ 전체 소스

Qt SqlDatabase Class 사용해야 하는데, 이를 위해 모듈을 연결하는 작업이 필요합니다.

모듈과 연결하기 위해 Project 파일에 아래와 같이 코드를 추가해야 합니다.

.pro file

QT += sql

 

- main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// main.cpp
#include "tableeditor.h"
#include "/opt/Qt5.8.0/Examples/Qt-5.8/sql/connection.h"
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    if (!createConnection())
        return 1;
 
    TableEditor editor("person");
    editor.show();
 
    return app.exec();
}

 

- TableEditor 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// tableeditor.h
#ifndef TABLEEDITOR_H
#define TABLEEDITOR_H
 
#include <QMainWindow>
#include <QtSql>
#include <QDebug>
#include <QSqlTableModel>
#include <QTableView>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QMessageBox>
 
class TableEditor : public QWidget
{
    Q_OBJECT
 
public:
    explicit TableEditor(const QString &tableName, QWidget *parent = nullptr);
    ~TableEditor();
 
private slots:
    void submit();
 
private:
    QPushButton *submitButton;
    QPushButton *revertButton;
    QPushButton *quitButton;
    QDialogButtonBox *buttonBox;
    QSqlTableModel *model;
};
 
#endif // TABLEEDITOR_H
 
 
// tableeditor.cpp
#include "tableeditor.h"
 
TableEditor::TableEditor(const QString &tableName, QWidget *parent) :
    QWidget(parent)
{
    model = new QSqlTableModel(this);
    model->setTable(tableName);
    model->setEditStrategy(QSqlTableModel::OnManualSubmit);
    model->select();
 
    model->setHeaderData(0, Qt::Horizontal, tr("ID"));
    model->setHeaderData(1, Qt::Horizontal, tr("First name"));
    model->setHeaderData(2, Qt::Horizontal, tr("Last name"));
 
    QTableView *view = new QTableView;
    view->setModel(model);
    view->resizeColumnsToContents();
 
    submitButton = new QPushButton(tr("Submit"));
    submitButton->setDefault(true);
    revertButton = new QPushButton(tr("&Revert"));
    quitButton = new QPushButton(tr("Quit"));
 
    buttonBox = new QDialogButtonBox(Qt::Vertical);
    buttonBox->addButton(submitButton, QDialogButtonBox::ActionRole);
    buttonBox->addButton(revertButton, QDialogButtonBox::ActionRole);
    buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
 
    connect(submitButton, &QPushButton::clicked, this&TableEditor::submit);
    connect(revertButton, &QPushButton::clicked,  model, &QSqlTableModel::revertAll);
    connect(quitButton, &QPushButton::clicked, this&TableEditor::close);
 
    QHBoxLayout *mainLayout = new QHBoxLayout;
    mainLayout->addWidget(view);
    mainLayout->addWidget(buttonBox);
    setLayout(mainLayout);
 
    setWindowTitle(tr("Cached Table"));
}
 
TableEditor::~TableEditor()
{
 
}
 
void TableEditor::submit()
{
    model->database().transaction();
    if (model->submitAll()) {
        model->database().commit();
    } else {
        model->database().rollback();
        QMessageBox::warning(this, tr("Cached Table"),
                             tr("The database reported an error: %1")
                             .arg(model->lastError().text()));
    }
}