Récupérer des objets complexes d'oracle depuis Java

Je vais indiquer, dans ce document, comment depuis une classe Java, récupérer un objet complexe SQL. Pour y parvenir je vais utiliser l'API JDBC (Java DataBase Connectivity) qui permet aux applications Java d'accéder par le biais d'une interface commune à des bases de données. Pour être le moins théorique possible, ce didacticiel consiste à réaliser un exemple.

1. Pré-requis

  • Vous êtes familier du SQL et du PL/SQL
  • Vous êtes familier de JDBC
  • Vous disposez d'une base de données Oracle 8i minimum
  • Vous disposez du JDK 8

2. Mapping Oracle vers Java et vice versa

Voici un tableau qui indique en fonction de ce qui est retourné par le SQL l'objet Java le mieux adapté :

Correspondance SQL - Java
Types SQL Types Java
CHAR String
VARCHAR2 String
LONG String
NUMBER java.lang.Byte, java.lang.Short, java.lang.Integer, java.lang.Long, java.lang.Float, java.lang.Double, java.math.BigDecimal, byte, short, int, long, float, double
RAW byte[]
LONGRAW byte[]
DATE java.sql.Date ou java.sql.Timestamp
ROWID oracle.sql.ROWID
REF CURSOR java.sql.ResultSet
BLOB oracle.sql.BLOB
CLOB oracle.sql.CLOB
BFILE oracle.sql.BFILE
Oracle object Une classe spécifié dans le « type map » sinon oracle.sql.STRUCT
Oracle object reference oracle.sql.REF
collection (varray ou nested table) oracle.sql.ARRAY

Je me concentre sur les types complexes, ce qui correspond aux lignes « Oracle object » et « REF CURSOR ». Pour comprendre ce didacticiel vous devez impérativement posséder les bases de JDBC c'est-à-dire savoir récupérer des types simples (VARCHAR2, NUMBER, DATE ...).

3. Les Oracle object

Les « Oracle object » sont des objets SQL. Ils peuvent être stockés dans des tables ou instanciés à la volée dans du PL/SQL. Pour illustrer cette partie, vous allez créer des objets dans la base de donnée. Vous allez ensuite les récupérer par JDBC et les stocker dans des JavaBeans. C'est le sens Oracle vers Java. Pour le sens Java vers Oracle vous allez instancier des JavaBean et vous allez insérer leurs données dans Oracle via JDBC.

3.1 Côté Oracle

Créez l'objet « PERSON » ainsi :

CREATE OR REPLACE TYPE "PERSON" as object (
firstname  varchar2(25),
name      varchar2(25),
birthyear  number
)

Ensuite créez la table « FRIENDS » qui contiendra des « PERSON » :

create table FRIENDS (
  f_id  number     primary key,
  f_pers PERSON         
)

Insérez dans la table « FRIENDS » quelques « PERSON » :

INSERT INTO FRIENDS VALUES (1,PERSON( 'Hyacinthe','MENIET'));
INSERT INTO FRIENDS VALUES (2,PERSON( 'Larry','ELLISON'));
INSERT INTO FRIENDS VALUES (3,PERSON( 'Bill','JOY'));

3.2 Côté Java

Créez la classe PersonBean ainsi :

package net.dotmyself.j2sql;

import java.io.Serializable;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;

/**
 * Maps Oracle "PERSON" object
 * @author Hyacinthe MENIET
 */
public class PersonBean implements SQLData, Serializable {
    
    private static final long serialVersionUID = 1L;
    
    private String firstname;
	private String name;
	private int birthYear;
     
    @Override
    public void readSQL(SQLInput stream, String typeName) throws SQLException {
        name = stream.readString();
	    firstname = stream.readString();
	    birthYear = stream.readInt();        
    }
    
    @Override
    public void writeSQL(SQLOutput stream) throws SQLException {
        stream.writeString(name);
	    stream.writeString(firstname);  
	    stream.writeInt(birthYear);        
    }
    
    @Override
    public String getSQLTypeName() throws SQLException {
        return "PERSON";
    }
    
    public int getBirthYear() {
        return birthYear;
    }
    
    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }
    
    public String getFirstname() {
        return firstname;
    }
    
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
}

A chaque « PERSON » chez Oracle correspondra un JavaBean « PersonBean ». Pour que ça fonctionne, PersonBean doit implémenter SQLData et Serializable. La méthode getSQLTypeName() indique le type SQL correspondant au JavaBean. La méthode writeSQL() est utilisée dans le sens Java vers Oracle et La méthode readSQL() dans l'autre sens. L'ordre de lecture et d'écriture dans « stream » est très important. Il correspond à celui que vous avez utilisé pour déclarer les variables dans « PERSON ».

3.3 Echange de données

Créez la classe SQL2Java ainsi :

package net.dotmyself.j2sql;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Creates JavaBeans which contain data from Oracle.
 *
 * @author Hyacinthe MENIET
 */
public class SQL2Java {

    public static void main(String[] args) {
        try {
            int pid;
            PersonBean person;
            Connection conn;
            conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.0.8.1:1520:JBD", 
                    "login", "password");
            Map<String, Class< ?>> map = conn.getTypeMap();
            map.put("PERSON", Class.forName("net.dotmyself.j2sql.PersonBean"));
            conn.setTypeMap(map);
            try (Statement stmt = conn.createStatement()) {
                ResultSet rs = stmt.executeQuery("SELECT f_id,f_pers FROM FRIENDS");
                while (rs.next()) {
                    pid = rs.getInt("F_ID");
                    person = (PersonBean) rs.getObject("F_PERS");
                    System.out.println(pid + ". " + person.getFirstname() + " " 
                            + person.getName() + " ( " + person.getBirthYear() + " )");
                }
                rs.close();
            }
            conn.close();
        } catch (SQLException | ClassNotFoundException ex) {
            Logger.getLogger(SQL2Java.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Cette classe instancie des JavaBean à partir de données Oracle. Elle réalise le sens Oracle vers Java.

Créez la classe Java2SQL ainsi :

package net.dotmyself.j2sql;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Creates Oracle objects which contain data from JavaBeans.
 * @author Hyacinthe MENIET
 */
public class Java2SQL {

    public static void main(String[] args) {
        try {
            PersonBean person = new PersonBean();
            Connection conn;
            conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.0.8.1:1520:JBD", "login", "password");
            Map<String, Class< ?>> map = conn.getTypeMap();
            map.put("PERSON", Class.forName("net.dotmyself.j2sql.PersonBean"));
            conn.setTypeMap(map);
            person.setBirthYear(2005);
            person.setFirstname("dot");
            person.setName("Myself");
            try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO FRIENDS VALUES (?,?)")) {
                pstmt.setInt(1, 4);
                pstmt.setObject(2, person);
                pstmt.executeUpdate();
            }
            conn.close();
        } catch (SQLException | ClassNotFoundException ex) {
            Logger.getLogger(Java2SQL.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Cette Classe créée des objets Oracle à partir de JavaBeans. Elle réalise le sens Java vers Oracle.

Il ne vous reste plus qu'à modifier les chaînes connections respectives et à les exécuter.

4. Oracle REF CURSOR

Les « Oracle REF CURSOR » sont des références sur des curseurs, ils s'utilisent généralement dans du code PL/SQL. Pour illustrer cette partie, vous allez créer une référence sur un curseur dans un package PL/SQL puis vous récupérerez les données référencées, dans une classe Java.

4.1 Côté Oracle

Créez une table « CONTACTS » ainsi :

create table CONTACTS (
  c_id  number     primary key,
  c_firstname varchar2(25),
  c_name varchar2(25)         
)

Insérez quelques contacts dans « CONTACTS » :

INSERT INTO CONTACTS VALUES (1,'Hyacinthe','MENIET');
INSERT INTO CONTACTS VALUES (2,'Larry','ELLISON');
INSERT INTO CONTACTS VALUES (3,'Bill','JOY');

Créez le package DEMO PL/SQL. La déclaration est la suivante :

CREATE OR REPLACE PACKAGE DEMO AS

TYPE ref_cursor IS REF CURSOR;
PROCEDURE get_contacts (p_ref OUT ref_cursor);

END DEMO;

Le body ressemble à ça :

CREATE OR REPLACE PACKAGE BODY DEMO AS

PROCEDURE get_contacts (p_ref OUT ref_cursor)
IS

BEGIN

  OPEN p_ref for
       SELECT c_id,c_firstname,c_name
        FROM CONTACTS
        ORDER BY c_name,c_firstname;
  
END get_contacts;

END DEMO;

4.2 Echange de données

Créez la classe Ref2Java qui permet d'appeler le code PL/SQL ci-dessus :

package net.dotmyself.j2sql;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Displays data from Oracle.
 * @author Hyacinthe MENIET
 */
public class Ref2Java {

    public static void main(String[] args) {
        try {
            Connection conn;
            conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.0.8.1:1520:JBD", 
                    "login", "password");
            CallableStatement cstmt;
            cstmt = conn.prepareCall("{call DEMO.get_contacts(?) }");
            cstmt.registerOutParameter(1, JDBCType.REF_CURSOR); // p_ref
            cstmt.executeQuery();
            try (ResultSet rs = (ResultSet) cstmt.getObject(1)) {
                while (rs.next()) {
                    System.out.println(rs.getInt("C_ID") + ". " + 
                            rs.getString("C_FIRSTNAME") + " " + rs.getString("C_NAME"));
                }
            }
            cstmt.close();
            conn.close();
        } catch (SQLException ex) {
            Logger.getLogger(Ref2Java.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Il ne vous reste plus qu'à modifier la chaîne de connexion et à exécuter la classe.

0 commentaire