一、QSqlQuery
在QT中,QSqlQuery用来执行sql语句操作,通常使用一下两个函数来进行相关的操作:
1.使用给定的数据库驱动执行对应的sql语句
explicit QSqlQuery(const QString& query=QString(), QSqlDatabase db=QSqlDatabase());
2.使用给定的数据库创建QSqlQuery对象,但不执行任何操作
explicit QSqlQuery(QSqlDatabase db);
下面给出一段代码更详细,直观的介绍上面两个函数:
//1. 使用第一种 假定数据库已经打开 对象为db
QSqlQuery query("select * from user",db);
//query会直接执行查询操作
while(query.next())
{
...
}
//2. 使用第二种
QSqlQuery query(db);
QString sql = "select * from user";
//query不会执行查询操作 需要query.exec(sql)才会执行查询操作
query.exec(sql);
while(query.next())
{
...
}
二、SQL注入攻击
什么时SQL注入攻击?即攻击者在进行一些sql语句操作时,构建特殊的输入作为参数传入,(SQL语法里的一些组合),从而欺骗数据库执行操作得到非法授权的数据。其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。
常见的SQL注入攻击构建的一条语句为: 'OR '1=1。如:
//假如传入的username = bob 'OR'1=1
QString sql = "select * from user where username =' " + username + "'";
//拼接后的语句
sql = "select *from user where username = ' bob ' OR ' 1=1' ";
//这样 1=1这个条件一直为真,sql语句一执行,就会把user表中所有数据打印出来
那怎么避免SQL注入攻击了?
使用prepared Query。主要步骤如下:
// username = bob' or '1=1
QString sql = "select * from user where usename = :username";
// 1.创建QSqlQuery对象
QSqlQuery query(db);
// 2.使用prepared语句 自动解析sql语句
query.prepared(sql);
// 3.使用bindValue替换占位符
query.bindValue(":username",username);
// 4.执行query操作
query.exec();
while(query.next())
{
...
}
注意:1.占位符,在QT中,可以使用 :+ 任意字符串作为占位符的形式
2.bindValue()执行后,默认给字符串加上了引号''
三、使用占位符遇到的一个问题
在使用占位符的操作时,遇到一个问题,虽然这个问题有点搞笑,但还是分享一下,希望各位在用到时,可以引以为鉴。
代码如下:
//首先创建数据驱动
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
//基本的设置
db.setHostName("127.0.0.1");
db.setPort(3306);
db.setUserName("root");
db.setPassword("1234");
db.setDatabaseName("qt_db");
//打开数据库 建立连接
bool isok = db.open();
if(isok)
{
qDebug() << "success" ;
QString aa = "%o%";
QString sql = "SELECT * FROM user WHERE username LIKE:match";
QSqlQuery query;
query.prepare(sql);
query.bindValue(":match", aa);
if(!query.exec(sql))
{
qDebug() << query.lastError().text();
}
while(query.next())
{
qDebug() << QString("Id: %1, Username: %2, Password: %3")
.arg(query.value("id").toInt())
.arg(query.value("username").toString())
.arg(query.value("password").toString());
}
return 0;
}else
{
qDebug() <<"fail " << db.lastError();
return -1;
}
各位看出什么问题了吗?在上面执行了一个模糊查询,但在运行代码的时候,就会打印下面的错误:
success
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ':match' at line 1 QMYSQL: Unable to execute query"
其实,这里问题也很简单,就是query.exec()。这query.exec(sql),会执行sql中的sql语句,而在此之前,已经声明了使用prepared来解析sql语句,所以这就造成了冲突,解析的语句并没有被执行,反而执行了原语句sql。所以,只要记住,使用了prepared后,query.exec()不需要再加任何参数。
四、QT 事务
事务,是并发控制的单位。是用户定义的一个操作序列,这些操作要么都执行,要么都不执行,是一个不可分割的整体。通过事务,能将逻辑相关的一组操作绑定在一起,保证了数据的完整性。
在QT中,事务相关的函数如下
1.查询是否支持事务:bool QSqlDriver::hasFeature(DriveFeature feature) const
2.开启事务: bool QSqlDatabase::transaction()
3.提交事务: bool QSqlDatabase::commit()
4.回滚事务: bool QSqlDatabase::rollback()
具体代码如下:
//已有QSqlDatabase对象db
bool isOK = db.driver()->hasFeature(QSqlDriver::Transcations);
//如果支持事务
if(isOK)
{
//打开事务
db.transaction();
QSqlQuery query1("insert into user (id,username,password) values (1,'peter','123')",db);
QSqlQuery query2("insert into user (id,username,password) values (4,'andy','xxx')",db);
...
if(query1.exec() && query2.exec())
{
db.commit();
}
else
{
db.rollback();
}
}
执行上面代码 ,由于原表中已经存在id=1的情况,所以query1执行失败,query2执行成功。但是,更新数据库发现,两个数据都没用更新在表中。
使用事务,能在处理大规模数据时,提高效率和性能。以下是针对网上的两个问题进行的测试,可以酌情采纳。
1. 开启事务、数据库更新、提交事务|回滚事务不在同一个函数是否可行
测试代码如下:
void MainWindow::openTransaction(const QString &connectionName)
{
QSqlDatabase db = getConnectionByName(connectionName);
db.transaction();
comRollTransaction(connectionName);
}
int MainWindow::updateDatabase(QSqlDatabase database)
{
QSqlQuery query1("insert into user (id,username,password) values (1,'peter','123')",database);
QSqlQuery query2("insert into user (id,username,password) values (4,'andy','xxx')",database);
if(query1.exec() && query2.exec())
{
//如果两个query都执行成功,就提交事务
return 0;
}
else
{
qDebug() << query1.lastError().text();
qDebug() << query2.lastError().text();
return -1;
}
}
void MainWindow::comRollTransaction(const QString &connectionName)
{
QSqlDatabase db = getConnectionByName(connectionName);
int rs = updateDatabase(db);
if(rs == 0)
{
db.commit();
}else
{
db.rollback();
}
}
测试结果和上面案例一样。由此可见,开启事务、数据库更新、提交事务|回滚事务不在同一个函数是可行的,因为事务是面向数据库连接的。
2.要向 user 表里插入 100000 个用户:1.开启事务每次插入 1000 个2.不使用事务插入 1 个
测试代码如下:
QSqlDatabase db = getConnectionByName(connectionName);
//默认的用户名
QString name = "xxx";
qDebug() << "当前时间:" <<QString::number(QDateTime::currentMSecsSinceEpoch());
for(int i = 0; i < 100; i++)
{
QString sql = "insert into user (id,username,password) values (:userid,:namem,:word)";
QSqlQuery query(db);
query.prepare(sql);
query.bindValue(":userid",QString("%1").arg(i+4));
query.bindValue(":namem",name);
query.bindValue(":word",QString("123%1").arg(i));
if(!query.exec())
{
qDebug() <<"error";
}
}
// db.transaction();
// int count = 0;
// for(int i = 0; i < 100; i++)
// {
// QString sql = "insert into user (id,username,password) values (:userid,:namem,:word)";
// QSqlQuery query(db);
// query.prepare(sql);
// query.bindValue(":userid",QString("%1").arg(i+4));
// query.bindValue(":namem",name);
// query.bindValue(":word",QString("123%1").arg(i));
// if(!query.exec())
// {
// db.rollback();
// }
// count++;
// //对1000求余
// if( i % 10 == 0)
// {
// db.commit();
// db.transaction();
// }
// }
// //如果还有剩余的插入,则提交剩下的事务
// if(count % 10 != 0)
// {
// db.commit();
// }
// db.close();
qDebug() << "当前时间:" <<QString::number(QDateTime::currentMSecsSinceEpoch());
由于100000条数据太大,这里改用100条数据。当使用一个一个数据插入时,耗时大概在387ms,而使用事务时,耗时在83ms,由此可见,事务的作用是非常大的。