Commit 1436f602 authored by Dr. Martin Goik's avatar Dr. Martin Goik

New SAX/JDBC Exercise

parent 589c9b70
.project
.classpath
/.settings/
/target/
A1.log
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="catalog">
<xs:complexType>
<xs:sequence>
<xs:element ref="product" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="product">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="description" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="age" type="xs:int" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="id" type="xs:ID" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
DROP TABLE IF EXISTS Description;
DROP TABLE IF EXISTS Product;
CREATE TABLE Product (
id CHAR(20) NOT NULL PRIMARY KEY
,name VARCHAR(255) NOT NULL
,age SMALLINT
);
CREATE TABLE Description (
product CHAR(20) NOT NULL REFERENCES Product
,orderIndex int NOT NULL -- preserving the order of descriptions belonging to a given product
,text VARCHAR(255) NOT NULL
,UNIQUE(product, orderIndex)
);
-- example data corresponding to products.xml --
-- Product lacking age property --
INSERT INTO Product (id, name) VALUES ('mpt', 'Monkey Picked Tea');
INSERT INTO Description VALUES('mpt', 0, 'Picked only by specially trained monkeys');
INSERT INTO Description VALUES('mpt', 1, 'Rare wild Chinese tea');
INSERT INTO Product VALUES ('instantTent', '4-Person Instant Tent', 15);
INSERT INTO Description VALUES('instantTent', 0, 'Exclusive WeatherTec system.');
INSERT INTO Description VALUES('instantTent', 1, '4-person, 1-room tent');
INSERT INTO Description VALUES('instantTent', 2, 'Pre-attached tent poles');
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.hdm-stuttgart.mi.sda1</groupId>
<artifactId>catalog2rdbms</artifactId>
<version>0.8</version>
<packaging>jar</packaging>
<name>catalog2rdbms</name>
<!--Fixme: Add a sensible project related domain here -->
<url>http://somedomain.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>de.hdm-stuttgart.mi.sda1</groupId>
<artifactId>saxerrorhandler</artifactId>
<version>0.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<configuration />
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>de.hdm_stuttgart.mi.sda1.catalog2sax.App</Main-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Schema/catalog.xsd">
<product id="mpt">
<name>Monkey Picked Tea</name>
<description>Rare wild Chinese tea</description>
<description>Picked only by specially trained monkeys</description>
</product>
<product id="instantTent">
<name>4-Person Instant Tent</name>
<description>4-person, 1-room tent</description>
<description>Pre-attached tent poles</description>
<description>Exclusive WeatherTec system.</description>
<age>15</age>
</product>
</catalog>
\ No newline at end of file
package de.hdm_stuttgart.mi.sda1.catalog2sql;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* Formatting product related INSERT statements
*
*/
public class DataInsert {
final Statement stmt;
/**
* @param conn Destination RDBMS
* @throws SQLException
*/
public DataInsert(final Connection conn) throws SQLException {
stmt = conn.createStatement();
}
/**
* Create an SQL INSERT statement corresponding to a &lt;product id='...' &gt; entry
* lacking age specification.
* @param productId See {@link #insertproduct(String, String, int)}
* @param productName See {@link #insertproduct(String, String, int)}
* @throws SQLException
*/
public void insertproduct(final String productId, final String productName) {
final String sqlInsertStmt = "INSERT INTO Product (id, name) VALUES ('" + productId + "', '" + productName + "');";
try {
stmt.executeUpdate(sqlInsertStmt);
} catch (SQLException e) {
Helper.exitWithErrorMessage("Unable to insert product without age property", 1);
}
}
/**
* Insert dataset corresponding to a &lt;product id='...' &gt; entry
* @param productId The product's unique id property
* @param productName The product's end user readable name.
* @param age The product's age
* @throws SQLException
*/
public void insertproduct(final String productId, final String productName, final int age) {
// A PreparedStatement is preferable but not yet introduced in lecture
//
final String sqlInsertStmt = "INSERT INTO Product VALUES ('" + productId + "', '" + productName + "', " + age + ");";
try {
stmt.executeUpdate(sqlInsertStmt);
} catch (SQLException e) {
Helper.exitWithErrorMessage("Unable to insert product including age property", 1);
}
}
/**
* Adding a description to a given product.
*
* @param productId The description belongs to this product
* @param descriptions All descriptions belonging to productId
* @throws SQLException
*/
public void insertDescription(final String productId, final List<String> descriptions) {
for (int i = 0; i < descriptions.size(); i++) {
final String sqlInsertStmt = "INSERT INTO Description VALUES('" + productId + "', " + i + ", '" +
descriptions.get(i) + "');";
try {
stmt.executeUpdate(sqlInsertStmt);
} catch (SQLException e) {
Helper.exitWithErrorMessage("Unable to insert product description", 1);
}
}
}
public void close() throws SQLException {
stmt.close();
}
}
package de.hdm_stuttgart.mi.sda1.catalog2sql;
public class Helper {
public static void exitWithErrorMessage(final String errMsg, int errorCode) {
System.err.println(errMsg);
System.exit(errorCode);
}
}
package de.hdm_stuttgart.mi.sda1.catalog2sql;
import java.io.IOException;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import de.hdm_stuttgart.mi.sda1.catalog2sql.handler.CatalogContentHandler;
import de.hdm_stuttgart.mi.sda1.saxerrorhandler.handler.SaxErrorHandler;
/**
* A simple SAX parser demo
*
*/
public class Xml2Rdbms {
private static final Logger log = LogManager.getLogger(Xml2Rdbms.class);
/**
* @param args
* Unused
* @throws SAXException
* @throws ParserConfigurationException
* @throws IOException
*/
public static void main(String[] args) throws ParserConfigurationException,
SAXException, IOException {
log.info("Creating SAX parser");
final SAXParserFactory saxPf = SAXParserFactory.newInstance();
final SAXParser saxParser = saxPf.newSAXParser();
final XMLReader xmlReader = saxParser.getXMLReader();
// Register Driver
final String sqlDriverClassName = "com.mysql.jdbc.Driver";
try {
Class.forName(sqlDriverClassName);
} catch (ClassNotFoundException e) {
Helper.exitWithErrorMessage("Unable to register driver class '" + sqlDriverClassName + "'", 1);
}
// Opening a JDBC connection
//
Connection conn = null;
final String jdbcConnectionUrl = "jdbc:mysql://localhost:3306/hdm";
final String userName = "hdmuser";
try {
conn = DriverManager.getConnection(jdbcConnectionUrl, userName, "XYZ");
} catch (SQLException e) {
Helper.exitWithErrorMessage("Unable to connect as user '" + userName + "' to '" +
jdbcConnectionUrl + "'", 1);
}
DataInsert dataInsert = null;
try {
dataInsert = new DataInsert(conn);
} catch (SQLException e) {
Helper.exitWithErrorMessage("Unable to initialize data inserter", 1);
}
log.info("Registering content- and error handler instances");
xmlReader.setContentHandler(new CatalogContentHandler(dataInsert));
xmlReader.setErrorHandler(new SaxErrorHandler(System.err));
final String xmlDocumentInstanceFilename = "products.xml";
log.info("Start parsing file '" + xmlDocumentInstanceFilename + "'");
xmlReader.parse(xmlDocumentInstanceFilename);
// Closing
try {
dataInsert.close(); // Closing Statement
conn.close(); // Closing Connection
} catch (SQLException e) {
Helper.exitWithErrorMessage("Unable to close RDBMS access", 1);
}
}
}
package de.hdm_stuttgart.mi.sda1.catalog2sql.handler;
import java.util.List;
import java.util.Vector;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import de.hdm_stuttgart.mi.sda1.catalog2sql.DataInsert;
/**
* Turning XML data into SQL INSERT statements
*
*/
public class CatalogContentHandler implements ContentHandler {
private static final Logger log = LogManager.getLogger(CatalogContentHandler.class);
Locator locator = null;
final DataInsert sqlFormatter;
private final List<String> currentDescriptions = new Vector<String>();
final StringBuffer currentElementContent = new StringBuffer();
String productId, productName, productAgeString;
/**
* @param sqlFormatter SQL INSERT statement formatter
*/
public CatalogContentHandler(final DataInsert sqlFormatter) {
this.sqlFormatter = sqlFormatter;
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
// Convenience Method
String displayWithLocatio(final String saxMsg) {
if (null == locator) {
return saxMsg;
} else {
return "File position (" + locator.getLineNumber() + ", " + locator.getColumnNumber() + "): " + saxMsg;
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
currentElementContent.setLength(0);
switch(qName) {
case "product":
productAgeString = null; // Value will be altered if &lt;age&gt; is present
productId = attributes.getValue("id");
log.info(displayWithLocatio("Product id=" + productId));
break;
}
}
@Override
public void endElement(final String uri, final String localName, final String qName)
throws SAXException {
switch(qName) {
case "product":
if (null == productAgeString) {
sqlFormatter.insertproduct(productId, productName);
} else {
try {
sqlFormatter.insertproduct(productId, productName, Integer.parseInt(productAgeString));
} catch (NumberFormatException ex) {
log.error("Property <age> is not of integer value:" + productAgeString);
}
}
flushDescriptionEntries();
break;
case "name":
productName = currentElementContent.toString();
break;
case "description":
// Do not interfere with the current INSERT INTO Product ...
// statement. Instead postpone related INSERT INTO Description ...
// operations, see flushDescriptionEntries().
currentDescriptions.add(currentElementContent.toString());
break;
case "age":
productAgeString = currentElementContent.toString();
break;
}
}
private void flushDescriptionEntries() {
// Add <description> related INSERTs
sqlFormatter.insertDescription(productId, currentDescriptions);
// Next <product> may be yet to come, so
// clear the current set of descriptions.
currentDescriptions.clear();
}
@Override
public void characters(final char[] ch, final int start, final int length)
throws SAXException {
currentElementContent.append(new String(ch, start,length));
}
// We don't need these remaining callbacks
@Override public void startDocument() throws SAXException {}
@Override public void endDocument() throws SAXException {}
@Override public void startPrefixMapping(String prefix, String uri)
throws SAXException {}
@Override public void endPrefixMapping(String prefix)
throws SAXException {}
@Override public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {}
@Override public void processingInstruction(String target, String data)
throws SAXException {}
@Override public void skippedEntity(String name) throws SAXException {}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<File name="A1" fileName="A1.log" append="false">
<PatternLayout pattern="%t %-5p %c{2} - %m%n"/>
</File>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<!-- You my want to define class or package level per-logger rules -->
<Logger name="de.hdm_stuttgart.mi.sda1.catalog2sax.App" level="debug">
<AppenderRef ref="A1"/>
</Logger>
<Root level="debug">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
\ No newline at end of file
......@@ -9643,6 +9643,28 @@ public class SimpleInsert {
</qandaset>
</section>
<section xml:id="xmldata2rdbms">
<title>Moving data from XML to relational systems</title>
<qandaset defaultlabel="qanda" xml:id="qandaXmldata2relational">
<title>Avoiding intermediate <xref linkend="glo_SQL"/> file
export</title>
<qandadiv>
<qandaentry>
<question>
<para>In <xref linkend="quandaentry_SqlFromXml"/> you
implemented a <xref linkend="glo_SAX"/> application
transforming XML product catalog instances into a series of
SQL statements. Modify your solution by directly inserting
corresponding data by means of <xref linkend="glo_JDBC"/> into
a relational database.</para>
</question>
</qandaentry>
</qandadiv>
</qandaset>
</section>
<section xml:id="sectSimpleInsertGui">
<title>A first GUI sketch</title>
......
......@@ -3,15 +3,19 @@ package de.hdm_stuttgart.mi.sda2.jpa.cd.domain;
import static javax.persistence.CascadeType.PERSIST;
import static javax.persistence.CascadeType.REMOVE;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OrderColumn;
import javax.persistence.Table;
......@@ -26,6 +30,13 @@ public class Student {
@Column(unique=true, nullable=false)
int matriculation;
@ElementCollection
final Set<String> emails = new HashSet<String>();
@ElementCollection
@OrderColumn
final List<Address> addresses = new ArrayList<Address>();
@OneToMany(mappedBy="student", cascade={PERSIST, REMOVE})
Set<StudentLecture> lectures = new HashSet<StudentLecture>();
......@@ -58,4 +69,11 @@ public class Student {
public Long getId() {
return id;
}
public Set<String> getEmails() {
return emails;
}
public List<Address> getAddresses() {
return addresses;
}
}
......@@ -9,6 +9,7 @@ import javax.xml.bind.JAXBException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hdm_stuttgart.mi.sda2.jpa.cd.domain.Address;
import de.hdm_stuttgart.mi.sda2.jpa.cd.domain.Lecture;
import de.hdm_stuttgart.mi.sda2.jpa.cd.domain.Student;
import de.hdm_stuttgart.mi.sda2.jpa.cd.domain.StudentLecture;
......@@ -34,7 +35,20 @@ public class Driver {
final EntityTransaction tx = manager.getTransaction();
final Student joe = new Student("Joe Blix", 12346);
final Student joe = new Student("Joe Blix", 12346),
eve = new Student("Eve Gardener", 54321);
// Adding E-Mails
joe.getEmails().add("joe@test.com");
joe.getEmails().add("joe@blix.org");
// Adding addresses
joe.getAddresses().add(new Address("Schulstrasse 4", "Bad Oyenhausen", "32547"));
joe.getAddresses().add(new Address("Am Märzenbaum 55", "Gummersbach", "66883"));
eve.getAddresses().add(new Address("Grüner Weg 6", "Friedberg", "61169"));
eve.getAddresses().add(new Address("Grüner Weg 6", "Friedberg", "61169"));
final Lecture db = new Lecture("Database systems", 11312);
final StudentLecture joeDb = new StudentLecture(joe, db, 5);
......@@ -44,26 +58,39 @@ public class Driver {
// enabled
// manager.persist(db);
manager.persist(joeDb);
manager.persist(eve);
}
tx.commit();
tx.begin();
{
manager.refresh(joe);
System.out.println("Lectures of Joe:");
log.info("Lectures of Joe:");
for (StudentLecture sl : joe.getLectures()) {
System.out.println(sl.getLecture().getTitle());
log.info(sl.getLecture().getTitle());
}
}
tx.commit();
tx.begin();
{
System.out.println("Deleting Joe from Database");
manager.remove(joe);
log.info("Reloading Joe:");
final Student joeRetrieved = manager.find(Student.class, joe.getId());
log.info(joeRetrieved.getFullName());
log.info("Retrieving Joe's adresses:" );
for (final Address a: joeRetrieved.getAddresses()) {
log.info(a);
}
}
tx.commit();
// tx.begin();
// {
// log.info("Deleting Joe from Database");
// manager.remove(joe);
// }
// tx.commit();
}
}
......@@ -15,7 +15,11 @@
<!-- EclipseLink should create the database schema automatically -->
<property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
<property name="eclipselink.ddl-generation.output-mode" value="database" />
<property name="eclipselink.logging.level" value="SEVERE"/>
<property name="eclipselink.logging.level" value="SEVERE"/>
<!-- Logging SQL operations -->
<property name="eclipselink.logging.level.sql" value="FINE"/>
<property name="eclipselink.logging.parameters" value="true"/>
</properties>