Qt pour Android : Base de données MySQL

Version PDF de ce document : qt-android-base-donnees-mysql.pdf


L’application à développer devra manipuler des données à partir d’une base de données MySQL en utilisant des requêtes SQL.

Lire : Activité bases de données

MySQL est un serveur de bases de données relationnelles SQL (SGBDR).

Attention : Avec Qt pour Android, le plugin SQLite est présent par défaut mais ce n’est pas le cas pour MySQL. Cela nécessite de le fabriquer pour pouvoir l’utiliser sous Android.

MySQL et MariaDB

MySQL est un système de gestion de bases de données relationnelles (SGBDR). Il est distribué sous une double licence GPL et propriétaire. Il fait partie des logiciels de gestion de base de données les plus utilisés au monde, autant par le grand public (applications web principalement) que par des professionnels, en concurrence avec Oracle, Informix et Microsoft SQL Server. MySQL est un serveur de bases de données relationnelles SQL. Il est multi-thread et multi-utilisateur. MySQL fonctionne sur de nombreux systèmes d’exploitation différents, incluant Linux, Mac OS X et Windows. Les bases de données sont accessibles en utilisant les langages de programmation C, C++, VB, VB .NET, C#, Delphi/Kylix, Eiffel, Java, Perl, PHP, Python, Windev, Ruby et Tcl. Une API spécifique est disponible pour chacun d’entre eux. MySQL fait partie du quatuor LAMP : Linux, Apache, MySQL, PHP. Il appartient également à ses variantes WAMP (Windows) et MAMP (Mac OS).

MariaDB est un système de gestion de base de données édité sous licence GPL. Il s’agit d’un fork communautaire de MySQL. C’est le fondateur de MySQL qui a lancé le projet MariaDB suite au rachat de MySQL par Sun Microsystems.

Installation du serveur MySQL

Vérifier si les paquetages mysql (mysql-server, etc …) sont installés sur votre serveur :

$ dpkg -l | grep -i mysql

Sinon pour installer le SGBDR MySQL, il faut faire :

$ sudo apt install mysql-server

Vérifier si le serveur mysql est démarré sur votre machine :

$ sudo service mysql status

$ sudo systemctl status mysql.service

Pour démarrer le serveur MySQL :

$ sudo service mysql start

$ sudo systemctl start mysql.service

Pour redémarrer le serveur MySQL :

$ sudo service mysql restart

$ sudo systemctl restart mysql.service

Pour arrêter le serveur MySQL :

$ sudo service mysql stop

$ sudo systemctl stop mysql.service

Démarrer la console mysql :

$ mysql -uroot -ppassword -hlocalhost
mysql>

Lister les bases de données :

mysql> show databases;

Sélectionner une base de données :

mysql> use mysql;

Lister les tables d’une base de données :

mysql> show tables;

Sélectionner des données d’une table :

mysql> select Host, User from user;

Lien : https://doc.ubuntu-fr.org/mysql

Configuration du serveur MySQL

Si l’application s’exécutait sur la même machine que le serveur de base de données, il serait alors possible d’indiquer localhost comme nom de machine pour le serveur. Pour un terminal Android, il faudra préciser l’adresse IP (ou le nom) du serveur à joindre et configurer un accès distant sur le serveur MySQL.

  1. Éditer le fichier le fichier /etc/mysql/my.cnf ou /etc/mysql/mysql.conf.d/mysqld.cnf : dans la section [mysqld], indiquer pour le paramétre bind-address l’adresse IP de votre interface d’écoute ou la valeur 0.0.0.0 pour toutes les interfaces réseau de votre serveur.
$ sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
...
bind-address = 0.0.0.0
...
  1. Ajouter un utilisateur (ici android avec le mot de passe password !) pour lequel vous autorisez l’accès distant (% = pour tous les hôtes par exemple !) à cette base de données :
$ mysql -uroot -ppassword -hlocalhost

mysql> use mysql;

mysql> CREATE USER 'android'@'%' IDENTIFIED BY 'password';

mysql> GRANT ALL PRIVILEGES ON mabase.* TO 'android'@'%';

mysql> FLUSH PRIVILEGES;
  1. Redémarrer le service MySQL :
$ sudo systemctl restart mysql.service

Remarque : en cas de problème, vérifier si un pare-feu (firewall) est actif et si les connexions entrantes sur le port 3306 (port d’écoute par défaut du serveur MySQL) ne sont pas bloquées (vous pouvez utiliser l’outil nmap).

Vérifications :

// pare-feu actif ?
$ systemctl status ufw.service

// tables de filtrage ?
$ sudo iptables -L

// scan du port d'écoute MySQL ?
$ nmap -A -p3306 -T4 localhost

Fabrication du plugin MySQL (sous Linux Ubuntu)

Lien : Build_Qt_5_MySQL_Plugin_for_Android

On choisit l’option Using mariadbclient (Option 2) en utilisant les ressources disponibles ici.

La version de Qt utilisée pour cet exemple est la 5.10.1. La fabrication du plugin nécessite une installation de Qt5 pour Android fonctionnelle et de disposer des sources de Qt.

On utilisera le script build_qt_mysql_driver.sh qu’il faudra paramétrer en fonction de son installation :

#!/bin/bash
export NDK_ROOT="/home/tv/Android/Sdk/android-ndk-r10e"
export QT_ROOT="/opt/Qt5.10.1"
SR="$NDK_ROOT/platforms/android-19/arch-arm"
BR="$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86/bin/arm-linux-androideabi-"
platform=android-armv7
platform_sort=arm
qmake="$QT_ROOT/5.10.1/android_armv7/bin/qmake"

...

Le script va successivement télécharger et compiler :

$ sudo ./build_qt_mysql_driver.sh

Si tout se passe bien, on obtient la bibliothèque pour MySQL libqsqlmysql.so dans /opt/Qt5.10.1/5.10.1/android_armv7/plugins/sqldrivers/. Celle-ci sera automatiquement ajoutée lors de la fabrication de l’apk de l’application pour Android.

Par contre, elle aura besoin d’accéder à la bibliothèque libmariadb.so pour faire fonctionner le plugin. Celle-ci a été fabriquée dans mariadb-connector-c-2.3.2-src/build/libmariadb et on va la déployer avec l’application en ajoutant au fichier .pro :

...
contains(ANDROID_TARGET_ARCH,armeabi-v7a) {
    ANDROID_EXTRA_LIBS = \
        $$PWD/android/libs/arm/libmariadb.so
}

Remarque : Le répertoire $$PWD/ est celui qui contient le code source du projet.

Vous retrouvez ces deux bibliothèques dans l’archive de l’exemple MonApplicationBDMySQLCharts.zip.

API Qt

Pour rappel, Qt fournit de nombreuses classes pour la gestion des base de données. Il faudra activer le module dans son fichier de projet .pro pour pouvoir accéder aux classes :

...
QT += sql
...

On utilisera alors la classe QSqlDatabase qui permet la connexion à une base de données.

Lien : La classe QSqlDatabase Qt5 (en)

Et ensuite la classe QSqlQuery pour exécuter des requêtes SQL.

Lien : La classe QSqlQuery Qt5 (en)

Exemple

On reprend l’exemple utilisé avec Qt Charts et SQLite que l’on va adapter à MySQL.

On doit disposer d’une base de données mabase comprenant 2 tables :

  • la table releves qui contiendra la description des relevés
  • la table mesures qui contiendra les mesures horodatées de températures pour chaque relevé
CREATE DATABASE IF NOT EXISTS `mabase`;

USE `mabase`;

CREATE TABLE IF NOT EXISTS releves ( 
        idReleve int(10) NOT NULL AUTO_INCREMENT,
        description VARCHAR(255) NULL,
        PRIMARY KEY (`idReleve`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

CREATE TABLE IF NOT EXISTS mesures ( 
        idMesure int(10) NOT NULL AUTO_INCREMENT,
        idReleve int(10) NOT NULL,
        horodatage datetime,
        temperature double NULL ,
        PRIMARY KEY (`idMesure`),        
        CONSTRAINT fk_mesures_1 FOREIGN KEY (`idReleve`) REFERENCES releves(idReleve) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Pour les tests, on va insérer quelques mesures :

INSERT INTO releves(idReleve,description) VALUES(1,'Relevé de la serre 1');
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 08:00:00',35.23);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 08:30:00',35.1);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 09:00:00',34.45);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 09:30:00',35.02);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 10:00:00',35.53);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 10:30:00',35.24);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 11:00:00',35.25);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 11:30:00',35.7);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 12:00:00',35.61);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 12:30:00',35.65);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 13:00:00',35.75);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 13:30:00',36.03);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 14:00:00',36.1);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 14:30:00',36.05);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 15:00:00',36.33);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 15:30:00',36.5);

INSERT INTO releves(idReleve,description) VALUES(2,'Relevé de la serre 2');
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 08:00:00',35.1);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 08:30:00',35.15);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 09:00:00',35.25);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 09:30:00',35.05);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 10:00:00',35.35);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 10:30:00',35.24);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 11:00:00',35.65);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 11:30:00',35.7);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 12:00:00',35.71);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 12:30:00',35.75);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 13:00:00',35.65);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 13:30:00',35.93);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 14:00:00',36.1);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 14:30:00',36.15);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 15:00:00',36.4);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 15:30:00',36.35);

Et quelques requêtes SQL que l’on utilise dans le code :

// la liste des relevés
SELECT description FROM releves ORDER BY releves.description ASC
// Les 5 dernières mesures du relevé 1
SELECT * FROM (SELECT mesures.horodatage, mesures.temperature FROM mesures INNER JOIN releves ON releves.idReleve = mesures.idReleve WHERE releves.description = 'Relevé de la serre 1' ORDER BY mesures.horodatage DESC LIMIT 5) tmp ORDER BY horodatage ASC LIMIT 5
// La moyenne des 5 dernières mesures du relevé 1
SELECT AVG(temperature) FROM (SELECT mesures.horodatage, mesures.temperature FROM mesures INNER JOIN releves ON releves.idReleve = mesures.idReleve WHERE releves.description = 'Relevé de la serre 1' ORDER BY mesures.horodatage DESC LIMIT 5) tmp ORDER BY horodatage ASC LIMIT 5

Projet Qt

On crée un projet Qt QML Quick avec le fichier .pro :

QT += widgets quick quickcontrols2 sql charts
CONFIG += c++11

HEADERS += \
    releve.h \
    mesure.h

SOURCES += main.cpp \
    releve.cpp \
    mesure.cpp

RESOURCES += qml.qrc \
             icons/MonApplicationBDCharts/index.theme \
             $$files(icons/*.png, true)

unix:!macx:
{
    android:
    {
        DISTFILES += \
            android-sources/AndroidManifest.xml
        ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources
        contains(ANDROID_TARGET_ARCH,armeabi-v7a) {
            ANDROID_EXTRA_LIBS = \
                $$PWD/android/libs/arm/libmariadb.so
        }
    }
}

Base de données MySQL

Dans la classe Releve, on assurera la connexion à la base de données MySQL avec la méthode connecterBD().

On précisera tout d’abord avec la méthode statique addDatabase() le type QMYSQL.

Ensuite, on fixera les paramètres de connexion avec : setHostName(), setUserName(), setPassword() et setDatabaseName(). Au final, on effectue la connexion avec open().

Par exemple :

...
qDebug() << Q_FUNC_INFO << QSqlDatabase::drivers(); // -> ("QSQLITE", "QMYSQL", "QMYSQL3")

db = QSqlDatabase::addDatabase("QMYSQL");

db.setHostName("192.168.52.12");
db.setUserName("android");
db.setPassword("password");
db.setDatabaseName("mabase");

if(db.open())
{
    erreurConnexion = false;
}
else
{
    erreurConnexion = true;
    qDebug() << Q_FUNC_INFO << QString::fromUtf8("Erreur : impossible de se connecter à la base de données !");
}

emit erreurChanged();
...

Le signal erreurChanged() est utilisé pour prévenir l’utilisateur si la connexion a échoué.

L’explication de l’utilisation des reqêtes SQL est décrite dans l’exemple SQLite.

L’IHM

L’interface utilisateur sera définie en QML avec Qt Quick Controls 2. La structure est celle utilisée et décrite dans l’exemple Qt Charts.

On va modifier la GUI pour ajouter une boîte de dialogue de connexion au démarrage qui permettra d’appeler la méthode connecterBD() avec les informations de connexion saisies par l’utilisateur :

ApplicationWindow {
    id: window
    title: qsTr("Mon MonApplicationBD MySQL")
    visible: true

    // au chargement de l'application, on affiche la fenêtre de connexion
    Component.onCompleted: {
        fenetreConnexion.open()
    }
    //  la fenêtre de connexion
    Dialog {
        id: fenetreConnexion
        x: (window.width - width) / 2
        y: window.height / 12
        focus: true
        modal: true
        title: "Base de données MySQL"
        standardButtons: Dialog.Ok | Dialog.Cancel
        ColumnLayout {
            spacing: 20
            anchors.fill: parent
            Label {
                elide: Label.ElideRight
                text: "Entrer les informations de connexion :"
                Layout.fillWidth: true
            }
            TextField {
                id: serveur
                focus: true
                placeholderText: "Serveur"
                inputMethodHints: Qt.ImhFormattedNumbersOnly // pour une adresse IP seulement
                Layout.fillWidth: true
            }
            TextField {
                id: utilisateur
                placeholderText: "Utilisateur"
                Layout.fillWidth: true
            }
            TextField {
                id: password
                placeholderText: "Mot de passe"
                echoMode: TextInput.Password
                Layout.fillWidth: true
            }
        }
        onAccepted: {
            if(Releve.connecterBD(serveur.text, utilisateur.text, password.text))
            {
                // on crée la page Releve et on l'affiche
                var component = Qt.createComponent("Releves.qml");
                if (component.status == Component.Ready)
                {
                     var page = component.createObject(vueReleve);
                     if (page == null)
                     {
                         console.log("Erreur création Releves.qml !");
                     }
                     else
                     {
                         // on désavtive l'entrée de menu
                         connexion.enabled = false
                     }
                }
                else if (component.status == Component.Error)
                {
                     console.log("Erreur chargement Releves.qml :", component.errorString());
                }
            }
        }
    }
    ...
    Item {
        id: vueReleve
        anchors.fill: parent
    }
}

Captures d’écran

On obtient :

Code source

Lien : MonApplicationBDMySQLCharts.zip

Voir aussi

Qt pour Android :

http://tvaira.free.fr/