ЗАНЯТИЕ №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 выносится на самостоятельную проработку.