Qt에서 제공하는 데이터베이스 모듈(API)은 다른 개발 프레임워크에 비해 사용하기 쉽다. Qt 에서 제공하는 데이터베이스 모듈을 이용하면 통일된 API를 이용해 다양한 데이터베이스에 접근할 수 있다.
각 데이터베이스에 접근해 데이터를 이용하기 위해서 제공하는 API 를 사용하지 않고 Qt에서 제공하는 공통된 데이터베이스 모듈을 이용해 Qt 응용 어플리케이션을 개발 할 수 있다.
위의 그림에서 보는 것과 같이 SQLite를 사용하든지 MariaDB를 사용하든지 Qt에서 제공하는 Qt SQL 모듈을 사용하면 각 데이터베이스를 핸들링 할 수 있다.
Driver Layer
특정 데이터베이스를 연결하기 위한 Low Level Bridge 역할
SQL API Layer
데이터베이스에 연결하기 위한 클래스를 제공하며 데이터베이스 테이블에 데이터를 활용할 수 있는 데이터베이스를 제공.
User Interface Layer
Model 클래스들을 이용해 View 위젯과 연결.
Qt는 주로 사용하는 Model 클래스로써 QSqlQueryModel, QSqlTableModel, QSqlRelatiionalTableModel 클래스를 제공한다.
Qt에서 제공하는 데이터베이스 모듈을 사용하기 위해서는 프로젝트 파일에 다음과 같이 “sql” 키워드를 사용해야 한다.
Qt += sql
만약 CMake 를 사용한다면 아래와 같이 명시하면 된다.
find_package(Qt6 REQUIRED COMPONENTS Sql)
target_link_libraries(mytarget PRIVATE Qt6::Sql)
데이터베이스 연결
TCP에서 상대방과 데이터를 송/수신 하기 위해서, 패킷 송/수신 전에 Connection을 맺어야 하는 것과 같이 먼저 Connection을 맺어야 한다.
QSqlDatabase db1 = QSqlDatabase::addDatabase("QMYSQL");
QSqlDatabase db2 = QSqlDatabase::addDatabase("QSQLITE");
QSqlDatabase db3 = QSqlDatabase::addDatabase("QPSQL");
QSqlDatabase 클래스는 데이터베이스에 연결하기 위한 기능을 제공한다.
또한 고유한사용자 계정을 사용해 연결 기능도 지원한다. 다음은 데이터베이스에 접근하기 위한 예제 소스코드이다.
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("bigblue"); // IP 또는 DNS Host name
db.setDatabaseName("flightdb"); // DB명
db.setUserName("acarlson"); // 계정 명
db.setPassword("1uTbSbAs"); // 계정 Password
bool ok = db.open();
addDatabase( ) 함수의 첫 번째 인자로 사용하고자 하는 데이터베이스 드라이버명을 명시해야 한다.
드라이버 명 | 설명 |
QDB2 | IBM DB2 7.1 이상 버전 |
QIBASE | Borland InterBase |
QMYSQL | MariaDB 또는 MySQL |
QOCI | Oracle Call Interface Driver |
QODBC | Open Database Connectivity |
QPSQL | PostgreSQL 7.3 이상 버전 |
QSQLITE2 | SQLite Version 2 |
QSQLITE | SQLite Version 3 |
QTDS | Sybase Adaptive Server |
내가 현재 사용하는 데이터베이스 드라이버는 MariaDB이므로, 데이터베이스 드라이버명에 'QMYSQL'를 명시해야 한다.
SQL 문을 이용한 데이터베이스 QUERY
QSqlQuery 클래스를 사용
select문 사용
QSqlQuery query;
QString qry;
qry = "SELECT name, salary FROM employee WHERE salary > 500";
query.exec(qry);
테이블의 다음 데이터를 차례대로 검색하고 원하는 필드의 데이터를 얻기 위해서 다음과 같이 사용할 수 있다.
while (query.next())
{
QString name = query.value(0).toString();
int salary = query.value(1).toInt();
}
테이블에 데이터를 삽입하기 위해서는 위에서 다음과 같이 사용할 수 있다.
QSqlQuery query;
query.exec("INSERT INTO employee (id, name, salary) "
"VALUES (1001, 'Thad Beaumont', 65000)");
만약 많은 양의 데이터를 핸들링 하기 위해서 SQL 문을 사용 시 Placeholder방식과 Positioning방식을 사용할 수 있다.
Placeholder방식
플레이스홀더 방식에서는 이름이 지정된 자리 표시자를 사용하여, 쿼리문에서 변수의 값을 삽입할 위치를 지정한다. 이때 자리 표시자는 콜론(:)을 사용하여 이름을 지정한다.
- 파라미터가 많거나 각각의 의미를 명확하게 표현하고 싶을 때 유용하다.
- 코드 가독성이 높아지고, 순서에 의존하지 않기 때문에 쿼리를 관리하기 더 쉽다.
QSqlQuery query;
query.prepare("INSERT INTO employee (id, name, salary) "
"VALUES (:id, :name, :salary)");
for(...)
{
query.bindValue(":id", 1001);
query.bindValue(":name", "Thad Beaumont");
query.bindValue(":salary", 65000);
query.exec();
}
- :id, :age, salary는 플레이스홀더로, 이들은 나중에 실제 값으로 바인딩된다.
- bindValue() 함수로 각 플레이스홀더에 해당하는 값을 지정한다.
Positioning방식
포지셔닝 방식에서는 물음표(?)를 사용하여 파라미터의 위치를 지정한다. 이때 파라미터는 순서대로 바인딩된다.
- 파라미터가 적고, 단순한 쿼리일 때 사용하기 좋다.
- 코드를 간결하게 유지할 수 있다. 하지만 파라미터가 많아지면 순서를 실수할 가능성이 커질 수 있다.
QSqlQuery query;
query.prepare( "INSERT INTO employee(id, name, salary) VALUES (?, ?, ?)");
for(...) {
query.addBindValue(1001);
query.addBindValue("Thad Beaumont");
query.addBindValue(65000);
query.exec();
}
Transaction 처리
Qt는 QSqlDatabase 클래스에서 제공하는 멤버 함수를 이용하면 Transaction 처리가 가능하다.
- 데이터 무결성 보장: 여러 작업 중 하나라도 실패하면, 모든 변경 사항이 취소되어 데이터의 무결성이 유지된다.
- 오류 처리 용이: 트랜잭션을 사용하면 오류 발생 시 데이터를 쉽게 원래 상태로 복구할 수 있습니다.
- 동시성 문제 해결: 트랜잭션을 사용하면 동시성 제어를 통해 여러 사용자가 같은 데이터를 동시에 수정할 때 발생할 수 있는 문제를 방지할 수 있습니다.
QSqlDatabase::database().transaction();
QSqlQuery query;
query.exec( "SELECT id FROM employee WHERE name = 'Torild Halvorsen'");
if (query.next()) {
int employeeId = query.value(0).toInt();
query.exec("INSERT INTO project (id, name, ownerid) "
"VALUES (201, 'Manhattan Project', "
+ QString::number(employeeId) + ')');
}
QSqlDatabase::database().commit();
데이터베이스 Model 클래스
Qt에서 제공하는 데이터베이스 모델 클래스 중에서 가장 사용 빈도가 높은 클래스는 다음과 같다.
모델 명 | 설명 |
QSqlQueryModel | SQL을 이용해 데이터를 READ하기 위한 방식 |
QSqlTableModel | 테이블의 데이터를 READ/WRITE하기 위해 적합. |
QSqlRelationalTableModel | Foreign Key를 이용하는 방식 |
QSqlQueryModel
QSqlQueryModel model;
model.setQuery("SELECT * FROM employee"); // "employee" 테이블의 모든 데이터를 가져오는 쿼리 실행
//각 행의 데이터를 출력하기 위해 반복문 실행
for (int i = 0; i < model.rowCount(); ++i) // 모델의 행 개수(model.rowCount())만큼 반복
{
// i번째 행에서 "id" 열의 값을 가져와 정수형으로 변환
int id = model.record(i).value("id").toInt();
// i번째 행에서 "name" 열의 값을 가져와 문자열로 변환
QString name = model.record(i).value("name").toString();
// id와 name 값을 출력
qDebug() << id << name;
}
- QSqlQueryModel 클래스의 setQuery() 멤버 함수를 이용해 SQL질의를 설정하고 record(int) 멤버 함수를 호출하면 데이터베이스에 저장된 테이블의 레코드를 읽어올 수 있다.
QSqlTableModel
QSqlTableModel model;
model.setTable("employee");
for (int i = 0; i < model.rowCount(); ++i) {
QSqlRecord record = model.record(i);
double salary = record.value("salary").toInt();
salary *= 1.1;
record.setValue("salary", salary);
model.setRecord(i, record);
}
model.submitAll();
- setQuery( )멤버함수를 이용해SQL질의를 설정한다.
- record(int) 멤버 함수를 호출하면 데이터베이스에 저장된 테이블의 레코드를 읽어올 수있다
setData( ) 멤버 함수
setData( ) 멤버 함수를 이용해 특정 row (행) 와 column (열) 을 다음과 같이 수정할 수 있다.
QSqlTableModel model;
model.setTable("employee");
model.setData(model.index(row, column), 75000);
model.submitAll();
removeRows( ) 멤버 함수
model.removeRows(row, 5);
model.submitAll();
- removeRows( ) 멤버 함수의 첫 번째 인자는 삭제할 시작 행(row)의 번호 이다.
- 두뻔째 인자는 삭제할 레코드의 개수이다.
QSqlRelationalTableModel
이 클래스는 Foreign Key를 이용해 테이블을 검색할 수 있는 Model 을 제공한다.
MainTable | |
필드 명 | 타입 |
id | INTEGER |
Date | DATE |
Time | TIME |
Hostname | INTEGER |
IP | INTEGER |
DeviceTable | |
필드명 | 타입 |
id | INTEGER |
Hostname | VARCHAR(255) |
IP | VARCHAR(16) |
DeviceTable 에 레코드를 먼저 삽입한다. 그리고 그 후에 Main Table 의 데이터를 삽입 시 DeviceTable 에 있는 Hostname 과 IP 를 삽입하고자 한다면 다음과 같이 사용할 수 있다.
QSqlRelationalTableModel *modelMain;
QSqlRelationalTableModel *modelDevice;
modelMain->setRelation(3, QSqlRelation("DeviceTable", "id", "Hostname"));
modelMain->setRelation(4, QSqlRelation("DeviceTable", "id", "IP"));
SQLite 데이터베이스를 이용한 예제
[수정] 버튼을 클릭하면 id 가 1번인 레코드의 First Name 을 ‘Eddy’ 로 변경한다. 그리고 Last Name 을 ‘Kim’ 으로 변경한다. [삭제] 버튼을 클릭하면 id 가 1번인 데이터를 삭제하는 예이다.
<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QSqlDatabase>
#include <QSqlTableModel>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QSqlDatabase m_db;
QSqlTableModel *m_model;
bool initializeDataBase();
void creationTable();
void insertDataToTable();
void initializeModel();
private slots:
void slot_pbtUpdate();
void slot_pbtDelete();
};
#endif // WIDGET_H
- initializeDataBase( ) 함수는 SQLite 데이터베이스 파일을 생성한다.
- creationTable( ) 함수는 생성한 데이터베이스 내에 테이블을 생성한다.
- insertDataToTable( ) 은 생성한 테이블에 데이터를 삽입한다.
- initializeModel( ) 함수는 QSqlTableModel 클래스 오브젝트를 생성하고 names 테이블을 model 데이터로 설정한다.
<widget.cpp>
#include "widget.h"
#include "./ui_widget.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QFile>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
if( initializeDataBase() )
{
creationTable();
insertDataToTable();
initializeModel();
ui->tableView->setModel(m_model);
connect(ui->pbtUpdate, SIGNAL(pressed()),
this, SLOT(slot_pbtUpdate()));
connect(ui->pbtDelete, SIGNAL(pressed()),
this, SLOT(slot_pbtDelete()));
}
}
Widget::~Widget()
{
delete ui;
}
bool Widget::initializeDataBase()
{
QFile::remove("./my.db");
m_db = QSqlDatabase::addDatabase("QSQLITE");
m_db.setDatabaseName("./my.db");
if( !m_db.open() ) {
qDebug() << Q_FUNC_INFO << m_db.lastError().text();
return false;
}
return true;
}
void Widget::creationTable()
{
QSqlQuery qry;
qry.prepare( "CREATE TABLE IF NOT EXISTS names "
"("
" id INTEGER UNIQUE PRIMARY KEY, "
" firstname VARCHAR(30), "
" lastname VARCHAR(30)"
")" );
if( !qry.exec() ) {
qDebug() << qry.lastError().text();
}
}
void Widget::insertDataToTable()
{
QSqlQuery qry;
qry.prepare( "INSERT INTO names "
"(id, firstname, lastname) "
"VALUES "
"(1, 'John', 'Doe')" );
if( !qry.exec() )
qDebug() << qry.lastError();
qry.prepare( "INSERT INTO names "
"(id, firstname, lastname) "
"VALUES"
"(2, 'Jane', 'Doe')" );
if( !qry.exec() )
qDebug() << qry.lastError();
qry.prepare( "INSERT INTO names "
"(id, firstname, lastname) "
"VALUES "
"(3, 'James', 'Doe')" );
if( !qry.exec() )
qDebug() << qry.lastError();
}
void Widget::initializeModel()
{
m_model = new QSqlTableModel(this, m_db);
m_model->setTable("names");
m_model->setEditStrategy(QSqlTableModel::OnManualSubmit);
m_model->select();
m_model->setHeaderData(0, Qt::Horizontal, tr("ID"));
m_model->setHeaderData(1, Qt::Horizontal, tr("First Name"));
m_model->setHeaderData(2, Qt::Horizontal, tr("Last Name"));
}
void Widget::slot_pbtUpdate()
{
QSqlQuery qry;
qry.prepare( "UPDATE names "
"SET firstname = 'Eddy', "
"lastname = 'Kim' WHERE id = 1" );
if( !qry.exec() )
qDebug() << qry.lastError();
m_model->setTable("names");
m_model->select();
ui->tableView->setModel(m_model);
}
void Widget::slot_pbtDelete()
{
QSqlQuery qry;
qry.prepare( "DELETE FROM names WHERE id = 1" );
if( !qry.exec() )
qDebug() << qry.lastError();
m_model->setTable("names");
m_model->select();
ui->tableView->setModel(m_model);
}
데이터베이스 테이블 검색 예제
- 이번 예제 에서는 데이터베이스 테이블에 저장된 데이터를 QSqlTableModel 로 가져와 QTableView 위젯에 출력한다. 그리고 시간을 기준으로 검색하는 기능을 구현해 보도록하자.
- 위의 그림에서 보는 것과 같이 시간을 기준으로 [검색] 버튼을 클릭하면 아래 데이터중에서 시간 조건과 일치하는 데이터만을 화면에 표시한다.
- 데이터를 검색하기 위해서 QSqlTableModel 클래스에서 제공하는 setFilter( ) 멤버 함수를 사용하면 된다.
- 프로젝트 생성 시 QWidget 클래스를 상속받는 Widget 클래스를 상속받는다.
- 그리고추가로 DatabaseHandler 클래스를 생성한다.
<DatabaseHandler.h>
#ifndef DATABASEHANDLER_H
#define DATABASEHANDLER_H
#include <QObject>
#include <QSql>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlDatabase>
#include <QFile>
#include <QDate>
#include <QDebug>
#define DATABASE_HOSTNAME "QtDevDataBase"
#define DATABASE_NAME "qtdev.db"
class DatabaseHandler : public QObject
{
Q_OBJECT
public:
explicit DatabaseHandler(QObject *parent = nullptr);
~DatabaseHandler();
void connectToDataBase();
bool insertIntoTable(const QVariantList &data);
private:
QSqlDatabase db;
private:
bool openDataBase();
bool restoreDataBase();
void closeDataBase();
bool createTable();
};
#endif // DATABASEHANDLER_H
- openDataBase( ) 함수는 데이터베이스 파일을 생성한다. 그리고 QSqlDatabase 클래스의 오브젝트를 선언한다.
- restoreDataBase( ) 함수는 openDataBase( ) 함수를 호출한다.
- openDataBase( ) 함수에서 true를 리턴 하면 createTable( ) 함수를 호출한다.
- createTable( ) 함수에서는 RECEIVE_MAIL 이름으로 Define된 테이블을 생성한다.
<DatabaseHandler.cpp>
#include "databasehandler.h"
#include <QDir>
#include <QDebug>
DatabaseHandler::DatabaseHandler(QObject *parent)
: QObject(parent)
{
}
void DatabaseHandler::connectToDataBase()
{
QString dirPath = QDir::currentPath();
dirPath.append("/" DATABASE_NAME);
QFile(dirPath).remove(dirPath);
this->restoreDataBase();
}
bool DatabaseHandler::restoreDataBase()
{
if(this->openDataBase()){
if(!this->createTable()){
return false;
} else {
return true;
}
} else {
qDebug() << "데이터베이스 연결 실패.";
return false;
}
}
bool DatabaseHandler::openDataBase()
{
QString dirPath = QDir::currentPath();
dirPath.append("/" DATABASE_NAME);
db = QSqlDatabase::addDatabase("QSQLITE");
db.setHostName(DATABASE_HOSTNAME);
db.setDatabaseName(dirPath);
if(db.open()){
return true;
} else {
return false;
}
}
void DatabaseHandler::closeDataBase()
{
db.close();
}
bool DatabaseHandler::createTable()
{
QSqlQuery query;
if(!query.exec( "CREATE TABLE RECEIVE_MAIL ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"TIME TIME NOT NULL,"
"MESSAGE INTEGER NOT NULL,"
"RANDOM VARCHAR(255) NOT NULL"
" )"
)){
qDebug() << "테이블 생성 에러 : "
<< query.lastError().text();
return false;
} else {
return true;
}
}
bool DatabaseHandler::insertIntoTable(const QVariantList &data)
{
QSqlQuery query;
query.prepare("INSERT INTO "
"RECEIVE_MAIL (TIME, RANDOM, MESSAGE) "
"VALUES (:TIME, :RANDOM, :MESSAGE )");
query.bindValue(":TIME", data[0].toTime());
query.bindValue(":MESSAGE", data[1].toInt());
query.bindValue(":RANDOM", data[2].toString());
if(!query.exec()){
qDebug() << "데이터 삽입 에러 - "
<< query.lastError().text();
return false;
} else {
return true;
}
}
DatabaseHandler::~DatabaseHandler()
{
}
- 위의 함수 중 insertIntoTable( ) 함수는 생성한 테이블에 레코드를 삽입한다.
- 테이블에레코드 삽입 시 Placeholder 방식을 사용해 레코드를 삽입한다.
< wdiget .h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QSqlTableModel>
#include "databasehandler.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
DatabaseHandler *m_dbHandler;
QSqlTableModel *m_model;
void setupModel(const QString &tableName, const QStringList &headers);
void createUserInterface();
private slots:
void onPushButton();
};
#endif // WIDGET_H
- 위의 함수 중 onPushButton( ) 은 [검색] 버튼을 클릭하면 호출되는 Slot 함수이다.
- setupModel( ) 함수는 QSqlTableModel 클래스 오브젝트를 선언하고 헤더를 선언한다. 그리고 오름차순으로 정렬 시킨다.
- createUserInterface( ) 함수에서는 GUI 상에 배치한 QTableView 위젯을 설정한다. 그리고 GUI상에서 보는 것과 같이 시간을 검색하기 위해 QTimeEdit의 시간을 현재 시간으로 설정한다.
<wdiget.cpp>
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
m_dbHandler = new DatabaseHandler();
m_dbHandler->connectToDataBase();
for (int i = 0; i < 10; i++) {
QVariantList data;
QTime currTime = QTime::currentTime();
currTime = currTime.addSecs(i * 1000);
int random = rand();
data.append(currTime);
data.append(random);
data.append("메시지 : " + QString::number(random));
m_dbHandler->insertIntoTable(data);
}
setupModel("RECEIVE_MAIL",
QStringList() << "ID"
<< "시간"
<< "번호"
<< "메시지");
createUserInterface();
}
Widget::~Widget()
{
delete ui;
}
void Widget::setupModel(const QString &tableName,
const QStringList &headers)
{
m_model = new QSqlTableModel(this);
m_model->setTable(tableName);
for (int i = 0, j = 0; i < m_model->columnCount(); i++, j++) {
m_model->setHeaderData(i, Qt::Horizontal, headers[j]);
}
m_model->setSort(0, Qt::AscendingOrder);
}
void Widget::createUserInterface()
{
ui->tableView->setModel(m_model);
ui->tableView->setColumnHidden(0, true); 기
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->tableView->resizeColumnsToContents();
ui->tableView->horizontalHeader()->setStretchLastSection(true);
for (int i = 0; i < 4; i++) {
ui->tableView->setColumnWidth(i, 100);
}
m_model->select();
ui->timeFROM->setTime(QTime::currentTime());
ui->timeTO->setTime(QTime::currentTime());
connect(ui->pbtSearch, SIGNAL(pressed()), this, SLOT(onPushButton()));
}
void Widget::onPushButton()
{
QString str = QString("TIME between '%3' and '%4'")
.arg(ui->timeFROM->time().toString("hh:mm:ss"));
m_model->setFilter(QString("TIME between '%3' and '%4'")
.arg(ui->timeFROM->time().toString("hh:mm:ss"),
ui->timeTO->time().toString("hh:mm:ss")));
m_model->select();
}
'Qt프로그램' 카테고리의 다른 글
TCP 프로토콜 / 동기 방식 비 동기 방식 구현 (0) | 2024.10.10 |
---|---|
TCP 프로토콜 기반 서버/클라이언트 접속구현 (0) | 2024.10.10 |
Model and View (1) | 2024.10.09 |
Container Classes (1) | 2024.10.08 |
QMainWindow 를 이용한 GUI 구현 (1) | 2024.10.08 |
댓글