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 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.
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
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.
/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
...
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;
$ 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
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.
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)
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 :
releves
qui contiendra la description des relevésmesures
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
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
}
}
}
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’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
}
}
On obtient :
Lien : MonApplicationBDMySQLCharts.zip
Qt pour Android :