Armazenando Arquivos em Banco de Dados MySQL com Qt

Durante o desenvolvimento de uma aplicação desktop com Qt, surgiu a necessidade de salvar arquivos gerados pelo programa diretamente no banco de dados MySQL.

Inicialmente, tentei ler o arquivo para uma variável do tipo QByteArray e inserir em um campo do tipo mediumblob, porém os dados sempre apareciam como NULL na tabela. Também tentei utilizar QDataStream para serializar os dados, mas o programa travava ao lidar com arquivos grandes. Ao verificar as configurações, descobri que o parâmetro max_allowed_packet no arquivo my.ini estava limitado a 16M. Alterei para 40M e mudei o tipo do campo para longblob, mas os dados continuavam sendo inseridos como NULL.

Pesquisando uma solução, encontrei uma referência sobre a função load_file() do MySQL:

SELECT LOAD_FILE('C:/ProgramData/MySQL/MySQL Server 8.0/Uploads/pic/11.jpg') AS RESULT;

Ao tentar executar, o resultado era vazio. Verifiquei então o parâmetro secure_file_priv:

show variables like 'secure_file_priv';

O valor retornado era um caminho de diretório específico, o que significa que apenas arquivos daquele local podem ser importados ou exportados. Se o valor fosse NULL, a importação e exportação seriam proibidas. Se fosse vazio, não haveria restrições.

Como o valor era um diretório específico, tentei modificar o arquivo my.ini adicionando a linha secure_file_priv = na seção [mysqld]. Mesmo assim, o valor não mudou. Pesquisei que era necessário reiniciar o MySQL, tentei o comando systemctl restart mysql.service sem sucesso. Encontrei uma alternativa acessando os serviços do Windows através do menu de contexto do "Meu Computador", mas ao parar o serviço, não consegui mais iniciá-lo.

Após pesquisar uma solução, o processo equivalia a reinstalar o MySQL do zero, conforme os passos abaixo:

  1. No diretório de instalação do MySQL (C:\Program Files\MySQL\MySQL Server 8.0), criei uma pasta data no mesmo nível do diretório bin (caso não existisse).
  2. Executei o comando de inicialização no prompt de comando (como administrador):
mysqld –initialize

  1. Instalei o serviço MySQL:
mysqld --install mysql

  1. Iniciei o serviço:
net start mysql

  1. Ao conectar ao banco, a senha enicial aleatória foi gerada no arquivo com extensão .err dentro da pasta data:
mysql –u root -p

  1. Alterei a senha do root:
alter user 'root'@'localhost' identified by 'senha_nova'; 

  1. Atualizei as permissões:
flush privileges;

Após recuperar o banco de dados, o objetivo era deixar secure_file_priv vazio para remover as restrições. Porém, após diversas tentativas, não consegui alterar esse valor.

Testei então a abordagem com load data local infile:

SHOW GLOBAL VARIABLES LIKE 'local_infile';

O valor inicial era OFF, precisei habilitar:

SET GLOBAL local_infile = true;

Depois tentei executar:

load data local infile 'D:\11.txt' into table user;

Essa abordagem não funcionou pois é para importação de dados, não de arquivos binários.

Por fim, testei o código original novamente e funcionou corretamente. Após a reinstalação, o valor de max_allowed_packet estava maior, cerca de 60M, superior ao que havia configurado anteriormente (40M).

Verifiquei com o comando:

show global variables like 'max_allowed%';

O problema foi resolvido, aparentemente pela reinstallação do MySQL.

Código Qt para inserção de arquivo:

void GerenciadorDados::salvarArquivo(QString nomeArquivo, QString caminho, QString categoria)
{
    QFile arquivo(caminho);
    if (!arquivo.open(QIODevice::ReadOnly))
        return;
    
    QByteArray dadosBinarios = arquivo.readAll();
    arquivo.close();

    QSqlQuery queryInserir;
    queryInserir.prepare("INSERT INTO `arquivos-tabela`(nome, categoria, conteudo, dataCriacao) VALUES(:nome, :categoria, :conteudo, :data);");
    queryInserir.bindValue(":nome", nomeArquivo);
    queryInserir.bindValue(":categoria", categoria);
    queryInserir.bindValue(":conteudo", dadosBinarios);
    queryInserir.bindValue(":data", QDateTime::currentDateTime());
    
    bool resultado = queryInserir.exec();
    if (!resultado)
    {
        qDebug() << queryInserir.lastError().text();
    }
}

Código para recuperação do arquivo:

void GerenciadorDados::recuperarArquivo()
{
    QSqlQuery query;
    bool ok = query.exec("select * from `arquivos-tabela`;");
    
    QByteArray dadosRecuperados;
    query.next();
    dadosRecuperados = query.value("conteudo").toByteArray();
    qDebug() << query.lastError().text();

    QFile arquivoSaida("D:\\Dados\\output.tkb");
    if (!arquivoSaida.open(QIODevice::WriteOnly))
        return;
    arquivoSaida.write(dadosRecuperados);
    arquivoSaida.close();
}

A recuperação funciona perfeitamante. Inicialmente havia utilizado QDataStream, que adicionava bytes extras no início do arquivo. Utilizando a escrita direta, o processo é mais rápido e sem dados corrompidos.

Tags: MySQL Qt C++ blob database

Publicado em 7-13 03:00