ЗАНЯТИЕ №5



Изучение интерфейсов DataSource и ConnectionPoolDataSource.



Оба интерфейса входят в пакет javax.sql и должны быть реализованы поставщиками JDBC классов (драйверов). Основное назначение интерфейсов DataSource и ConnectionPoolDataSource состои в предоставлении возможности получения соединения с базой данных абстрагируясь от местоположения сервера СУБД и типа драйвера конкретного производителя. Интерфейсы определяют ряд обязательных для реализации методов, в том числе и метод getConnection(). Объекты, получаемые от реализации данных интерфейсов используются для задания параметров соединения с базой данных и установки соединения в виде объекта типа Connection.

JDBC DataSource объекты используются для получения физического соединения с базой данных и являются альтернативой DriverManager. При этом нет необходимости регистрировать драйвер. Необходимо только установить соответствующие параметры для установки соединения и выполнить метод getConnection(). При создании объекта типа DataSource локально (прямо в приложении) параметры соединения задаются соответствующими методами, предусмотренными поставщиком драйвера. Эти методы не определены интерфейсом DataSource, т.к. параметры для соединения с СУБД разных производителей могут отличаться как по типу, так и по количеству (например, не для всех СУБД необходимо указывать тип драйвера или сетевой протокол). Так при работе с СУБД Oracle для использования соответствующих set и get методов необходимо получить объект типа OracleDataSource, который является экземпляром одноименного класса, реализующего интерфейс DataSource. В следствии этого такой способ создания объектов типа DataSource делает программный код менее мобильным и зависимым от конкретной реализации драйвера. Ниже приведен пример программного кода, иллюстрирующий локальное использование объектов типа DataSource.

import java.sql.*;
import javax.sql.*;
import oracle.jdbc.driver.*;
import oracle.jdbc.pool.*;

public class DataSource {
    
        
    public static void main(String[] args) {
        try {
            OracleDataSource ods = new OracleDataSource();
            
            ods.setUser("stud");
            ods.setPassword("stud");
            ods.setDriverType("thin");
            ods.setDatabaseName("orbis");
            ods.setServerName("localhost");
            ods.setPortNumber(1521);
            
            Connection conn = ods.getConnection();
            System.out.println("Connection successful!!!");
            
        } catch (SQLException se) {
            se.printStackTrace();
        }
        System.out.println("Goodbye!");
    }
    
} 



Полностью возможности объектов типа DataSource проявляются в сочетании с использованием интерфейса JNDI. Использование службы имен и каталогов позволяет хранить заранее созданные системным администратором объекты типа DataSource с предустановленными параметрами соединения. Ниже приведены некоторые стандартные имена свойств (параметров), разработанные компанией Sun:

Имя свойства    Тип данных Java      Назначение
-------------------------------------------------------------
databaseName    String               имя базы данных
serverName      String               имя сервера
user            String               имя пользователя (ID)
password        String               пароль пользователя
portNumber      Int                  номер порта сервера СУБД

Комбинирование интерфейсов DataSource и JNDI играет ключевую роль в разработке многоуровневых корпоративных приложений, основанных на компонентной технологии J2EE. В случае использования комбинации интерфейсов DataSource и JNDI в программном коде приложения необходимо только затребовать объект типа DataSource у службы имен и каталогов. При этом детали соединения и зависимый от конкретной реализации драйвера программный код оказывается скрытым от приложения в хранящемся в службе имен и каталогов объекте.

Таким образом, совместное использование объектов типа DataSource и JNDI предполагает наличие двух выполняемых независимо друг от друга шагов:

1. Необходимо сохранить именованный объект типа DataSource в службе имен и каталогов, используя метод Context.bind() пакета javax.naming.

2. Затребовать в приложении объект типа DataSource у службы имен и каталогов, использую метод Context.lookup(). После чего можно использовать его метод DataSource.getConnection() для получения соединения с базой данных.

Ниже приведен пример совместного использования интерфейса JNDI и объекта типа OracleDataSource. В качестве службы имен и каталогов используется файловая система и необходимый для этого SPI (fscontext.jar и providerutil.jar).

import java.sql.*;
import javax.sql.DataSource;
import oracle.jdbc.pool.OracleDataSource;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.InitialContext;
import java.util.Hashtable;

public class JndiDataSource{
    
    static Connection conn = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    static Context ctx = null;
    static DataSource ds = null;

    public static void main (String args []){

        // Initialize the Context
        String sp = "com.sun.jndi.fscontext.RefFSContextFactory";
        String file = "file:JNDI";
        String dataSourceName = "myDatabase";
        try {
            //Create Hashtable to hold environment properties
            //then open InitialContext
            Hashtable env = new Hashtable();
            env.put (Context.INITIAL_CONTEXT_FACTORY, sp);
            env.put (Context.PROVIDER_URL, file);
            ctx = new InitialContext(env);

            //Bind the DataSource object
            bindDataSource(ctx, dataSourceName);

            //Retrieve the DataSource object
            DataSource ds = null;
            ds = (DataSource) ctx.lookup(dataSourceName);

            //Open a connection, submit query, and print results
            Connection conn = ds.getConnection();
            Statement stmt = conn.createStatement();
            String sql = "SELECT count(*) FROM all_tables";
            ResultSet rs = stmt.executeQuery(sql);
            System.out.println("The result is: ");
            while(rs.next())
                System.out.println(rs.getInt(1));

            // Close the connections to the data store resources
            ctx.close();
            rs.close();
            stmt.close();
            conn.close();
        }catch (NamingException ne){
            ne.printStackTrace();
        }catch (SQLException se){
            se.printStackTrace();
        //ensure all resources are closed
        }finally{
            try{
                if(ctx!=null)
                    ctx.close();
            }catch (NamingException ne){
                ne.printStackTrace();
            }finally{
                try{
                    if(conn!=null)
                        conn.close();
                }catch (SQLException se){
                    se.printStackTrace();
                }
            }
        }
    System.out.println("Goodbye!");
    }

    //Method to bind DataSource object

    public static void bindDataSource(Context ctx, String dsn)
        throws SQLException, NamingException{

        //Create an OracleDataSource instance
        OracleDataSource ods = new OracleDataSource();

        //Set the connection parameters
        ods.setUser("stud");
        ods.setPassword("stud");
        ods.setDriverType("thin");
        ods.setDatabaseName("orbis");
        ods.setServerName("localhost");
        ods.setPortNumber(1521);

        //Bind the DataSource
        ctx.rebind (dsn,ods);
    }
}

Приложение начинается с инициализации объекта типа InitialContext, который открывает соединение со службой имен и каталогов, задает начальный контекст (начальный каталог дерева хранения объектов) и используется для загрузки объекта типа DataSource. Далее используется метод bindDataSource() для сохранения объекта типа DataSource в службе имен и каталогов.

Для демонстрации работы с сохраненным объектом создается переменная ds типа DataSource. Полиморфизм позволяет назначать ей экземпляры любых классов, реализующих интерфейс DataSource, в нашем случае это объект типа OracleDataSource. Метод lookup() запрашивает сохраненный объект типа OracleDataSource и возвращает результат типа Object, по этому его надо преобразовать в тип DataSource. Получив требуемый объект типа DataSource можно использовать его метод getConnection() для получения соединения с базой данных.

Объекты типа ConnectionPoolDataSource используются для получения объектов типа PooledConnection, которые представляют физическое соединение с базой данных и являются поставщиками объектов типа Connection. Сам по себе интерфейс PooledConnection не предусматривает методов для создания объектов типа Statement, по этому необходимо использовать объекты типа Connection.

Объекты типа Connection, получаемые с помощью объектов типа PooledConnection представляют логическое соединение с базой данных. Они выполняют те же функции, что и объекты типа Connection, получаемые с использованием интерфейса DriverManager. Они используюся для создания объектов типа Statement, PrepareStatement или CollableStatement, и управляют уровнем транзакций. Однако, метод close() таких объектов не закрывает соединение с базой данных, а возвращает объект логического соединения обратно в пул для повторного использования его другими клиентами.

Ниже приведен пример использования локального объекта типа ConnectionPoolDataSource, требующего установки параметров соединения в программном коде приложения.

import java.sql.*;
import javax.sql.*;
import oracle.jdbc.pool.*;

public class ConnPool {

    //Global variables
    static OracleConnectionPoolDataSource ocpds = null;
    static PooledConnection pc_1 = null;
    static PooledConnection pc_2 = null;

    public static void main(String[] args){

        String jdbcUrl = "jdbc:oracle:thin:@localhost:1521:orbis";
        String user = "stud";
        String password = "stud";
        try{
            ocpds = new OracleConnectionPoolDataSource();
            ocpds.setURL(jdbcUrl);
            ocpds.setUser(user);
            ocpds.setPassword(password);

            //Create a pooled connection
            pc_1 = ocpds.getPooledConnection();

            //Open a Connection and a Statement object
            Connection conn_1 = pc_1.getConnection();
            Statement stmt = conn_1.createStatement();

            //Build query string
            String sql = "SELECT count(*) FROM v$session ";
            sql = sql + "WHERE username = 'STUD'";

            //Execute query and print results
            ResultSet rs = stmt.executeQuery(sql);
            rs.next();
            String msg = "Total connections after ";
            System.out.println(msg + "conn_1: " + rs.getString(1));

            ///Open second logical connection and execute query
            Connection conn_2 = pc_1.getConnection();
            stmt = conn_2.createStatement();
            rs = stmt.executeQuery(sql);
            rs.next();
            System.out.println(msg + "conn_2: " + rs.getString(1));

            //Open second physical connection and execute query.
            pc_2 = ocpds.getPooledConnection();
            rs = stmt.executeQuery(sql);
            rs.next();
            System.out.println(msg + "pc_2: " + rs.getString(1));

            //Close resources
            conn_1.close();
            conn_2.close();

        //Standard error handling.
        }catch(SQLException se){
            //Handle errors for JDBC
            se.printStackTrace();
        }catch(Exception e){
            //Handle errors for Class.forName
            e.printStackTrace();
        }finally{
            //Finally clause used to close resources
            try{
                if(pc_1!=null)
                    pc_1.close();
            }catch(SQLException se){
                se.printStackTrace();
            }finally{
                try{
                    if(pc_2!=null)
                        pc_2.close();
                }catch(SQLException se){
                    se.printStackTrace();
                }
            }
        }//end try
    System.out.println("Goodbye!");
    }
}

В примере создается объект pc_1 типа PooledConnection для поставки объектов типа Connection. Далее создаются два логических соединения conn_1 и conn_2. После их создания выводится количество физических соединения. Физическое соединение одно, не смотря на наличие двух открытых логических соединений. При создании второго объекта pc_2 типа PooledConnection физических соединений становится два.

Изучение использования объекта типа ConnectionPoolDataSource с предустановленными параметрами в комбинации с интерфейсом JNDI выносится на самостоятельную проработку.