Java, XML, and Databases

Java, XML, and Databases

By Mario Aquino, OCI Software Engineer

October 2002


Introduction

XML and its supporting technologies XML Schema and XSL Transformation provide very powerful mechanisms to describe, validate, and transform data. Using these technologies, applications can be made to communicate and share data with other systems, regardless of platform incompatibilities. This article focuses on the process of automatically generating XML instance documents as well as XML Schemas by querying the contents and structure of database tables using Java's JDBC Metadata capabilities and XSL Stylesheets.

Motivation

A common (though awkward) way to create XML objects is by writing a method equivalent to toXML() inside of a value object class so that instances can write out their state to an XML representation. Typically in this approach, a query is made to a database to retrieve a number of rows of data. The result set returned by the database is used to create instances of value objects that temporarily store the data internally, then convert their state into an XML document. This code may look may look something like this:

  1. import org.w3c.dom.*;
  2. import javax.xml.parsers.*;
  3. public class ValueObject {
  4. private String someData;
  5. private int someNumber;
  6. ...
  7. //Standard accessor methods...
  8. public String getSomeData() {
  9. return someData;
  10. }
  11. ...
  12. //Standard mutator method...
  13. public void setSomeData(String somedata) {
  14. someData = somedata;
  15. }
  16. ...
  17. //And the toXML() method
  18. public org.w3c.dom.Document toXML() {
  19. Document doc = null;
  20. try {
  21. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  22. DocumentBuilder db = dbf.newDocumentBuilder();
  23. Document doc = db..newDocument();
  24. Element root = doc.createElement("ValueObject");
  25. doc.appendChild(root);
  26. Element node = doc.createElement("someData");
  27. root.appendChild(node);
  28. Text value = doc.createTextNode(someData);
  29. node.appendChild(value);
  30. ...//And so on for all the fields
  31. } catch (Exception e) {
  32. //Bad form, but just for demonstration!
  33. }
  34. return doc;
  35. }
  36. }

Then, there has to be another class that queries the database and retrieves data from the ResultSet returned by the query. This class has to know both the names and datatypes of the columns in the database as well as the names of the mutator methods on the ValueObject class. Briefly, the code in this class looks something like this:

  1. ...//Assuming the class uses a Statement object to get the results from the DB
  2. ResultSet rs = stmt.executeQuery(someSQLString);
  3. List collection = new ArrayList(); //Collection to hold all the ValueObjects
  4. ValueObject vo = null;
  5. while(rs.next()) {
  6. vo = new ValueObject();
  7. vo.setSomeData(rs.getString("SOME_DATA_COLUMN_NAME"));
  8. vo.setSomeNumber(rs.getInteger("SOME_NUMBER_COLUMN_NAME"));
  9. ...//Continue for all the columns that the result set returns
  10. collection.add(vo);
  11. }
  12. //Then eventually some code will get the contents of the ValueObject as
  13. //a Document object
  14. Document doc = someValueObjectInstance.toXML();

All this is too much work just to get some data into an XML document (regardless of where it is going) not to mention all the dependencies that are introduced by explicitly referring to table column names and object mutator methods. A much better approach is through the use of metadata interfaces that are part of Java's JDBC API to retrieve the names and datatypes of the columns returned in a ResultSet and use that information to build XML instance documents on the fly. Before getting into the details of dynamically generated XML documents, a brief review of database concepts and an examination of two of the JDBC metadata interfaces is in order.

Databases and Metadata

Many if not all relational database products follow a model of data structures or entities defined as tables with a fixed number of named columns, each of which responsible for storing data of a particular type. Additionally, relationships between columns in the tables can usually be indicated throught the use of primary and foreign keys; primary keys representing columns in a table that are required to contain unique values or combinations of values and foreign keys representing columns in a table that contain a reference value that is uniquely defined in another table in the database. Using primary and foreign keys, databases are able to maintain "referential integrity" for the data items they store, that is, they can enforce relationships that define business requirements inherent to the data. All these details comprise the "metadata" that defines the structure of database tables.

The diagram below depicts tables that are part of a database called "Regatta". This database stores records that represent the results of sail boat races.

Regatta Tables

The tables each define their columns with a name and a datatype. As well, primary keys appear in the top most area of the rectangles and the columns that represent foreign keys have (FK) next to their datatypes. Finally, dotted lines are drawn between some of the tables to identify the foreign key relationships; the closed end of the dotted lines rests on the table that defines a foreign key column that refers to a primary key column in the table at the other end of the line.

Database Metadata

Java's JDBC API provides several interfaces that support the retrieval of data stored in a database. The API also includes interfaces that can gather details about a database itself, the kinds of operations it supports, as well as information about the tables it contains. One such interface is appropriately named DatabaseMetaData. This interface can be used to find out everything about a database from the number and names of schemas it includes, to the tables defined in those schemas, to the columns contained in the tables, to the relationships those columns may have with columns in other tables elsewhere in the database.

With its ability to discover the entire structure of a database, this interface can be used to create XML Schemas for the purposes of mapping entities defined in a database to structures that exist in an XML document. XML Schemas declare the structures and their relationships that are allowed in an XML interface much like a database schema. An XML Schema can be used to validate XML instance documents that claim to adhere to the rules defined the schema. Document description and definition are most important when two systems need to share data via XML interfaces. XML Schemas that back the data structures exchanged by interfacing systems can be used to validate the data as it passes from the sending to the receiving system.

Result Set Metadata

The JDBC API also provides an interface to discover metadata details about rows returned in result sets. The ResultSetMetaData interface provides information about each of the columns in a ResultSet including the column names, data types, precision, nullability, searchability, etc. The utility of this as well as the DatabaseMetaData interface can be realized through the creation of tools that dynamically generate XML documents from database result sets or from the very details of the entities managed by the database itself.

Implementation

With relatively little effort, it is possible to see just how valuable interfaces that provide metadata about databases and result sets can be. Below is some code that takes a ResultSet and, using the ResultSetMetaData interface, creates an XML document from its contents.

  1. import java.sql.*;
  2. import javax.xml.parsers.*;
  3. import org.w3c.dom.*;
  4. ...
  5. public Document convertRSToDocument(ResultSet rs) {
  6. Document doc = null;
  7.  
  8. try {
  9. ResultSetMetaData rsmd = rs.getMetaData();
  10.  
  11. //Get an array of columns from the RSMetaData
  12. String[] columns = new String[rsmd.getColumnCount()];
  13. for (int i = 0; i < columns.length; i++) {
  14. //The set of column names begins with '1' rather than '0'
  15. columns[i] = rsmd.getColumnName(i + 1);
  16. }
  17.  
  18. //Get an array of the types of each RS column from the RSMetaData
  19. int[] columnTypes = new int[columns.length];
  20. for (int i = 0; i < columnTypes.length; i++) {
  21. //The set of column types also begins with '1' rather than '0'
  22. columnTypes[i] = rsmd.getColumnType(i + 1);
  23. }
  24.  
  25. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  26. DocumentBuilder db = dbf.newDocumentBuilder();
  27. doc = db.newDocument();
  28.  
  29. Element root = doc.createElement("ResultSet");
  30. doc.appendChild(root);
  31.  
  32. Text text = null;
  33. //We are now ready to loop through the ResultSet. For each row found,
  34. //we add new elements that are named after the columns in the ResultSet,
  35. //have an attribute that mentions the formal (SQL) type of the result, and
  36. //hold the value for the column in each row
  37. while (rs.next()) {
  38. Element row = doc.createElement("row");
  39. root.appendChild(row);
  40.  
  41. //Each row has a fixed set of columns, loop through each
  42. //column, testing against the type of the column as indicated in the
  43. //ResultSetMetaData interface.
  44. for (int i = 0; i < columns.length; i++) {
  45. Element elem = doc.createElement(columns[i]);
  46. row.appendChild(elem);
  47.  
  48. switch (columnTypes[i]) {
  49. case Types.BIGINT:
  50. long bint = rs.getLong(i + 1);
  51. //Add a 'type' attribute to the element. This may be
  52. //useful in identifying how the data should be handled
  53. //if it is read from the Document later on
  54. elem.setAttribute("type", "BigInt");
  55. text = doc.createTextNode(Long.toString(bint));
  56. break;
  57. case Types.BOOLEAN:
  58. boolean bool = rs.getBoolean(i + 1);
  59. elem.setAttribute("type", "boolean");
  60. text = doc.createTextNode(Boolean.toString(bool));
  61. break;
  62. case Types.DATE:
  63. ...//And so on for all the types defined in java.sql.Types
  64. case Types.LONGVARCHAR:
  65. case Types.VARCHAR:
  66. String str = rs.getString(i + 1);
  67. elem.setAttribute("type", "Varchar");
  68. if (str == null) {
  69. //Add a null attribute to the element to represent null
  70. //value in the database
  71. elem.setAttribute("null", "true");
  72. str = "";
  73. }
  74. text = (Text)doc.createCDATASection(str);
  75. break;
  76. default:
  77. //log here because we didn't figure out
  78. //what this thing was
  79. log.warn("Unknown element type found!!!");
  80. log.warn("Column = " + columns[i]);
  81. elem.setAttribute("type", "Unknown");
  82. text = doc.createTextNode("");
  83. break;
  84. }
  85. elem.appendChild(text);
  86. }
  87. }
  88.  
  89. } catch (SQLException sqle) {
  90. //Handle the exception in some way
  91. } catch (ParserConfigurationException pce) {
  92. //Handle the exception in some way
  93. }
  94. return doc;
  95. }

Compared to the first approach described in this article, this method provides a much easier way of building an XML document from data that resides in a database. Using the Regatta database as an example, lets create an XML document containing race results for all boats in the "J-24" boat class using the method above:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <ResultSet>
  3. <row>
  4. <raceday type="Date">2002-09-15</raceday>
  5. <racenumber type="integer">1</racenumber>
  6. <boatname type="Varchar"><![CDATA[Gertrude]]></boatname>
  7. <name type="Varchar"><![CDATA[Jane Seamore]]></name>
  8. <starttime type="Varchar"><![CDATA[09:00:01]]></starttime>
  9. <endtime type="Varchar"><![CDATA[12:00:01]]></endtime>
  10. <adjustedtime type="Varchar"><![CDATA[3:0:0]]></adjustedtime>
  11. <class type="Varchar"><![CDATA[J 24]]></class>
  12. </row>
  13. <row>
  14. <raceday type="Date">2002-06-16</raceday>
  15. <racenumber type="integer">1</racenumber>
  16. <boatname type="Varchar"><![CDATA[Big Bess]]></boatname>
  17. <name type="Varchar"><![CDATA[Peter Tosh]]></name>
  18. <starttime type="Varchar"><![CDATA[08:00:01]]></starttime>
  19. <endtime type="Varchar"><![CDATA[12:12:12]]></endtime>
  20. <adjustedtime type="Varchar"><![CDATA[4:12:11]]></adjustedtime>
  21. <class type="Varchar"><![CDATA[J 24]]></class>
  22. </row>
  23. <row>
  24. <raceday type="Date">2002-09-15</raceday>
  25. <racenumber type="integer">1</racenumber>
  26. <boatname type="Varchar"><![CDATA[Lucky Slew]]></boatname>
  27. <name type="Varchar"><![CDATA[Harvey Wallbanger]]></name>
  28. <starttime type="Varchar"><![CDATA[09:00:01]]></starttime>
  29. <endtime type="Varchar"><![CDATA[14:41:00]]></endtime>
  30. <adjustedtime type="Varchar"><![CDATA[5:40:59]]></adjustedtime>
  31. <class type="Varchar"><![CDATA[J 24]]></class>
  32. </row>
  33. <row>
  34. <raceday type="Date">2002-09-15</raceday>
  35. <racenumber type="integer">1</racenumber>
  36. <boatname type="Varchar"><![CDATA[Monkey Business]]></boatname>
  37. <name type="Varchar"><![CDATA[Gary Hart]]></name>
  38. <starttime type="Varchar"><![CDATA[09:00:01]]></starttime>
  39. <endtime type="Varchar"><![CDATA[14:09:00]]></endtime>
  40. <adjustedtime type="Varchar"><![CDATA[5:8:59]]></adjustedtime>
  41. <class type="Varchar"><![CDATA[J 24]]></class>
  42. </row>
  43. </ResultSet>

With the data in XML format, there are now a great number of things that can be done depending on whether the data is ready to be consumed or if it needs to be transformed to fit a different organizational model. If the data does need to be rearranged, the Document could be passed to an XSL Stylesheet to transform its content into a more meaningful format.

Creating an XML Schema to match the organization of the database is made using the DatabaseMetaData interface and through the use of the ResultSet to Document conversion method. The code example below shows how a few calls to the DatabaseMetaData interface reveal all the details about the structures and relationships defined in the database schema.

  1. public Document getDBMetadataAsDocument(String catalog, String schemaPattern,
  2. String tableNamePattern, String[] types)
  3. {
  4. Document doc = null;
  5. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  6.  
  7. try {
  8. DocumentBuilder db = dbf.newDocumentBuilder();
  9. doc = db.newDocument();
  10. Element root = doc.createElement("root");
  11. doc.appendChild(root);
  12.  
  13. DatabaseMetaData dbmd = conn.getMetaData();
  14. //Retrieve a list of the tables belonging to the catalog and
  15. //database schema indicated in the call to this method
  16. ResultSet rs = dbmd.getTables(catalog, schemaPattern, tableNamePattern, types);
  17. Element tables = doc.createElement("tables");
  18. root.appendChild(tables);
  19.  
  20. //All the details about the returned tables are now added to
  21. //the main document
  22. tables.appendChild(
  23. doc.importNode(
  24. convertRSToDocument(rs).getDocumentElement(),
  25. true));
  26. //Our document now has all the tables in the database. For each
  27. //table, we need to get the rest of the metadata.
  28. //To get the table names, we need to query the document for any
  29. //elements called 'TABLE_NAME'. These came from the call that was just made.
  30. NodeList nodes = doc.getElementsByTagName("TABLE_NAME");
  31. String[] tableNames = new String[nodes.getLength()];
  32. for (int i = 0; i < nodes.getLength(); i++) {
  33. tableNames[i] = nodes.item(i).getFirstChild().getNodeValue();
  34. }
  35.  
  36. for (int i = 0; i < tableNames.length; i++) {
  37. //Getting table columns...
  38. rs = dbmd.getColumns(catalog, schemaPattern, tableNames[i], null);
  39. addResultsToNode(rs, root, "columns")
  40.  
  41. //Getting primary keys...
  42. rs = dbmd.getPrimaryKeys(catalog, schemaPattern, tableNames[i]);
  43. addResultsToNode(rs, root, "primarykeys");
  44.  
  45. //Getting foreign keys and where they point...
  46. rs = dbmd.getImportedKeys(catalog, schemaPattern, tableNames[i]);
  47. addResultsToNode(rs, root, "importedkeys");
  48.  
  49. }
  50.  
  51. //Change the DATA_TYPE elements to their SQL type names just for
  52. //aesthetics... They are actually returned as integers by the
  53. //DatabaseMetaData API
  54. nodes = doc.getElementsByTagName("DATA_TYPE");
  55. for (int i = 0; i < nodes.getLength(); i++) {
  56. int type = Integer.parseInt(
  57. nodes.item(i).getFirstChild().getNodeValue());
  58. switch(type) {
  59. case Types.ARRAY:
  60. nodes.item(i).getFirstChild().setNodeValue("Array");
  61. break;
  62. case Types.BIGINT:
  63. nodes.item(i).getFirstChild().setNodeValue("BigInt");
  64. break;
  65. case Types.BINARY:
  66. nodes.item(i).getFirstChild().setNodeValue("Binary");
  67. break;
  68. case Types.BIT:
  69. nodes.item(i).getFirstChild().setNodeValue("Bit");
  70. break;
  71. ...//And so on for all valid SQL types (Defined in java.sql.Types)
  72. case Types.VARCHAR:
  73. nodes.item(i).getFirstChild().setNodeValue("Varchar");
  74. break;
  75. default:
  76. nodes.item(i).getFirstChild().setNodeValue("Unknown");
  77. break;
  78. }
  79. }
  80.  
  81. } catch (SQLException sqle) {
  82. //Handle the exception in some way
  83. } catch (ParserConfigurationException pce) {
  84. //Handle the exception in some way
  85. } catch (DOMException de) {
  86. //Handle the exception in some way
  87. }
  88. return doc;
  89. }
  90.  
  91. private void addResultsToNode(ResultSet rs, Node node, String elementName)
  92. throws DOMException
  93. {
  94. Document doc = null;
  95. if (node instanceof Document) {
  96. doc = (Document)node;
  97. } else {
  98. doc = node.getOwnerDocument();
  99. }
  100. Element elem = doc.createElement(elementName);
  101. node.appendChild(elem);
  102.  
  103. //The call below does several things:
  104. //1) It converts the ResultSet that was retrieved earlier from a call to the
  105. //DatabaseMetaData into a Document object (using the convertRSToDocument()
  106. //method that appears above)
  107. //2) It imports (through a deep copy) all of the nodes from the new Document
  108. //object into the Document object of the node based into the call to this
  109. //method
  110. //3) It adds those newly imported nodes as children of a node to the
  111. //main document
  112. elem.appendChild(
  113. doc.importNode(
  114. convertRSToDocument(rs).getDocumentElement(),
  115. true));
  116. }

The method above creates a Document with details about the schema of a particular database. Using the Regatta database (from above), it is possible to see what this database metadata gathering method provides:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <root>
  3. <tables>
  4. <ResultSet>
  5. <row>
  6. <TABLE_CAT type="Varchar" null="true"></TABLE_CAT>
  7. <TABLE_SCHEM type="Varchar" null="true"></TABLE_SCHEM>
  8. <TABLE_NAME type="Varchar"><![CDATA[boat_class]]></TABLE_NAME>
  9. <TABLE_TYPE type="Varchar"><![CDATA[TABLE]]></TABLE_TYPE>
  10. <REMARKS type="Varchar" null="true"></REMARKS>
  11. </row>
  12. <row>
  13. <TABLE_CAT type="Varchar" null="true"></TABLE_CAT>
  14. <TABLE_SCHEM type="Varchar" null="true"></TABLE_SCHEM>
  15. <TABLE_NAME type="Varchar"><![CDATA[boats]]></TABLE_NAME>
  16. <TABLE_TYPE type="Varchar"><![CDATA[TABLE]]></TABLE_TYPE>
  17. <REMARKS type="Varchar" null="true"></REMARKS>
  18. </row>
  19.  
  20. <!--...And so on for the rest of the tables-->

This is what the column nodes look like:

  1. <columns>
  2. <ResultSet>
  3. <row>
  4. <TABLE_CAT type="Varchar" null="true"></TABLE_CAT>
  5. <TABLE_SCHEM type="Varchar" null="true"></TABLE_SCHEM>
  6. <TABLE_NAME type="Varchar"><![CDATA[boat_class]]></TABLE_NAME>
  7. <COLUMN_NAME type="Varchar"><![CDATA[classid]]></COLUMN_NAME>
  8. <DATA_TYPE type="TinyInt">Integer</DATA_TYPE>
  9. <TYPE_NAME type="Varchar"><![CDATA[int4]]></TYPE_NAME>
  10. <COLUMN_SIZE type="integer">4</COLUMN_SIZE>
  11. <BUFFER_LENGTH type="Varchar" null="true"></BUFFER_LENGTH>
  12. <DECIMAL_DIGITS type="integer">0</DECIMAL_DIGITS>
  13. <NUM_PREC_RADIX type="integer">10</NUM_PREC_RADIX>
  14. <NULLABLE type="integer">0</NULLABLE>
  15. <REMARKS type="Varchar" null="true"></REMARKS>
  16. <COLUMN_DEF type="Varchar"><![CDATA[nextval('"boat_class_classid_seq"'::text)]]></COLUMN_DEF>
  17. <SQL_DATA_TYPE type="integer">0</SQL_DATA_TYPE>
  18. <SQL_DATETIME_SUB type="integer">0</SQL_DATETIME_SUB>
  19. <CHAR_OCTET_LENGTH type="Varchar"><![CDATA[4]]></CHAR_OCTET_LENGTH>
  20. <ORDINAL_POSITION type="integer">1</ORDINAL_POSITION>
  21. <IS_NULLABLE type="Varchar"><![CDATA[NO]]></IS_NULLABLE>
  22. </row>
  23.  
  24. <!--...And so on for the rest of the columns-->

The primary key nodes look like this:

  1. <primarykeys>
  2. <ResultSet>
  3. <row>
  4. <table_cat type="Unknown"></table_cat>
  5. <table_schem type="Unknown"></table_schem>
  6. <table_name type="Varchar"><![CDATA[boat_class]]></table_name>
  7. <column_name type="Varchar"><![CDATA[classid]]></column_name>
  8. <key_seq type="TinyInt">1</key_seq>
  9. <pk_name type="Varchar"><![CDATA[boat_class_pk]]></pk_name>
  10. </row>
  11. </ResultSet>
  12. </primarykeys>

And the foreign key nodes (referred to as importedkeys in the API) look like this:

  1. <importedkeys>
  2. <ResultSet>
  3. <row>
  4. <PKTABLE_CAT type="Varchar" null="true"></PKTABLE_CAT>
  5. <PKTABLE_SCHEM type="Varchar" null="true"></PKTABLE_SCHEM>
  6. <PKTABLE_NAME type="Varchar"><![CDATA[boat_class]]></PKTABLE_NAME>
  7. <PKCOLUMN_NAME type="Varchar"><![CDATA[classid]]></PKCOLUMN_NAME>
  8. <FKTABLE_CAT type="Varchar" null="true"></FKTABLE_CAT>
  9. <FKTABLE_SCHEM type="Varchar" null="true"></FKTABLE_SCHEM>
  10. <FKTABLE_NAME type="Varchar"><![CDATA[boats]]></FKTABLE_NAME>
  11. <FKCOLUMN_NAME type="Varchar"><![CDATA[class]]></FKCOLUMN_NAME>
  12. <KEY_SEQ type="TinyInt">0</KEY_SEQ>
  13. <UPDATE_RULE type="TinyInt">3</UPDATE_RULE>
  14. <DELETE_RULE type="TinyInt">3</DELETE_RULE>
  15. <FK_NAME type="Varchar"><![CDATA[boats_class_fk]]></FK_NAME>
  16. <PK_NAME type="Varchar"><![CDATA[boat_class_pk]]></PK_NAME>
  17. <DEFERRABILITY type="TinyInt">7</DEFERRABILITY>
  18. </row>
  19.  
  20. <!--...And so on for the rest of the foreign keys-->

While the data in this format seems useful, what is really desired is an XML Schema that reflects the structures and relationships defined within the database. To get the XML document (partially displayed above) into XML Schema format, an XSL Stylesheet is needed to transform and reorganize the data into a notation that follows XML Schema language. As it turns out, the stylesheet to create the XML Schema from this document is surprisingly simple:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <xsl:stylesheet version="1.0"
  3. xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  4. xmlns:xs="http://www.w3.org/2001/XMLSchema">
  5. <xsl:output method="xml" indent="yes" encoding="UTF-8" version="1.0"></xsl:output>
  6.  
  7. <xsl:template match="/">
  8. <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  9. <xsl:apply-templates select="//tables//TABLE_NAME"></xsl:apply>
  10. </xs:schema>
  11. </xsl:template>
  12.  
  13. <xsl:template match="TABLE_NAME">
  14. <xsl:variable name="tableName" select="."></xsl:variable>
  15. <xs:element>
  16. <xsl:attribute name="name"><xsl:value-of select="$tableName"></xsl:value></xsl:attribute>
  17. <xs:complexType>
  18. <xs:sequence>
  19. <xsl:apply-templates select="//columns//COLUMN_NAME[preceding-sibling::TABLE_NAME=$tableName]"></xsl:apply>
  20. </xs:sequence>
  21. </xs:complexType>
  22. <xsl:apply-templates select="//primarykeys//pk_name[preceding-sibling::table_name=$tableName]"></xsl:apply>
  23. <xsl:apply-templates select="//importedkeys//FK_NAME[preceding-sibling::FKTABLE_NAME=$tableName]"></xsl:apply>
  24. </xs:element>
  25. </xsl:template>
  26.  
  27. <xsl:template match="COLUMN_NAME">
  28. <xsl:variable name="columnName" select="."></xsl:variable>
  29. <xsl:variable name="datatype" select="$columnName/following-sibling::DATA_TYPE"></xsl:variable>
  30. <xs:element>
  31. <xsl:attribute name="name"><xsl:value-of select="$columnName"></xsl:value></xsl:attribute>
  32. <xsl:choose>
  33. <xsl:when test="$datatype='Varchar'">
  34. <xs:simpleType>
  35. <xs:restriction base="xs:string">
  36. <xs:maxLength value="{$datatype/following-sibling::COLUMN_SIZE}"></xs:maxLength>
  37. </xs:restriction>
  38. </xs:simpleType>
  39. </xsl:when>
  40. <xsl:otherwise>
  41. <xsl:attribute name="type">
  42. <xsl:choose>
  43. <xsl:when test="$datatype='BigInt'">xs:long</xsl:when>
  44. <xsl:when test="$datatype='Boolean'">xs:boolean</xsl:when>
  45. <xsl:when test="$datatype='Date'">xs:date</xsl:when>
  46. <xsl:when test="$datatype='Timestamp'">xs:dateTime</xsl:when>
  47. <xsl:when test="$datatype='Double'">xs:double</xsl:when>
  48. <xsl:when test="$datatype='Float'">xs:float</xsl:when>
  49. <xsl:when test="$datatype='Integer'">xs:int</xsl:when>
  50. <xsl:when test="$datatype='Time'">xs:time</xsl:when>
  51. <xsl:when test="$datatype='TinyInt'">xs:short</xsl:when>
  52. <xsl:otherwise>xs:any</xsl:otherwise>
  53. </xsl:choose>
  54. </xsl:attribute>
  55. </xsl:otherwise>
  56. </xsl:choose>
  57. </xs:element>
  58. </xsl:template>
  59.  
  60. <xsl:template match="pk_name">
  61. <xsl:variable name="primaryKey" select="."></xsl:variable>
  62. <xs:key>
  63. <xsl:attribute name="name"><xsl:value-of select="$primaryKey"></xsl:value></xsl:attribute>
  64. <xs:selector>
  65. <xsl:attribute name="xpath">.</xsl:attribute>
  66. </xs:selector>
  67. <xs:field>
  68. <xsl:attribute name="xpath">
  69. <xsl:value-of select="$primaryKey/preceding-sibling::column_name"></xsl:value>
  70. </xsl:attribute>
  71. </xs:field>
  72. </xs:key>
  73. </xsl:template>
  74.  
  75. <xsl:template match="FK_NAME">
  76. <xsl:variable name="foreignKey" select="."></xsl:variable>
  77. <xs:keyref>
  78. <xsl:attribute name="name">
  79. <xsl:value-of select="$foreignKey"></xsl:value>
  80. </xsl:attribute>
  81. <xsl:attribute name="refer">
  82. <xsl:value-of select="$foreignKey/following-sibling::PK_NAME"></xsl:value>
  83. </xsl:attribute>
  84. <xs:selector>
  85. <xsl:attribute name="xpath">.</xsl:attribute>
  86. </xs:selector>
  87. <xs:field>
  88. <xsl:attribute name="xpath">
  89. <xsl:value-of select="$foreignKey/preceding-sibling::FKCOLUMN_NAME"></xsl:value>
  90. </xsl:attribute>
  91. </xs:field>
  92. </xs:keyref>
  93. </xsl:template>
  94. </xsl:stylesheet>

The stylesheet goes top-down through the XML document, first getting table structures (elements with the name TABLE_NAME) and then getting each of their columns (COLUMN_NAME elements), datatypes (converted from their SQL types into corresponding XML Schema simple types), primary keys (pk_name elements) and any foreign keys (FK_NAME elements) the table may have.

The primary keys are represented (and enforced) within XML Schema language by elements. These elements define a 'name' attribute (representing the name of the key) and have and child elements, each having xpath attributes. The element requires that it's child be unique within the document and must not be nil (i.e. it cannot be empty). The element indicates the context node for which the key is defined. This behavior matches that of primary keys in a database.

Foreign keys in XML Schema (defined in elements) have a structure almost identical to their primary key counterparts ( elements); all elements define the same attributes and child elements as the elements and additionally define an attribute named 'refer'. Like the elements, an element requires that it's element not be empty, however the value of the element must match exactly the value of the element defined for the element whose name is referred to in the refer attribute. These structures enforce referential integrity between and elements much like databases do between primary and foreign keys.

The following is the XML Schema that is the final product of the XSL Stylesheet transformation:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  3. <xs:element name="boat_class">
  4. <xs:complexType>
  5. <xs:sequence>
  6. <xs:element name="classid" type="xs:int" ></xs:element>
  7. <xs:element name="class">
  8. <xs:simpleType>
  9. <xs:restriction base="xs:string">
  10. <xs:maxLength value="64" ></xs:maxLength>
  11. </xs:restriction>
  12. </xs:simpleType>
  13. </xs:element>
  14. <xs:element name="phrf" type="xs:int" ></xs:element>
  15. </xs:sequence>
  16. </xs:complexType>
  17. <xs:key name="boat_class_pk">
  18. <xs:selector xpath="." ></xs:selector>
  19. <xs:field xpath="classid" ></xs:field>
  20. </xs:key>
  21. </xs:element>
  22. <xs:element name="boats">
  23. <xs:complexType>
  24. <xs:sequence>
  25. <xs:element name="boatid" type="xs:int" ></xs:element>
  26. <xs:element name="skipper" type="xs:int" ></xs:element>
  27. <xs:element name="class" type="xs:int" ></xs:element>
  28. <xs:element name="sailnumber" type="xs:int" ></xs:element>
  29. <xs:element name="boatname">
  30. <xs:simpleType>
  31. <xs:restriction base="xs:string">
  32. <xs:maxLength value="64" ></xs:maxLength>
  33. </xs:restriction>
  34. </xs:simpleType>
  35. </xs:element>
  36. </xs:sequence>
  37. </xs:complexType>
  38. <xs:key name="boats_pk">
  39. <xs:selector xpath="." ></xs:selector>
  40. <xs:field xpath="boatid" ></xs:field>
  41. </xs:key>
  42. <xs:keyref name="boats_class_fk" refer="boat_class_pk">
  43. <xs:selector xpath="." ></xs:selector>
  44. <xs:field xpath="class" ></xs:field>
  45. </xs:keyref>
  46. <xs:keyref name="boats_skipper_fk" refer="skipper_pk">
  47. <xs:selector xpath="." ></xs:selector>
  48. <xs:field xpath="skipper" ></xs:field>
  49. </xs:keyref>
  50. </xs:element>
  51. <xs:element name="race_days">
  52. <xs:complexType>
  53. <xs:sequence>
  54. <xs:element name="racedayid" type="xs:int" ></xs:element>
  55. <xs:element name="raceday" type="xs:date" ></xs:element>
  56. <xs:element name="racename">
  57. <xs:simpleType>
  58. <xs:restriction base="xs:string">
  59. <xs:maxLength value="64" ></xs:maxLength>
  60. </xs:restriction>
  61. </xs:simpleType>
  62. </xs:element>
  63. </xs:sequence>
  64. </xs:complexType>
  65. <xs:key name="race_days_pk">
  66. <xs:selector xpath="." ></xs:selector>
  67. <xs:field xpath="racedayid" ></xs:field>
  68. </xs:key>
  69. </xs:element>
  70. <xs:element name="race_number">
  71. <xs:complexType>
  72. <xs:sequence>
  73. <xs:element name="racenumberid" type="xs:int" ></xs:element>
  74. <xs:element name="racedayid" type="xs:int" ></xs:element>
  75. <xs:element name="racenumber" type="xs:int" ></xs:element>
  76. </xs:sequence>
  77. </xs:complexType>
  78. <xs:key name="race_number_pk">
  79. <xs:selector xpath="." ></xs:selector>
  80. <xs:field xpath="racenumberid" ></xs:field>
  81. </xs:key>
  82. <xs:keyref name="race_number_racedayid_fk" refer="race_days_pk">
  83. <xs:selector xpath="." ></xs:selector>
  84. <xs:field xpath="racedayid" ></xs:field>
  85. </xs:keyref>
  86. </xs:element>
  87. <xs:element name="race_results">
  88. <xs:complexType>
  89. <xs:sequence>
  90. <xs:element name="raceresultid" type="xs:int" ></xs:element>
  91. <xs:element name="racenumberid" type="xs:int" ></xs:element>
  92. <xs:element name="boatid" type="xs:int" ></xs:element>
  93. <xs:element name="skipperid" type="xs:int" ></xs:element>
  94. <xs:element name="starttime">
  95. <xs:simpleType>
  96. <xs:restriction base="xs:string">
  97. <xs:maxLength value="64" ></xs:maxLength>
  98. </xs:restriction>
  99. </xs:simpleType>
  100. </xs:element>
  101. <xs:element name="endtime">
  102. <xs:simpleType>
  103. <xs:restriction base="xs:string">
  104. <xs:maxLength value="64" ></xs:maxLength>
  105. </xs:restriction>
  106. </xs:simpleType>
  107. </xs:element>
  108. <xs:element name="adjustedtime">
  109. <xs:simpleType>
  110. <xs:restriction base="xs:string">
  111. <xs:maxLength value="64" ></xs:maxLength>
  112. </xs:restriction>
  113. </xs:simpleType>
  114. </xs:element>
  115. </xs:sequence>
  116. </xs:complexType>
  117. <xs:key name="race_results_pk">
  118. <xs:selector xpath="." ></xs:selector>
  119. <xs:field xpath="raceresultid" ></xs:field>
  120. </xs:key>
  121. <xs:keyref name="race_results_boatid_fk" refer="boats_pk">
  122. <xs:selector xpath="." ></xs:selector>
  123. <xs:field xpath="boatid" ></xs:field>
  124. </xs:keyref>
  125. <xs:keyref name="race_results_racenumberid_fk" refer="race_number_pk">
  126. <xs:selector xpath="." ></xs:selector>
  127. <xs:field xpath="racenumberid" ></xs:field>
  128. </xs:keyref>
  129. <xs:keyref name="race_results_skipperid_fk" refer="skipper_pk">
  130. <xs:selector xpath="." ></xs:selector>
  131. <xs:field xpath="skipperid" ></xs:field>
  132. </xs:keyref>
  133. </xs:element>
  134. <xs:element name="skipper">
  135. <xs:complexType>
  136. <xs:sequence>
  137. <xs:element name="skipperid" type="xs:int" ></xs:element>
  138. <xs:element name="skippername">
  139. <xs:simpleType>
  140. <xs:restriction base="xs:string">
  141. <xs:maxLength value="64" ></xs:maxLength>
  142. </xs:restriction>
  143. </xs:simpleType>
  144. </xs:element>
  145. </xs:sequence>
  146. </xs:complexType>
  147. <xs:key name="skipper_pk">
  148. <xs:selector xpath="." ></xs:selector>
  149. <xs:field xpath="skipperid" ></xs:field>
  150. </xs:key>
  151. </xs:element>
  152. </xs:schema>

Summary

This article has shown how two of the metadata interfaces provided by Java's JDBC API can be used to convert ResultSets into XML instance documents and XML Schemas that match the structure and relationships expressed in a database. This is a very powerful capability because it highlights how easy it can be to transform data from one format into another. One common use of XML as a systems integration technology is the exchange of XML documents containing data that two applications are meant to share. Using the methods described above, it is possible to create not only XML documents from simple queries to a database, but also an XML Schema capable of validating both the structure and the format of the data described in the XML document. It should therefore be possible to replicate a database from one system to another, including the creation of database schema SQL files (which are just text files) reverse-engineered from the XML Schemas that describe them.

Resources

The source code for this article can be found here.

References

The following are links to sites that offer related or supplementary information based on the topic of this article.

Software Engineering Tech Trends is a monthly publication featuring emerging trends in software engineering.

© Object Computing, Inc. 1993, 2019. All rights reserved.

secret