.% Développement Android : Activité n°7 (Base de données MySQL) % Thierry Vaira <tvaira@free.fr> http://tvaira.free.fr/ % 2016-2019 (rev. 1)

Activité n°7 : Base de données MySQL

Objectif

L’objectif est d’écrire une application Android qui accède une base de données MySQL.

De nombreuses applications manipulent des données et il est souvent nécessaire des les stocker dans des bases de données. Ces base de données peuvent être exploités par des requêtes SQL.

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

Pré-requis

Voir aussi

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 d’un 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

Un exemple de base de données

On va créer une base de données ruche comprenant une table décrite dans le fichier ruche.sql :

DROP DATABASE IF EXISTS `ruche`;

CREATE DATABASE IF NOT EXISTS `ruche`;

USE `ruche`;

--
-- Structure de la table `Ruche`
--

CREATE TABLE IF NOT EXISTS `Ruche` (
  `idRuche` int(11) NOT NULL AUTO_INCREMENT,
  `Nom` varchar(255) NOT NULL,
  `Description` varchar(255) DEFAULT NULL,
  `DateMiseEnService` date NOT NULL,
  `Adresse` varchar(17) DEFAULT NULL,
  `Longitude` varchar(15) DEFAULT NULL,
  `Latitude` varchar(15) DEFAULT NULL,
  PRIMARY KEY (`idRuche`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `Ruche` (`Nom`,`Description`,`DateMiseEnService`,`Adresse`,`Longitude`,`Latitude`) VALUES('Ruche 1','...','2015-03-29','','-4.5°', '43.9°');
$ mysql -uroot -ppassword -hlocalhost < ruche.sql

Activité n°7

AndroidManifest.xml

L’accès à une base de données MySQL suppose que l’on ait un accès réseau. Pour cela, on va attribuer les permissions INTERNET, ACCESS_NETWORK_STATE et ACCESS_WIFI_STATE à l’application Android dans son fichier AndroidManifest.xml.

Exemple :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.tv.monapplicationmysql">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Remarque : l’absence de ces permissions entraînera une exception Permission denied au moment de la connexion.

JDBC

On va utiliser JDBC (Java DataBase Connectivity), une API permettant l’accès aux bases de données en Java.

Les classes de JDBC sont regroupées dans le package java.sql. Les classes principales sont :

  • DriverManager : pour charger et configurer le driver de la base de données
  • Connection : pour se connecter et s’authentifier à une base de données
  • Statement : pour exécuter une requête SQL
  • ResultSet : pour parcourir les résultats retournés par une requête SQL
  • ResultSetMetaData : pour obtenir des informations sur les colonnes (nom et type) d’un ResultSet
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;

Pour pouvoir utiliser JDBC, il faut un pilote qui est spécifique à la base de données à laquelle on veut accéder.

Pour MySQL, il faut télécharger mysql-connector.jar puis l’ajouter à votre projet dans AndroidStudio :

C’est la classe DriverManager qui gère l’établissement des connexions. Il faut lui spécifier le(s) pilote(s) JDBC que l’on utilisera.

La méthode la plus simple consiste à utiliser Class.forName() sur la classe qui implémente l’interface java.sql.Driver. Le nom de cette classe est com.mysql.jdbc.Driver :

Class.forName("com.mysql.jdbc.Driver"); // chargement du pilote MySQL

Une fois le pilote enregistré auprès de DriverManager, on pourra obtenir une instance de connexion en appelant DriverManager.getConnection().

Pour se connecter à une base de données MySQL, il faut donc instancier un objet de la classe Connection. Le nom de la base de données doit être indiqué dans l’URL passé en argument ainsique que le compte utilisateur.

Exemple :

String url = "jdbc:mysql://192.168.52.12/mabase";

Connection conMySQL = DriverManager.getConnection(url, "root", "password"));

Liens : FAQ JDBC et JDBC Tutorial

Exécuter des requêtes SQL

Une fois la connexion établie, il est possible d’exécuter des requêtes SQL. Il faut un objet de la classe Statement pour envoyer des requêtes SQL à la base de données. L’instanciation d’un objet Statement est réalisée en appelant createStatement() de l’objet Connection. Pour exécuter une requête SQL de type SELECT, on appelle ensuite executeQuery() qui retourne un ResultSet contenant les résultats de la requête.

ResultSet resultats = null;
String requete = "SELECT Nom FROM Ruche WHERE idRuche='1'";

Statement st = conMySQL.createStatement();

// Executer une requête
resultats = st.executeQuery(requete);

// Parcourir les résultats
while (resultats.next()) 
{
    System.out.println(resultats.getString(1).toString() + "\n"); // colonne 1
}

Les principales méthodes pour obtenir des données à partir d’un ResultSet sont :

  • getInt(int) et getInt(String) : retourne le contenu de la colonne sous la forme d’un entier
  • getFloat(int) et getFloat(String) : retourne le contenu de la colonne la forme d’un nombre flottant
  • getDate(int) et getDate(String) : retourne le contenu de la colonne la forme d’une date

Pour les méthodes précédentes, on passera en paramètre le numéro de colonne (int) ou son nom (String).

Quelques autres méthodes utiles :

  • next() : pour se déplacer sur le prochain enregistrement (false si la fin est atteinte)
  • close() : pour fermer le traitement du ResultSet
  • getMetaData() : pour récupérer un objet de type ResultSetMetaData associé au ResultSet
ResultSetMetaData rsmd = resultats.getMetaData();
int nbColonnes = rsmd.getColumnCount();

while (resultats.next()) 
{
   for (int i = 1; i <= nbColonnes; i++)
      System.out.print(resultats.getString(i) + " ");
   System.out.println();
}

resultats.close();

Pour exécuter une requête de type INSERT (ou UPDATE ou DELETE), on utilisera la méthode executeUpdate() de la classe Statement

String requete = "UPDATE Ruche SET Description = 'ma ruche' WHERE idRuche='1'";
//String requete = "INSERT INTO Ruche (Nom,Description,DateMiseEnService,Adresse,Longitude,Latitude) VALUES('Ruche 2','...','2015-03-29','','-4.5°', '43.9°');";
//String requete = "DELETE FROM Ruche WHERE idRuche='2'";

Statement st = conMySQL.createStatement();

// Executer la requête
int ret = stmt.executeUpdate(requete);

Liens : FAQ JDBC et JDBC Tutorial

Tâche d’arrière plan

Pour rappel sous Android, on ne peut pas effectuer des traitements consommateurs en temps dans le thread UI car celui-ci se “figerait” et ne répondrait plus aux actions de l’utilisateur. D’autre part si une activité réagit en plus de cinq secondes, elle sera tuée par l’ActivityManager qui la considérera comme morte.

Il faudra donc créer et exécuter des threads d’arrière-plan pour les traitements lourds et/ou lents comme une connexion à une base de données.

Lien : Tâches d’arrière-plan

Depuis la version 1.5 d’Android, la classe AsyncTask fournit une nouvelle façon d’effectuer des tâches en arrière-plan.

Elle s’exécutera dans deux threads distincts : le thread UI et le thread de traitement.

Les méthodes à redéfinir de la classe AsynchTask sont :

  • doInBackground() est la méthode qui s’exécute dans un autre thread. Seule cette méthode est exécutée dans un thread à part, les autres méthodes s’exécutent dans le thread UI.

  • onPreExecute() est appelée par le thread UI avant l’appel à doInBackground().

  • onPostExecute() est appelée lorsque la méthode doInBackground() est terminée.

  • onProgressUpdate() est appelée par la méthode publishProgress() à l’intérieur de la méthode doInBackground().

Lien : AsyncTask

Le principe est le suivant :

public class MainActivity extends AppCompatActivity 
{
    TextView txtResultats;
    
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.MainActivity);
        
        txtResultats = (TextView) this.findViewById(R.id.txtResultats);
        // ...
    }
    
    public void accederMySQL() 
    {
        AccesMySql accesMySql = new AccesMySql();
        accesMySql.execute("");
    }
   
    private class AccesMySql extends AsyncTask<String, Void, String> 
    {
        String resultats = "";

        @Override
        protected void onPreExecute() 
        {
            super.onPreExecute();
            Toast.makeText(MainActivity.this, "...", Toast.LENGTH_SHORT).show();
        }

        @Override
        protected String doInBackground(String... params) 
        {
            try 
            {
                Class.forName("com.mysql.jdbc.Driver");
                Connection conMySQL = DriverManager.getConnection("jdbc:mysql://192.168.52.12/mabase", "root", "password"));
                String message = "Connexion réussie !\n";
                Statement st = conMySQL.createStatement();
                ResultSet rs = st.executeQuery("SELECT description FROM releves ORDER BY releves.description ASC");

                while (rs.next()) 
                {
                    message += rs.getString(1).toString() + "\n";
                }
                
                resultats = message;
            } 
            catch (Exception e) 
            {
                e.printStackTrace();
                res = e.toString();
            }
            
            return resultats;
        }

        @Override
        protected void onPostExecute(String result) 
        {
            txtResultats.setText(resultats);
        }
    }
}

Exemple Android

Code source : MonApplicationMySQL.zip

Retour