Con frecuencia nos vemos obligados a visualizar una gran cantidad de datos mediante una JTable. El resultado es una carga lenta, un scroll penoso y un consumo de RAM intolerable. Ciertamente, hay alternativas. Uno siempre intenta minimizar los datos que carga e incluso se inventa algn tipo de cach! que necesita un mont"n de pruebas hasta que se da por aceptable. #a mayor parte de los datos en las cargas masivas suelen tener su origen en una base de datos a la que accedemos mediante un driver $%&C. En este art'culo hago una propuesta de un uso racional tanto del TableModel como de las posibilidades que nos ofrece $%&C (.) *y posteriores+ para reducir a pr,cticamente nada el coste de la representaci"n de grandes volmenes de datos mediante JTables. !"# $A%EMO& MAL' Antes de e-poner mi propuesta creo que es conveniente analizar algunas de las acciones m,s frecuentes que nos llevan a la ineficiencia. "sar lo (ue )a es*+ ,ec,o &ien, .usar lo que ya est, hecho/ no es malo si lo que est, hecho funciona como es debido. Este no es el caso, por e0emplo, de una de las clases .ya hechas/ de JTableModel1 DefaultTableModel. #os m,s comodones nos limitamos a usarla directamente, y los que lo son menos, se permiten subclasearla. 2in embargo, %efault3ableModel es el final de una cadena de dos elementos1 la interficie JTableModel y la clase abstracta que la implementa1 AbstractTableModel. #a clase DefaultTableModel no es m,s que una soluci"n de compromiso que nos ofrece 2un. 4o dir'a que, en realidad, es tan solo un e0emplo pr,ctico, m,s o menos afortunado. No leer con -e*alle las A.Is (ue *ocan En realidad, nuestra primera apro-imaci"n se basaba en un .copy5paste/ del c"digo de un compa6ero que en cinco minutos, mientras tom,bamos un caf!, nos cont" que eso de las tablas y el $%&C era muy sencillito, pero que el Swing, ya se sabe, es m,s lento que el caballo del malo. En definitiva, sin ver un Javadoc ni por el forro, nos hemos lanzado a escribir nuestra primera versi"n de la aplicaci"n. 7s invito a hacer una prueba. 8reguntad a vuestros compa6eros *y a vosotros mismos, claro est,+ si tienen una bookmark de las A89s de $ava en su e-plorador. :er!is qu! poquitos la tienen. .OR !"# E&T/ MAL LO !"E $A%EMO&' "sar lo (ue )a es*+ ,ec,o0 De1aul*Ta2leMo-el #a clase DefaultTableModel e-tiende AbstractTableModel y, si bien para algunos casos simples y con poco volumen de datos puede ser til, tiene varios problemas. El primero es el uso ; de ;; e-haustivo de la clase Vector. <o entraremos en detalles, s"lo decir que Vector tiene todos sus m!todos sincronizados lo que es bastante caro y, en la mayor parte de los casos, innecesario. 2er'a m,s conveniente usar una ArrayList para el almacenamiento de datos. Es equivalente a Vector, pero no tiene sus m!todos sincronizados. 8ara aquellos casos simples en los que puede ser interesante usar un modelo con las funcionalidades de DefaultTableModel, he desarrollado un nuevo modelo con la misma funcionalidad que DefaultTableModel pero basado en una ArrayList1 ArrayListTableModel. 2i bien ArrayListTableModel da un mayor rendimiento que DefaultTableModel *un (=> apro-imadamente+, no es la panacea. #as pruebas se han realizado con una tabla de ?@.A;B registros que ocupa, apro-imadamente, C,;? M&. Esto significa que, en ambos casos hemos de tener cargados en RAM una List de, al menos, C,;? M&. &ueno, hemos conseguido me0orar el rendimiento, pero no los requisitos de memoria. No leer con -e*alle las A.Is (ue *ocan 2! que no se puede leer todo con detalle, pero es conveniente dar una leidita a los Javadoc de las A89s involucradas en nuestro problema y al menos mantener pointers a lo que nos ha parecido interesante *aunque no le hayamos visto una aplicaci"n inmediata+. DEu! A89s est,n involucradas en nuestro problemaF &,sicamente, nueve1 javax.swing.JTable javax.swing.table.JTableModel javax.swing.table.AbtractTableModel javax.swing.table.TableCellRenderer java.sql.Connection java.sql.DatabaseMetaData java.sql.tate!ent java.sql.Resultet java.sql.ResultetMetaData 3odas aparecen en la soluci"n propuesta y comentaremos con m,s detalle, en su momento, los aspectos que nos interesan de cada una de ellas. &OL"%I3N %ON Arra)Lis* 2e recomienda utilizar ArrayList en vez de Vector siempre que podamos. 3ened en cuenta que Vector e-iste desde el $%G ;.) mientras que ArrayList aparece en $%G ;.(. Algo debe aportar... Hemos comentado que el factor determinante es que ArrayList no tiene m!todos sync"roni#ed. Esto implica que si otro thread modifica nuestra ArrayList, hay que hacer la sincronizaci"n a mano. 2i consideramos que este hecho no es importante para nuestra aplicaci"n, y creo que en el B)> de los casos de modelos de JTable no lo es, podemos usar un modelo basado en ArrayLists en nuestro modelo, 2i lo es, podemos seguir usando el consabido DefaultTableModel. ( de ;; El problema es que 2un no proporciona ningn modelo basado en ArrayList y nos tocar, escribirlo desde cero. 4o he escrito un nuevo modelo basado en ArrayList, ArrayListTableModel, que se supone que debe funcionar e-actamente igual que DefaultTableModel y lo pongo a vuestra disposici"n. Mis pruebas de rendimiento, como he comentado m,s arriba, me dan un (=> de me0ora de rendimiento respecto a DefaultTableModel. Recordad, sin embargo, que nadie nos quita el hecho de tener todos los registros en RAM. Como es l"gico, ArrayListTableModel e-tiende AbstractTableModel. 2i ten!is que escribir vuestro propio modelo, e-tended siempre AbstractTableModel, no e-tend,is DefaultTableModel. &OL"%I3N %ON JDB% Y &%ROLLABLE %"R&OR& Esta es, para m', la soluci"n "ptima ya que cumple con los dos requisitos fundamentales1 ;. Me0ora el rendimiento (. Utiliza poca memoria #a soluci"n que propongo es sencilla de implementar y, adem,s, muy eficiente. 8odr'a limitarme a e-plicar someramente su implementaci"n y dar el c"digo, pero como no creo que sea una soluci"n definitiva y es posible que a m,s de uno se le ocurra algo me0or, escribir! un poco m,s e intentar! e-plicar c"mo usa JTable los TableModel. Es evidente que la soluci"n propuesta no es la panacea. Como ver!is el traba0o lo hace el gestor de bases de datos, lo que supone una cone-i"n abierta con la base de datos mientras se est! e0ecutando nuestra aplicaci"n y una carga para el servidor. El 4ara-i56a M7% 8Mo-el 7ie9 %on*roller: Creo que todos sab!is m,s o menos c"mo funciona este paradigma, as' que no entrar! en detalles introductorios y me centrar! en lo que nos interesa, simplificando *hasta la incorrecci"n+ lo que sea necesario. Swing no utiliza e-actamente el M:C, sino una variante1 M% *Model Delegate+ en la que el Delegate incluye View y Controller. Adem,s, JTable es un componente que est, formado por varios subcomponentes y cada uno de ellos con su modelo y su delegate. JTa2le ) M7%0 un e;e64lo sencillo 2implificando mucho, diremos que una JTable muestra unos datos que se encuentran en el modelo. Una JTable tiene unas dimensiones determinadas en las que .caben/ un nmero concreto de registros mostrables. JTable lo sabe y le pide al modelo s"lo aquellos registros que puede mostrar. En la mayor parte de los casos, nuestras JTables se encontrar,n dentro de un Jcroll$ane ya que no disponemos de espacio suficiente para mostrar todos los registros. Cada vez que, por e0emplo, movemos la barra de desplazamiento vertical del Jcroll$ane, nuestra JTable le pide m,s datos al modelo para poderlos mostrar. #a soluci"n es "ptima ya que s"lo le pide unos pocos datos1 aqu!llos que puede representar. El m!todo que utiliza para pedir datos al modelo es getValueAt%int row' int colu!n(. 9maginemos que tenemos una JTable con dos columnasI la primera muestra los nmeros de ) a )** y la segunda, su doble *el valor de la primera columna, multiplicado por dos+. Una utilizaci"n C de ;; no recomendable de nuestro modelo mantendr'a una matriz con los valores de los ;)) primeros nmeros y su doble1 int+,+, data - new int+)**,+.,/ +..., 0rivate void fillData%( 1 for %int i - )/ i 2- )**/ i33( 1 data+i4),+*, - i/ data+i4),+), - i 5 ./ 6 6 En nuestro modelo, declaramos una matriz de enteros de )**x. y la llenamos mediante el m!todo fillData%(. 8ara pedir datos a nuestro modelo, nuestra JTable usa los siguientes m!todos del modelo1 0ublic int getColumnCount() 1 return data+*,.lengt"/ 6 0ublic int getRowCount() 1 return data.lengt"/ 6 El primero le sirve para acotar el nmero de columnas. 2i tiene ( columnas, no pregutar, por la onceava... El segundo le sirve para acotar el nmero de registros disponibles. Jinalmente, una vez tiene claro cu,ntas columnas y cu,ntos registros tiene el modelo de datos, pregunta por el valor concreto de una celda usando el m!todo 7bject getValueAt%int row8ndex' int colu!n8ndex(. En nuestro caso, lo podr'amos implementar f,cilmente as'1 0ublic 7bject getValueAt%int row8ndex' int colu!n8ndex( 1 return new 8nteger%data+row8ndex4),+colu!n8ndex,(/ 6 7bservemos que esta es una implementaci"n sumamente ineficiente. Hay que llenar una matriz y despu!s consultarla. Esto puede no ser caro para los ;)) primeros nmeros, pero si queremos traba0ar, por e0emplo, con los K)).))) primeros nmeros, nuestra implementaci"n empezar'a a renquear. :eamos una implementaci"n m,s eficiente1 0ublic int getColu!nCount%( 1 return ./ 6 0ublic int getRowCount%( 1 return 9*****/ 6 0ublic 7bject getValueAt%int row8ndex' int colu!n8ndex( 1 if %colu!n8ndex -- *( 1 return new 8nteger%row8ndex(/ 6 return new 8nteger%%row8ndex5.((/ 6 En esta segunda implementaci"n optimizamos al m,-imo los recursos de memoria ya que no = de ;; almacenamos ningn valor. #os valores se calculan en tiempo de e0ecuci"n. JTa2le ) M7%0 un 6al e;e64lo con JDB% 2i bien el e0emplo anterior es bastante ilustrativo y nos puede venir bien para implementar algunos de nuestros modelos, no e0emplifica el caso m,s t'pico1 los datos a representar provienen de una consulta a la base de datos. #legados a este punto, optamos por una soluci"n t'picamente ineficiente como esta1 tate!ent st!t - null/ Resultet rs - null/ Vector rows - new Vector%(/ tring select - :;L;CT <7M=R;' A$;LL8D7)' A$;LL8D7. >R7M $;R7<A?/ try 1 st!t - connection.createtate!ent%(/ rs - st!t.execute@uery%select(/ w"ile %rs.next%(( 1 tring no!bre - rs.gettring%)(/ tring a0ellido) - rs.gettring%.(/ tring a0ellido. - rs.gettring%A(/ Vector row - new Vector%(/ row.add;le!ent%no!bre(/ row.add;le!ent%a0ellido)(/ row.add;le!ent%a0ellido.(/ rows.add;le!ent%row(/ 6 6 catc" %@L;xce0tion e( 1 e.0rinttacBTrace%(/ 6 finally 1 if %st!t C- null( 1 try 1 st!t.close%(/ 6 catc" %@L;xce0tion e( 1 6 6 6 Vector col<a!es - new Vector%A(/ col<a!es.add;le!ent%:<o!bre?(/ col<a!es.add;le!ent%:$ri!er A0ellido?(/ col<a!es.add;le!ent%:egundo A0ellido?(/ DefaultTableModel !odel - new DefaultTableModel%rows' col<a!es(/ !iTabla.setModel%!odel(/ Como la lista de personas sea la de la gu'a telef"nica de Espa6a, no hay JTable que lo soporte. Lste suele ser el problema al que nos enfrentamos. Cargar toda la tabla en memoria es realmente costoso. An+lisis -el 4ro2le6a Ji0!monos en el primer e0emplo. El modelo, en realidad, no contiene datos. #os calcula. Es eficiente porque no tiene un lento proceso de carga y porque pr,cticamente no usa memoria. 2implemente, proporciona a la JTable lo que !sta le pide. En nuestro e0emplo $%&C el modelo contiene todos los datos. Hay que cargarlos y almacenarlos en memoria. <o es, pues, eficiente si los datos son muchos. K de ;; 7bservemos, sin embargo, que los datos ya est,n en la base de datos. 2i esto es as', Dpor qu! no .calculamos/ los datos que nos pide la JTable pidi!ndoselos a nuestra base de datos y simplemente le retornamos lo que necesitaF "n 6o-elo 4ro4orciona -a*os al -ele5a*e si5uien-o un 4ro*ocolo concre*o, no es necesario (ue l *en5a los -a*os< Bas*a con (ue los su6inis*re 8calcul+n-olos u o2*enin-olos -e un *ercero 2a;o -e6an-a:< "NA .O&IBLE &OL"%I3N0 &crolla2leTa2leMo-el Hasta la aparici"n de $%&C (.), no pod'amos hacer otra cosa que cargar los datos resultantes de nuestra consulta en memoria. El Resultet s"lo pod'a moverse en una direcci"n1 hacia adelante. <o pod'a, pues, dar respuesta a los requerimientos de una tabla capaz de moverse hacia adelante y hacia atr,s. 8ero a partir de $%&C (.), disponemos de Resultets que pueden moverse libremente por el con0unto de resultados de una consulta. El modelo, pues, puede limitarse a .calcular/ los datos que debe devolver y e-traerlos del Resultet. De*er6inar si nues*ro -ri=er JDB% so4or*a scrolla2le cursors #amentablemente, no todos los drivers $%&C soportan scrollable cursors. 8ara determinar si nuestro driver los soporta, hemos de consultar la metadata de la base de datos1 0rivate boolean su00ortscroll8nsensitive%Connection con( 1 DatabaseMetaData !d - null/ try 1 !d - con.getMetaData%(/ 6 catc" %@L;xce0tion e( 1 tring errMsg - D;rror getting database !etadata.D/ t"row new crollableTableModel;xce0tion%errMsg' e(/ 6 try 1 return !d.su00ortsResultetTy0e% Resultet.TE$;FCR7LLF8<;<8T8V;(/ 6 catc" %@L;xce0tion e( 1 tring errMsg - D;rror getting database !etadata.D/ t"row new crollableTableModel;xce0tion%errMsg' e(/ 6 6 GG su00ortscroll8nsensitive%( "so -e scrolla2le cursors Como hemos visto, para que nuestro modelo funcione debemos disponer de scrollable cursors. 2uponiendo que su0ortscroll8nsensitive%( nos devuelva true, podemos definir un tate!ent que use scrollable cursors1 st!t - connection.createtate!ent%Resultet.TE$;FCR7LLF;<8T8V;' Resultet.C7<CHRFR;ADF7<LE(/ <o entrar! aqu' en detalles sobre la sinta-is de este m!todo, s"lo dir! que el primer par,metro establece el tipo de scroll y el segundo, el nivel de aislamiento del cursor. O2*enci>n ) re*orno -e los -a*os Una vez disponemos de nuestro Resultet scrollable, hemos de utilizar sus posibilidades ? de ;; sobrescribiendo el m!todo getValueAt%(1 0ublic 7bject getValueAt%int row8ndex' int colu!n8ndex( 1 int row<dx - row8ndex 3 )/ int col<dx - colu!n8ndex 3 )/ try 1 resultet.absolute%row<dx(/ return resultet.get7bject%col<dx(/ 6 catc" %@L;xce0tion e( 1 tring errMsg - D;rror getting value at D 3 row8ndex 3 D' D 3 colu!n8ndex/ t"row new crollableTableModel;xce0tion%errMsg' e(/ 6 6 #o primero que hace nuestro m!todo es traducir las coordenadas de JTable a coordenadas de $%&C. En $%&C se empieza a contar desde ;, y en el resto del mundo $ava, se empieza a contar desde ). As', si JTable nos pide los datos del registro ), columna ), hemos de traducirlo a $%&C como registro ;, columna ;. A continuaci"n situamos el cursor en el registro $%&C solicitado1 resultet.absolute%row<dx(/ Una vez situado el cursor, obtenemos los datos almacenados en la columna $%&C que se nos pide1 resultet.get7bject%col<dx(/ 7bs!rvese que pedimos y devolvemos un 7bject. Las clases -e las colu6nas JTable es capaz de mostrar nuestros datos en funci"n de su tipo. 8ara ello utiliza clases que implementan la interf'cie TableCellRenderer. 8ero, como es l"gico, alguien tiene que decirle qu! clase de ob0eto recibe. 2i no se le dice, utiliza el m!todo totring%( del ob0eto de datos para mostrar su contenido. En nuestro caso, devolvemos un 7bject y, si no le proporcionamos m,s datos, nos mostrar, lo que devuelva el m!todo totring%( del ob0eto que devolvemos. 2i no hay un renderer para un tipo de ob0eto concreto, tambi!n utiliza el m!todo totring%( de dicho ob0eto. Es conveniente, pues, indicarle qu! clase de ob0eto le estamos devolviendo para cada columna. Esto se lleva a cabo implementando el m!todo abstracto getColu!nClass%( de AbstractTableModel. #o cierto es que, por e0emplo, DefaultTableModel no implementa este m!todo. Un motivo m,s para utilizarlo con precauci"n. #a implementaci"n para nuestro modelo podr'a ser la siguiente1 List colClasses - null/ +..., 0ublic Class getColu!nClass%int colu!n8ndex( 1 if %colClasses -- null( 1 colClasses - new ArrayList%(/ ResultetMetaData !d - null/ try 1 !d - resultet.getMetaData%(/ int colCount - !d.getColu!nCount%(/ for %int i - */ i 2 colCount/ i33( 1 try 1 A de ;; tring class<a!e - !d.getColu!nClass<a!e%i 3 )(/ Class c - Class.for<a!e%class<a!e(/ colClasses.add%c(/ 6 catc" %Class<ot>ound;xce0tion e( 1 tring errMsg - D;rror getting colu!n classes.D/ t"row new crollableTableModel;xce0tion%errMsg' e(/ 6 6 GG for i 6 catc" %@L;xce0tion e( 1 tring errMsg - D;rror getting colu!n classes.D/ t"row new crollableTableModel;xce0tion%errMsg' e(/ 6 6 Class c - %Class(colClasses.get%colu!n8ndex(/
return c/ 6 Como puede verse, la informaci"n sobre el tipo del ob0eto retornado proviene de ResultetMetaData. 8ara cada una de las columnas, preguntamos cu,l es el nombre de la clase *getColu!nClass<a!e%i 3 )(+, una vez m,s traduciendo coordenadas de JTable a $%&C. A partir del nombre de la clase, obtenemos la Class1 Class c - Class.for<a!e %class<a!e(/ que es lo que devolvemos a la $3able. Ella ya se encargar, de ver c"mo muestra los datos de esa clase. O2*enci>n -e los no62res -e las colu6nas Como hemos visto, una de las cosas que JTable pide al modelo son los nombres de las columnas. 8odemos obtener estos nombres directamente del Resultet utilizando la clase ResultetMetaData1 ArrayList col<a!es - null/ +..., ResultetMetaData rs!d - null/ try 1 rs!d - resultet.getMetaData%(/ int colCount - rs!d.getColu!nCount%(/ if %colCount -- *( 1 GG T7D7I <o "ay colu!nasC 6 t"is.col<a!es - new ArrayList%(/ for %int i - */ i 2 colCount/ i33( 1 tring colLabel - rsmd.getColumnLabel(i+1); t"is.col<a!es.add%colLabel(/ 6 6 catc" %@L;xce0tion e( 1 e.0rinttacBTrace%(/ tring errMsg - D;rror getting ResultetMetadataD/ t"row new crollableTableModel;xce0tion%errMsg' e(/ 6 7bs!rvese que utilizo el m!todo getColu!nLabel%( para obtener el nombre de la columna, cuando pareciera m,s l"gico usar el m!todo getColu!n<a!e%(. #o cierto es que getColu!nLabel%( devuelve lo mismo que getColu!nCount%(, e-cepto si especificamos una etiqueta espec'fica en nuestra consulta 2E#1 ;L;CT < A :<o!bre?' A A :A0ellidos? >R7M $;R7<A @ de ;; En este e0emplo, getColu!n<a!e%( devolver'a < y A, mientras que getColu!nLabel%( devolver'a <o!bre y A0ellidos. 7bviamente, nuestro modelo debe ofrecer la posibilidad de especificar una ArrayList con los nombres de las columnas. #"gicamente, habr, que sobrescribir el m!todo getColu!n<a!e%(1 0ublic tring getColu!n<a!e%int colu!n( 1 return %tring(col<a!es.get%colu!n(/ 6 %uan*as colu6nas' Como hemos visto m,s arriba, una de las cosas que JTable necesita saber es el nmero de columnas del modelo. 8ara ello usa el m!todo getColu!nCount%( que podemos implementar como sigue1 0ublic int getColu!nCount%( 1 return col<a!es.si#e%(/ 6 %uan*os re5is*ros' 3al como hemos comentado, JTable necesita saber tambi!n cu,ntos registros tiene el modelo para poder acotar los par,metros pasados al m!todo getValueAt%int row8ndex' int colu!n8ndex(. 8ara ello utiliza el m!todo getRowCount%(, que podr'a ser implementado, en nuestro caso, de la siguiente manera1 int rowCount - 4)/ +..., 0ublic int getRowCount%( 1 if %t"is.rowCount -- 4)( 1 try 1 resultet.last%(/ t"is.rowCount - resultet.getRow%(/ 6 catc" %@L;xce0tion e( 1 tring errMsg - D;rror scrolling to latest row.D/ t"row new crollableTableModel;xce0tion%errMsg' e(/ 6 6 %isponemos de la variable rowCount inicializada a 4), lo que nos indicar, que todav'a no hemos calculado su valor. El el m!todo getRowCount%(, pasamos a calcular su valor siempre que !ste valga 4) *es decir, siempre que todav'a no lo hayamos inicializado+. 8ara ello, situamos el cursor en el ltimo registro *resultet.last%(+ y asignamos a rowCount el valor devuelto por el m!todo getRow%(I esto es, el nmero del ltimo registro que, en notaci"n $%&C equivale al nmero de registros. Ren-i6ien*os Entramos aqu' en el apartado m,s pr,ctico de todos1 qu gano en cada caso? Hemos comentado ya que el modelo ArrayListTableModel supon'a una me0ora de rendimiento del (=> respecto al modelo DefaultTableModel. #as pruebas realizadas sobre la misma base de datos con crollableTableModel me indican que la me0ora de rendimiento B de ;; respecto a DefaultTableModel es, apro-imadamente, del ?00@. LA %LA&E &crolla2leTa2leMo-el Como he comentado m,s arriba, pongo a disposici"n pblica la clase crollableTableModel. 2i bien, hasta ahora he comentado algunos aspectos de la implementaci"n, no he hablado todav'a del uso. :amos, pues, a ello. %ons*ruc*ores 4u2lic &crolla2leTa2leMo-el8%onnec*ion con, &*rin5 selec*: .ar+6e*ro %o6en*ario con Una cone-i"n abierta con la base de datos selec* #a instrucci"n ;L;CT necesaria para obtener los datos #os nombres de las columnas se obtienen a partir del ResultetMetaData, tal como he comentado m,s arriba. &crolla2leTa2leMo-el8%onnec*ion con, &*rin5 selec*, Lis* colNa6es: .ar+6e*ro %o6en*ario con Una cone-i"n abierta con la base de datos selec* #a instrucci"n ;L;CT necesaria para obtener los datos colNa6es Una java.util.List *p.e. ArrayList+ con los nombres de las columnas El nmero de elementos de col<a!es deber, coincidir con el nmero de columnas devuelto por la consulta. 4u2lic &crolla2leTa2leMo-el8Resul*&e* rs: .ar+6e*ro %o6en*ario rs Un Resultet configurado con croll 8ntensive. Recordemos que para que el Resultet soporte scroll, debemos especificarlo en el momento de creaci"n del tate!ent. 8or e0emplo1 st!t - connection.createtate!ent%Resultet.TE$;FCR7LLF;<8T8V;' Resultet.C7<CHRFR;ADF7<LE(/ #os nombres de las columnas se obtienen a partir del ResultetMetaData, tal como he comentado m,s arriba. ;) de ;; 4u2lic &crolla2leTa2leMo-el8&*a*e6en* s*6*: .ar+6e*ro %o6en*ario s*6* Un tate!ent configurado con croll 8ntensive conteniendo un Resultet. #os nombres de las columnas se obtienen a partir del ResultetMetaData, tal como he comentado m,s arriba. 4u2lic &crolla2leTa2leMo-el8Resul*&e* rs, Lis* colNa6es: .ar+6e*ro %o6en*ario rs Un Resultet configurado con croll 8ntensive. selec* #a instrucci"n ;L;CT necesaria para obtener los datos colNa6es Una java.util.List *p.e. ArrayList+ con los nombres de las columnas 4u2lic &crolla2leTa2leMo-el8&*a*e6en* s*6*, Lis* colNa6es: .ar+6e*ro %o6en*ario &*6* Un tate!ent configurado con croll 8ntensive conteniendo un Resultet. selec* #a instrucci"n ;L;CT necesaria para obtener los datos colNa6es Una java.util.List *p.e. ArrayList+ con los nombres de las columnas E;e64lo -e uso El siguiente c"digo muestra un sencillo e0emplo de uso de crollableTableModel, usando tan solo una conne-i"n a base de datos y un select1 0ublic class crollableTableModelTest 1 0ublic static void !ain%tring+, args( 1 Connection con - null/ [... Carga del Driver JDBC y connexin a BD ...] J>ra!e f - new J>ra!e%(/ f.setDefaultClose70eration%J>ra!e.;J8TF7<FCL7;(/
JTable tabResults - new JTable%(/ Jscroll$ane s0TabResults - new Jscroll$ane%tabResults(/ f.getContent$ane%(.add%s0TabResults' =orderLayout.C;<T;R(/ tring sel - :;L;CT 5 >R7M $;R7<AL?/ crollableTableModel !odel - new crollableTableModel%con' sel(/ tabResults.setModel%!odel(/ f.0acB%(/ GG Ventana centrada en la 0antalla f.setLocationRelativeTo%null(/ f.setVisible%true(/ 6 GG !ain%( 6 ;; de ;;