Professional Documents
Culture Documents
Informacin de los autores: David Alonso Moro Estudiante de Ingeniera Tcnica en Informtica de Sistemas Facultad de Ciencias Universidad de Salamanca davidalonso_mad@hotmail.com Arstides Garcs Sanz Estudiante de Ingeniera Tcnica en Informtica de Sistemas Facultad de Ciencias Universidad de Salamanca aristidesgarces@hotmail.com Este documento puede ser libremente distribuido. 2002 Departamento de Informtica y Automtica Universidad de Salamanca.
Resumen
El principal objetivo de este documento es presentar al lector las tecnologas de Java JSP y JDBC y como la segunda completa a la primera para desarrollar aplicaciones Web. La primera parte de este documento est pensada para ensear la evolucin de los lenguajes de Internet hasta llegar a JSP y a partir de aqu enumerar las ventajas y funcionamiento de esta nueva tecnologa. En la ltima parte se explica JDBC como una interfaz que permite a los programadores obtener las ventajas de un gestor de bases de datos (SGBD) desde Internet, usando un lenguaje orientado a objetos y sin preocuparse por el tipo de base de datos, ni de su protocolo nativo
Abstract
The mean goal of this document is to present the JSP and JDBC Java technologies to the readers and how the second one completes the former one in order to make Web aplications. The first part of this document is thought showing people the evolution of the Internet languages until JSP and from this point enumerates the advantages of this new technology and the way that it works. In the last part its explained JDBC as an interfaz that lets programmers to get the advantages of a DBMG from Internet in an object oriented language and without worrying about the kind of the data base and his native protocol.
Tabla de Contenidos
1. Historia de la evolucin Web hasta JSP____________________________________1 2. JSP ________________________________________________________________2 2.1. Qu es JSP? ____________________________________________________2 2.2. Cmo funciona? _________________________________________________3 2.3. Elementos de JSP _________________________________________________4 2.4. Conclusiones ____________________________________________________5 3. JDBC ______________________________________________________________6 3.1. Introduccin _____________________________________________________6 3.2. Qu es JDBC ___________________________________________________6 3.3. Arquitectura _____________________________________________________6 3.4. Clases esenciales _________________________________________________8 3.5. Controladores ____________________________________________________8 3.6. Pasos Bsicos ___________________________________________________10 3.6.1. Cargar la clase del controlador _________________________________10 3.6.2. Abrir la conexin a la base de datos______________________________11 3.6.3. Ejecutar consultas contra la base de datos _________________________12 3.6.4. Procesar los resultados ________________________________________13 3.6.5. Cerrar la conexin a la base de datos _____________________________14 4. Ejemplo de pgina JSP que usa JDBC____________________________________14 5. Conclusin _________________________________________________________16 6. Referencias_________________________________________________________17 7. Bibliografa recomendada _____________________________________________17
ndice de Figuras
Figura 1. Esquema de funcionamiento de CGI__________________________________1 Figura 2. Lgica aplicada por el contenedor para gestionar la conversin JSP _________3 Figura 3. Pasos Bsicos de JDBC____________________________________________7 Figura 4. Arquitectura de JDBC _____________________________________________7 Figura 5. Estructura de los 4 tipos de controladores JDBC _______________________10
ii
<HTML></HTML>
GET/cgi-bin/pgm
Base de datos
Los CGI fueron un gran avance, pero tienen un serio problema. Normalmente generan un nuevo proceso para cada peticin HTTP. Si un nmero de personas elevado solicitan la misma informacin al mismo tiempo, se cargarn en la memoria del servidor el mismo programa CGI tantas veces como personas hayan solicitado la informacin. Esto no supone ningn problema si el trafico es escaso pero con la envergadura de la red hoy en da, esto provoca sobrecarga y posiblemente la denegacin de servicios por parte del servidor. Como primera respuesta al problema de los CGI varios fabricantes de servidores crearon sus propias soluciones desarrollando APIs. Estas APIs permiten generar aplicaciones que se conectan directamente al servidor y se cargan en memoria una sola vez. De esta forma se puede crear aplicaciones Web muy rpidas. Pero al ser propietarias no eran portables de unas servidores a otros, por lo que en muchos casos la cantidad de cdigo a rescribir era muy significativa. En 1997 Sun lanz al mercado una solucin que tendra bastante aceptacin, los Servlets de Java. Esta solucin resuelve, al igual que las otras soluciones propietarias, el problema de sobrecarga de procesos en el servidor, ya que se carga una nica vez en memoria el Servlet y se
-1-
crean tantos hilos de ejecucin como solicitudes. Adems el API de Java est bien estandarizado y muy popularizado, de forma que al estar escritos en Java, son portables directamente a la mayora de servidores (I-planet, Apache, Microsoft IIS, WebStar...) y a los que no, lo son si se instala un plug-in de Java Servlets a ese servidor (Al estar tan popularizado el estndar hay plug-in para casi todos los servidores). Con esto ltimo se termina con el problema de las soluciones propietarias (para ms ventajas de Servlets respecto a CGI consultar la bibliografa [2]). Mientras Sun tambin gener otra solucin paralela, los Applets, que en un primer momento tuvieron mucha aceptacin ya que permita meter cdigo Java en una pgina Web hacindolas ms atractivas. Lo que hizo que Sun se dedicara a Servlets fue que los navegadores no eran compatibles directamente con esta tecnologa y otros eran incompatibles se hiciera lo que se hiciera. De esta forma se dej de invertir en Applets para destinar los fondos a los Servlets. Una vez que se empez a usar Servlets de forma masiva se vio que la creacin de HTML utilizando lneas y lneas de sentencias println() era cansado, problemtico y difcil de mantener. Adems los diseadores de pginas no son desarrolladores Java y no se les debera pedir que crearan aprendieran Java, ni que dependieran de que un desarrollador codificase en Java lo que ellos haban hecho en HTML. El problema se acrecentaba cuando el diseador quera modificar las pginas, prcticamente tocaba volver a hacer la pgina. Llegados a este punto la propia Sun creo las Java Server Pages, ms conocidas como JSP. Estas permiten independizar los aspectos antes planteados al tiempo que conservan todas y cada una de las ventajas de sus precursores, los Servlets.
Alonso y Garcs
En caso que se modifique el cdigo fuente JSP el contenedor vuelve a hacer todo el proceso de recompilado automticamente basndose en la situacin temporal del archivo. De esta forma aunque es muy costoso crear el Servlet cuando se hace la primera peticin, las posteriores peticiones no provocan volver a pasar por todos los pasos, por lo que es una buena prctica que el desarrollador visite la pgina por primera vez, para que cuando llegue el primer cliente obtenga una respuesta rpida. Las fases se distinguen mejor en el siguiente grfico:
No
Compilar el cdigo fuente del servlet en un archivo de clase No Cargar la clase y crear el ejemplar
Figura 2. Lgica aplicada por el contenedor para gestionar la conversin JSP 3 POO Trabajo Voluntario
Alonso y Garcs
Adems de estos elementos, JSP proporciona una serie de facilidades adicionales mediante los siguientes elementos: Extensiones de etiquetas: Refuerzan la arquitectura JSP posibilitando la ampliacin del entorno de creacin de pginas, permitiendo crear etiquetas propias. Objetos implcitos: Para simplificar el cdigo en expresiones y scriplets JSP, tenemos ocho variables definidas automticamente, que completan el entorno en el que operar. Se pueden utilizar en cualquier pgina nada ms que poniendo su nombre: o o o o request: Este es el HttpServletRequest asociado con la peticin, y permite mirar los parmetros de la peticin. response: Este es el HttpServletResponse asociado con la respuesta al cliente. pageContext: Medio de obtener acceso a la pgina, a la respuesta, a la sesin o a los atributos de la aplicacin. session: Este es el objeto HttpSession asociado con la peticin. Gracias al cual JSP permite el uso de sesiones, recordemos que HTTP es un protocolo sin estado y haba que ingenirselas para seguir la sesin mediante el uso de cookies, rescribiendo la URL o con campos de formulario ocultos. application: Este es el getServletConfig().getContext() . ServletContext obtenido mediante
o o o o
out: el objeto de flujo de salida de la respuesta JspWriter. config: el objeto de configuracin del Servlet. page: Esto es slo un sinnimo de this, y no es muy til en Java. Fue creado como situacin para el da que los lenguajes de script puedan incluir otros lenguajes distintos de Java. exception: Es una excepcin sin capturar vlida solo en pginas de error.
3.2. Qu es JDBC?
Primero cabe aclarar el significado de sus siglas. Se relaciona muy a menudo con el acrnimo ODBC por lo que se suele expresar como Java Database Connectivity pero oficialmente, segn Javasoft, JDBC no significa nada ni es acrnimo de nada. JDBC es una interfaz de programacin de aplicaciones (API de ingls Aplication Program Interface) entre los programas Java y sistemas RDBMS (Relational Database Management System) independiente de la plataforma y del gestor de bases de datos utilizado. Es una interfaz al nivel de llamada. Esto significa que un programa emplea llamadas a funciones o mtodos para tener acceso a las caractersticas del sistema gestor de bases de datos, en oposicin a las instrucciones SQL incrustadas, traducidas por el precompilador. Esto permite tener una, mayor flexibilidad a la hora de realizar peticiones, ya que no hace falta tener precompiladas todas y cada una de las posibles consultas en SQL que vayamos a usar.
3.3. Arquitectura
La interaccin tpica con una base de datos consta de los siguientes cuatro pasos bsicos: Abrir la conexin a la base de datos Ejecutar consultas contra la base de datos Procesar los resultados Cerrar la conexin a la base de datos
Pero JDBC abstrae al desarrolador del Sistema Gestor que tenga nuestra base de datos por lo que tendremos que cargar inicialmente el controlador que lleve a cabo dicha funcin, introduciendo as un nuevo paso anterior a todos los dems Cargar la clase del controlador JDBC
Alonso y Garcs
2. Abrir una conexin a una base de datos: Connection con= DriverManager.getConnection (jdbc:xxx:FuentedeDatos); 3. Emitir instrucciones SQL: stmt = con.CreateStatement(); Rs=stmt.executeQuery (SELECT * FROM miTabla);
Instruccin
Conjunto de resultados
JDBC hace de intermediario entre nuestro programa escrito en Java y la base de datos para cumplir con estos pasos, como se muestra en la figura 4.
Aplicacin JAVA
Manejador JDBC
Comando SQL Conjunto de resultados
Base de Datos
Figura 4. Arquitectura de JDBC
En los siguientes puntos se aclarar como llevar a cabo los pasos descritos anteriormente en JDBC haciendo uso de las 16 interfaces, 8 clases y 4 tipo de excepciones que aade. Que junto
con las 12 interfaces y 2 clases que aade la API Optional Package (JDBC 2.0) dan un amplio abanico de funcionalidades.
Statement: Es la clase que proporciona los mtodos para que la sentencia SQL sea ejecutada sobre la base de datos y recupere el resultado. Hay tres tipos de Statement y cada uno especializa al anterior: o Statement: es el encargado de ejecutar las sentencias SQL estticas. Se crea con Connection.createStatement(). o PreparedStatement: se utiliza para ejecutar las sentencias SQL precompiladas. Permite que los parmetros de entrada sean establecidos de forma dinmica. Se crean con Connection.preparedStatement(sql string). o CallableStatement: un preparedStatement que llama un procedimiento almacenado, es decir, mtodos almacenados en la propia base de datos. No todos los SGBD lo admiten pero es muy til para los que s.
ResultSet: es la clase a la que pertenece el objeto en el que se devuelve la respuesta a la peticin que hicimos a la base de datos. No es ms que un conjunto ordenado de filas de una tabla. Es lo que se devuelve cuando ejecutamos el mtodo statement.executeQuery(sql string). Existen mtodos como next() y getXXX() para iterar por las filas y obtener los valores de los campos que queramos.
DatabaseMetaData: ofrece una interfaz para operar con la estructura y capacidades de la base de datos. Se obtiene de connection.getMetaData().
ResultSetMetaData: es el ResultSet que se devuelve al hacer un executeQuery de un objeto DatabaseMetaData. DriverManager: interfaz que registra los controladores JDBC y proporciona las conexiones que permiten manejar las URL especficas de JDBC. Se consigue con el mtodo getConnection de la propia clase SQLException: La clase base de las excepciones de JDBC
3.5. Controladores
Para que una aplicacin Java que utilice el API JDBC pueda acceder a un Servidor concreto necesita un driver de JDBC especfico para l, esto es debido a que JDBC emplea una capa
Alonso y Garcs
intermedia compuesta por la clase DriverManager y el o los controladores JDBC (a veces llamados drivers). En realidad un controlador no es nada ms que la implementacin de la interfaz java.sql.Driver que ofrece la compaa distribuidora de la base de datos. Su misin es conectarse con la base de datos y devolver una conexin a esta, es decir, un java.sql.Connection. Para llamar adecuadamente a los controladores se hace a travs de DriverManager que determina cual es el controlador adecuado y lo registra. Los controladores se dividen en 4 tipos: Nivel 1: Puente JDBC:ODBC. Los controladores de este tipo utilizan un controlador ODBC intermedio de Microsoft para comunicarse con los servidores de base de datos. Sun lo describe como en un controlador experimental y recomienda su uso en caso que no haya otra alternativa. Este protocolo presenta una serie de desventajas considerables: o o o El controlador JDBC est limitado en capacidad por el anlogo ODBC, que es de proceso nico y responde mal ante cargas excesivas. La biblioteca de cdigo nativo debe estar instalada en el sistema cliente JdbcOdbc.dll. Necesitamos que el controlador ODBC tenga configurada una fuente de datos ODBC.
Estas desventajas lo hacen inadecuado para las Applets, sin embargo ofrece una serie de ventajas: o Como las JSP no operan como Applets no tiene ninguna de las limitaciones anteriores. ODBC est generalmente admitido y da acceso a sistemas con fuentes de datos ya configuradas. Los productos para bases de datos ODBC estn ampliamente difundidos (Access, FoxBase).
Nivel 2: API nativa, parcialmente en Java. Esta categora esta compuesta por controladores que hablan con los servidores de bases de datos en el protocolo nativo de la base de datos. Es decir, es idntico a los de nivel uno pero el controlador intermedio no es ODBC sino el correspondiente al gestor especfico de esa base de datos, que conoce el protocolo nativo. Adems presenta el mismo problema que los de nivel 1, es necesario tener instaladas y configuradas las bibliotecas nativas en el sistema cliente.
Nivel 3:Java puro con software intermedio a base de datos. Esta categora esta formada por controladores de Java puro( no hay cdigo binario) que hablan un protocolo de red estndar(como HTTP) con un servidor de acceso a bases de datos. Este servidor traduce el protocolo de red a uno de base de datos especficos de la marca. No necesita instalacin en cliente. Nivel 4:Controlador JDBC de protocolo nativo en Java puro. Est formada por controladores de Java puro que hablan el protocolo de la base de datos especfico de la marca del servidor de bases de datos que se haya designado para servir de interfaz. No necesita instalacin en cliente.
Los de tipo 1 y 2 hacen necesario que el cliente tenga instalado ms cantidad de software y los de tipo 4 resulta inadecuados si el SGBD se encuentra detrs de un cortafuegos.
9 POO Trabajo Voluntario
Aplicacin en Java
Controlador: Tipo 1
Controlador: Tipo 2
Controlador: Tipo 3
Controlador: Tipo 4
DBMS
DBMS
DBMS
DBMS
Base de datos
Base de datos
Base de datos
Base de datos
Alonso y Garcs
Una de ellas es aprovecharse de los mtodos ofrecidos y que liberan de trabajo. El cdigo resultante sera el siguiente (en este caso se carga un controlador de tipo 1) try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); }catch (ClassNotFoundException e) { System.out.println("Imposible encontrar el driver:" + e.getMessage()); } Internamente se llama al mtodo antes citado para registrar el driver que pasamos como parmetro al mtodo Class.forName. Para ms referencias y casos especiales referirse a [1]. Otra forma es poner el nombre del controlador en la propiedad del sistema jdbc.drivers que DriverManager carga durante su iniciacin. En JDBC 2.0 se ahorra tener que escribir el nombre de la clase controlador y de las URL de las bases de datos ya que se guardan en el servicio de nombres. Todo ello a travs del objeto DataSource. El ejemplo quedara como a continuacin: InitialContext ctc=new initialContext(); DataSource ds =(DataSource) ctx.lookup (java:comp/env/jdbc/lyricnote_internal); Connection con = null; try{ con = ds.getConnection(); ... } finally{ if (con = null) con.close(); }
El administrador de controladores mantiene una lista de controladores registrados, as cuando se invoca el mtodo getConnection() pregunta a cada controlador si acepta la URL especificada. Si la acepta devuelve un objeto Connection y si no devuelve null. En caso de usar DataSource el parmetro URL no se necesita por las razones anteriormente vistas. Si por el contrario no usamos DataSource habremos de saber que e argumento URL es una cadena de la forma: <protocolo>:<subprotocolo>:<subnombre> Protocolo es siempre jdbc. El subprotocolo se refiere al controlador que se va a usar y ha suministrado la empresa distribuidora (odbc en el caso de un controlador de tipo 1).
11
Por ltimo, el subnombre identifica la base de datos a la que conectarse, pudiendo contener parmetros de conexin.
Un ejemplo: jdbc:odbc:univ
Los comandos de manipulacin devuelven el nmero de filas modificadas mientras que la instruccin SELECT devuelve el denominado conjunto de resultados. Al tratarse de una interfaz java.sql.Statement no posee constructor y el objeto se crea en la llamada a Connection.createStatement(). Connection con = null; try{ con = ds.getConnection(); Statement stm=con.createStatement(); ... } finally{ if (con = null) con.close(); } Con esto ya tenemos el objeto necesario para poder llevar a cabo las instrucciones que queramos sobre la base de datos. Disponemos de cuatro mtodos para ejecutar comandos: executeUpdate: para hacer INSERT, UPDATE o DELETE. Y tambin para CREATE TABLE. Devuelve el n de filas actualizadas. Int n_rows = stm.executeUpdate(UPDATE ); executeQuery: para instrucciones SELECT, devolviendo el conjunto e resultados. ResultSet rs = stm.executeQuery(SELECT ); excute: se puede usar con cualquier propsito pero est concebida para las instrucciones que devuelven un contador de actualizacin, conjunto de resultado mltiple o una combinacin de ambos. Devuelve un booleano indicando cual de estas opciones es, proporcionando un mtodo para recuperar los resultados de cada una de ellas. Boolean i = stm.execute (sql_line);
POO Trabajo Voluntario 12
Alonso y Garcs
executeBatch(): sirve para enviar un conjunto de instrucciones de actualizacin que se han de ejecutar en un solo lote. Adems de ofrecer los mtodos clearBatch, addBatch para editar el conjunto de instrucciones que se quieren ejecutar. stm.clearBatch (); stm.addBatch (line_1); . stm.addBatch (line_n); int[] counst = stm.executeBatch();
Adems esta interfaz ofrece otras dos subinterfaces que amplan su funcionabilidad PreparedStatement y CallableStatement: PreparedStatement: emplea SQL precompilado en vez de sentencias normales. Esto mejora el rendimiento si la instruccin se realiza en multitud de ocasiones. La cadena hay que pasrsela a la hora de crear el objeto y no en el executeQuery. En la sentencia SQL podemos aprovechar las ventajas del tipo String y usar ? para hacer que las instrucciones no sean estticas. PreparedStatement pstm=con.prepareStatement(sql_line); pstm.executeQuery(); CallableStatement: Mejora de la anterior. Se emplea para ejecutar procedimientos almacenados si es que la base de datos lo permite. Los procedimientos almacenados no son otra cosa que instrucciones SQL almacenadas en la propia base de datos (Viene ya hechas en la base de datos). CallableStatement cstm=con.prepareCall(call myproc(x,y)); cstm.executeQuery();
13
Los ResultSet en las primeras versiones de JDBC no eran explorables en todos los sentidos sino que haba que empezar por el principio y seguir esa misma direccin, pero con JDBC 2.0 se puede empezar por donde se quiera y seguir por cualquier sitio con solo asignarle las propiedades necesarias en la creacin. A esto se le conoce con el nombre de conjunto de resultados desplazables. Esta versin de JDBC aade estos mtodos: isAfterLast(), isBeforeFirst(), isFirst(), isLast().Absolute(), relative(), previous(), first(), last(), beforeFirst() y afterLast(). Otra propiedad que con JDBC 2.0 le podemos asignar al conjunto de resultados es que se pueda actualizar las columnas, aadir las filas o borrar las ya existentes. Llamndose conjunto de resultados actualizables. Simplemente con aadir, quitar o modificar capos en el conjunto de resultados se actualizan las tablas que residen en la base de datos. Se ha de tener en cuenta, aunque no se desarrolle con profundidad, que JDBC incluye dos interfaces para el manejo de metadatos, los cuales constituyen una gran informacin de conexiones y conjuntos de resultados. Estas interfaces son DatabaseMetaData y ResultSetMetaData.
Alonso y Garcs
String sql=select DNI,NOMBRE from PERSONAL; ResultSet rs=sentencia.executeQuery(sql); %> <HTML> <HEAD> <TITLE>Lista de personal</TIITLE> </HEAD> <BODY> <CENTER> <B>Lista de personal </B> </CENTER> <TABLE border=1 cellpadding=2 cellspacing=0 > <TR> <TH>DNI</TH> <TH>Nombre</TH> </TR> <%-- Recorremos mostrando el conjnto de resultados--%> <%while(rs.next()) { %> <TR> <TH><%=rs.getString(1)%></TH> <TH><%=rs.getString(2)%></TH> </TR> <% }/*del while*/ %> <%-- Cerramos todo y desconectamos --%> <% rs.close(); sentencia.close(); con.close(); %> </TABLE> </BODY> </HTML> Como se ve en el ejemplo se puede separar el cdigo de JSP de HTML dejando nicamente las partes indispensables dentro del cdigo HTML. El cdigo JSP hasta el inicio del HTML puede ir en otra pgina que se incluya mediante la directiva include ya explicada.
15
5. Conclusiones
Tecnologas como JSP han supuesto un cambio importante en la gestin de los negocios, ya que facilitan de sobremanera el ofertar a clientes de todos los rincones del mundo sus productos o servicios a travs de la red de redes (Internet) sin necesidad de programas clientes especficos en el ordenador del cliente. La ventaja principal que aporta JSP al mundo empresarial ha sido poder encargar a profesionales de pginas Web la apariencia de su pgina y el funcionamiento a expertos Java sin problemas engorrosos de ningn tipo. Se debe admitir que es una tecnologa muy nueva, por lo que todava falta mucho camino por andar antes que compita en trminos reales contra otras aplicaciones que estn ms maduras como SSI, PHP del grupo PHP, ASP de Microsoft o Cold Fusin de Allaire, pero su promesa es muy interesante (como se public en el ao 2002 en http://www.mexicoextremo.com.mx). Por otro lado debemos considerar que casi todas las aplicaciones JSP necesitan acceder a bases de datos. JDBC es el API estndar que proporciona Java para este propsito. Permite acceder a casi cualquier base de datos trabajando sobre ella mediante una programacin orientada a objetos con la misma capacidad que se hiciese directamente en SQL. Adems al existir pocos objetos clave es fcil de aprender. El objeto Connection crea un vnculo con la base de datos, el Statement hace posible ejecutar instrucciones SQL y recuperar un ResultSet donde est el conjunto de resultados. As JDBC es el complemento ideal para tanto Servlets, como JSP ya que les aade una caracterstica indispensable, que es el acceso a datos. Adems no es una tecnologa estancada sin que est en evolucionando constantemente y cada versin proporciona caractersticas mejoradas, siendo as la tecnologa dominante para acceso a datos desde Java. A la vista de lo expuesto, programar en JSP ayudndose de JDBC es una opcin muy interesante a considerar para el desarrollo de aplicaciones Web. Adems es de especial inters para la gente familiarizada con Java, pues no necesitan aprender otro lenguaje.
16
Alonso y Garcs
6. Referencias
[1] Phil Hanna. JSP Manual de Referencia. McGraw-Hill Osborne Media 2002 [2] Juan Antonio Palos (Ozito). Servlets y Java Server Pages (JSP) 1.0. http://www.programacion.com/java/servlets_jsp/
7. Bibliografa recomendada
Official Java Page. http://java.sun.com Servlets y JDBC. http://www.javahispano.com/tutoriales/servlets.html Acceso a BD desde Java. JDBC. http://wwwetsi2.ugr.es/depar/ccia/mabd/material/practicas/transparencias/AccesoBDJava.pdf Programacin con JDBC (JavaDataBase Connectivity ), Servletsy JSP (Java Server Pages). http://www.walc2000.unam.mx/material/track2/JDBCBL1.pdf Tutorial de JavaServer Pages Autor: Miguel Angel. http://www.javahispano.com/download/jsp.pdf
17