Gute Performance-Ergebnisse der Java 6 VM

Unter http://blogs.sun.com/dagastine/entry/java_6_leads_out_of schreibt David Dagastine über die ersten Performance-Messungen der JVM Version 6. Die Zahlen (und Grafiken) sehen schon gut aus; die Version 6 schneidet bei (fast) allen Szenarien von Sun am Besten ab — so wurden die Szenarien wahrscheinlich auch ausgesucht 🙂 BEA JRockit 5.0_06 R26.4 hat mich ein wenig überrascht, da die JVM in einigen Messungen leicht über Suns JVM lag, in den meisten Fällen aber weit am Ende lag.

Interessant erscheint mit der Hinweis, dass das Parameter-Tuning vermutlich in Zukunft einfacher wird, weil die JVM automatisch ihre Modi wählt. Die guten Laufzeitergebnisse kamen auch zustande, ohne einen speziellen Schalter gesetzt zu haben.

EJB 2.x und EJB 3.0 vereinen

EJB 2 und EJB 3 im Container

Jeder EJB 3 Container muss auch EJB 2.x Beans verwalten können. Beim Design der neuen Spezifikation war es wichtig, dass
– eine EJB 2.x Bean in eine EJB 3.0 Bean injiziert werden kann,
– eine EJB 3.0 Bean sich von außen als EJB 2.x Bean ansprechen lässt.

Klassisches Home/Component-Interface

Nehmen wir für eine Session-Bean eine EJBHome-Schnittstelle MyHome an, dessen create()-Methode ein MyRemote liefert.

public interface MyHome extends EJBHome {
public MyRemote create()
throws CreateException, RemoteException;
}

public interface MyRemote extends EJBObject {
public void foo() throws RemoteException;
}

@EJB injiziert eine EJB 2 Bean

Mit der Annotation @EJB injiziert der Container nicht nur EJB 3 Verweise, sondern auch EJB 2 Beans. Da EJB 2 Beans kein Business-Interface haben, injiziert der Container Exemplare, die das Home-Interface implementieren. Der Container soll die Objektvariable myHome belegen:

@EJB private MyHome myHome;

Der Zugriff auf die EJB 2-Bean läuft wie üblich über create():

MyRemote bean = myHome.create();
bean.foo();

@RemoteHome/@LocalHome

Mit den neuen Annotationen aus EJN 3.0 kann eine Enterprise-Bean mit einer EJB 2 Sicht veröffentlicht werden. Dazu ist ein Remote- und Component-Interface nötig.
– Etwa wie im Beispiel MyHome und MyRemote.
Im nächsten Schritt wird die Bean mit @RemoteHome/@LocalHome annotiert, etwa so:

@RemoteHome( MyHome.class )

Nur das Home-Interface, aber nicht das Componten-Interface, kommt in die Annotation.
Die EJB implementiert das Componten-Interface nicht, muss auch keine Annotationen vor diese Methoden setzen. Die create-Methoden werden mit @Init annotiert. Der Name ist egal.

Beispiel einer EJB 2 mit der EJB 3 API

@Stateful
@RemoteHome( MyHome.class )
public class MyEJB2Bean
{
@Init
public void create() { }

public void foo() { }
}

S_t_ring — Hoppala! Ein neues Framework? Habe ich was verpasst?

Beim Durchsehen der Webseiten meine Seminarkonkurrenz ist mir ein Link auf ein Buch aufgefallen, was mich zu http://entwickler-press.de brachte. Interessiert über ein neues Spring 2.0 Buch musste ich erfahren, dass es schon wieder ein neues Framework gibt: String. Interessant. Von der Webseite zitiert:

„Das Buch beschreibt das Spring-Framework im Enterprise-Einsatz. Es zeigt die Erweiterungen und neuen Möglichkeiten der aktuellen Version 2.0 und stellt weitere Frameworks vor, die sich im Projektalltag gut mit String kombinieren lassen. Die Autoren legen Ihren Schwerpunkt auf die Datenbankanbindung und Testing.“

Das oberste Stack-Element duplizieren

Die Klasse Stack besitzt zwar die Basisfunktionalität, die ein Stapel besitzen sollte, aber auch nicht mehr. Hin und wieder wünschen wir uns aber eine Funktion, die das oberste Stack-Element dupliziert, kurz dup().

Bei der Implementierung treten allerdings zwei Fragen auf, mit denen zwei völlig unterschiedliche Lösungsansätze verbunden sind. Da die Klasse Stack wie die anderen Datenstrukturen auf Objekte ausgelegt ist, müssen wir uns darüber Klarheit verschaffen, wie das obere Objekt dupliziert werden soll. Soll eine Kopie der Objekt-Referenz neu auf den Stapel gelegt werden oder etwa das gesamte Objekt geklont werden?

Die einfache Lösung

Die einfachste Lösung besteht darin, das oberste Objekt einfach mittels der schon vorhandenen Stack-Methoden push() und peek() draufzulegen. Nehmen wir an, wir haben eine Unterklasse DupStack, dann sieht die erste Variante zum Clonen so aus:

void dup() /* throws EmptyStackException */
{
push( peek() );
}

peek() gibt aber lediglich eine Referenz auf das Objekt zurück. Und das anschließende push() speichert diese Referenz dann auf dem Stapel. Nehmen wir an, wir haben zwei StringBuffer-Objekte auf dem Stapel. Wenn wir nun dup() aufrufen und den String ändern, der oben auf dem Stapel liegt, so ändern wir automatisch das zweite Element gleich mit. Dies ist aber nicht unbedingt beabsichtigt, und wir müssen uns Gedanken über eine alternative Lösung machen. Wir sehen, dass dup() in der Klasse Stack fehlt, weil seine Implementierung davon abhängt, ob eine Referenz- oder eine Wertsemantik für Kellerelemente gewünscht ist.

Die kompliziertere Lösung mit Klonen

Um das oberste Stack-Element zu kopieren, bietet sich die clone()-Methode von Object an. All die Objekte, die sich klonen lassen, und das sind längst nicht alle, implementieren das Interface Cloneable. Nun ließe sich einfach folgern: Wenn das zu duplizierende Objekt ein Exemplar von Cloneable ist, dann können wir einfach die clone()-Methoden aufrufen und das zurückgegebene Objekt mittels push() auf den Stapel bringen.

void dup2() throws CloneNotSupportedException
{
try
{
Object top = peek();

if ( top instanceof Cloneable )
push( top.clone() );

}
catch ( EmptyStackException e ) { }
}

Beziehungsweise

void dup3() throws CloneNotSupportedException /*, EmptyStackException */
{
push( peek().clone() );
}

Dies funktioniert für die meisten Objekte, allerdings nicht für Objekte der Klasse Object. Denn clone() der Klasse Object ist protected – wir dürfen also von außen nicht dran, nur eine Unterklasse und die Klasse selbst. Hier haben wir also zwei Probleme.

  • Leider lässt sich nur mit Aufwand überprüfen, ob das Objekt auf dem Stapel auch wirklich ein pures Object ist, denn alle Objekte sind instanceof Object. Glücklicherweise gibt es kaum eine Anwendung, wo reine Object-Elemente gesichert werden müssen.
  • Was machen wir mit Objekten, die nicht klonbar sind? Leider gibt es für diese Frage keine direkte Antwort. Eine universelle Stack-Klasse mit einer uneingeschränkten dup()-Methode gibt es nicht. Wir müssen als Stack-Benutzer festlegen, dass das oberste Element Clonable ist, um zumindest eine eigene Implementierung nutzen zu können. Oder wir bleiben dabei, bei nicht klonbaren Objekten doch nur die Referenz zu duplizieren. Das wäre zumindest für eineindeutige Objekte mit Wertsemantik die ideale Lösung.

@ManagedResource in Spring is really cool

First, write a class and annotate it:

package com.javatutor.spring.jmx;

import java.util.HashMap;

import java.util.Map;

import org.springframework.jmx.export.annotation.ManagedAttribute;

import org.springframework.jmx.export.annotation.ManagedOperation;

import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource( objectName = "com.javatutor.spring.jmx:name=StringMap" )

public class StringMapImpl

{

private Map<String, String> map = new HashMap<String, String>();

@ManagedAttribute

public int getSize()

{

return map.size();

}

@ManagedOperation

public String get( String key )

{

return map.get( key );

}

@ManagedOperation

public String put( String key, String value )

{

return map.put( key, value );

}

public void remove( String key )

{

map.remove( key );

}

}

Next, write a spring config file:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="StringMap" class="com.javatutor.spring.jmx.StringMapImpl" />

<bean id="MBeanExporter"

class="org.springframework.jmx.export.MBeanExporter">

<property name="assembler" ref="MBeanInfoAssembler" />

<property name="autodetect" value="true" />

<property name="namingStrategy" ref="NamingStrategy" />

</bean>

<bean id="AnnotationJmxAttributeSource"

class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"

/>

<bean id="NamingStrategy"

class="org.springframework.jmx.export.naming.MetadataNamingStrategy">

<property name="attributeSource" ref="AnnotationJmxAttributeSource" />

</bean>

<bean id="MBeanInfoAssembler"

class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">

<property name="attributeSource" ref="AnnotationJmxAttributeSource" />

</bean>

</beans>

Now the client:

package com.javatutor;

import javax.swing.JOptionPane;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.javatutor.spring.jmx.StringMapImpl;

public class SpringWithJmxExample

{

public static void main( String[] args )

{

ApplicationContext beFac = new ClassPathXmlApplicationContext( "springJmx.xml"

);

StringMapImpl cacheBean = (StringMapImpl) beFac.getBean( "StringMap" );

cacheBean.put( "Key", "Value" );

System.out.println( cacheBean.get("Key") );

JOptionPane.showMessageDialog( null, "End" );

}

}

Start the program. Do not forgett to start with -Dcom.sun.management.jmxremote.
Start JConsole and enjoy.

 

Inselraus: Die Datenbank Derby

Derby (http://db.apache.org/derby/) ist ein pures Java-RDBMS unter der freien Apache-Lizenz. Die Datenbank geht auf Cloudscape zurück, was IBM Mitte 2004 auf der LinuxWorld in San Francisco der Apache Software Foundation übergeben hat. (1999 übernahm Informix Software Inc.XE „Informix Software Inc.“ die Datenbank Cloudscape von Cloudscape Inc., doch IBM übernahm die Datenbanktechnologien von Informix im Jahre 2001.) Ein großer Vorteil von Derby sind neben dem geringen Speicherbedarf die Transaktionsunterstützung, Trigger und SQL-Kompatibilität mit DB2.

Download und Vorbereitung

Auf der Download-Seite befinden sich drei Archive, wovon wir eine Datei mit der Endung -bin beziehen (etwa db-derby-10.1.1.0-bin.zip) und auspacken. Wir nehmen zu Testzwecken c:\Programme\derby an, sodass in C:\Programme\derby\frameworks\NetworkServer\bin die Skripte zum Starten und Stoppen des Servers zu finden sein sollten.

Derby lässt sich in zwei Modi fahren: als eingebettetes Datenbanksystem und als Netzwerkserver. Im Fall eines eingebauten Datenbanksystems ist lediglich die Klasse für den eingebetteten Treiber zu laden und die Datenbank zu bestimmen, schon geht’s los:

Class.forName( „org.apache.derby.jdbc.EmbeddedDriver“ );
Connection con = DriverManager.getConnection( „jdbc:derby:OpenGeoDB;create=true“ );

Wir wollen Derby als Netzwerkserver starten, um mit unterschiedlichen Clients – etwa einem Eclipse-Plugin – auf die Datenbank zugreifen zu können. In diesem Modus horcht Derby über TCP/IP an einem Port (standardmäßig 1527) auf eingehende Verbindungen.

Vor dem Start muss die Umgebungsvariable JAVA_HOME gesetzt sein, und DERBY_INSTALL auf das Installationsverzeichnis zeigen. Zunächst gehen wir in startNetworkServer.bat und setzen dort die nötigen Umgebungsvariablen:

Listing 20.1 startNetworkServer.bat


@REM — This file for use on Windows systems
@REM ———————————————————

@echo off
set JAVA_HOME=C:\Programme\Java\jdk1.5.0
set DERBY_INSTALL=C:\Programme\derby\

Ebenso setzen wir in startNetworkServer.bat die Umgebungsvariablen passend, da wir den Server auch wieder stoppen wollen.

Server-Start und weitere Tools

Nun lässt sich – etwa mit Doppelklick – das Skript startNetworkServer.bat starten, und die Datenbank quittiert freundlich:

Der Server ist bereit, am Port 1527 Verbindungen zu akzeptieren.

Das Programm sysinfo zeigt Versionsnummer und Klassenpfad an. Mit dem interaktiven Werkzeug ij lassen sich SQL-Anweisungen abschicken und so die ersten Versuche mit der Datenbank aufnehmen. dblook extrahiert das Datenbankschema.

JDBC-Treiber

Für einen JDBC-Zugriff ist ein JDBC-Treiber nötig, der bei Derby für eine Client-/Server-Kommunikation nicht dabei ist, denn Derby nutzt die Distributed Relational Database ArchitectureXE „Distributed Relational Database Architecture“ (DRDAXE „DRDA“), die auch DB2 verwendet. DRDA ist eine von der Open GroupXE „Open Group“ definierte Möglichkeit, von einem Client eine entfernte relationale Datenbank anzusteuern. Auf http://www-128.ibm.com/developerworks/db2/downloads/jcc/ ist die Datei db2jcc_for_derby.zip aufgeführt, die – nach einer Registrierung – auf der nächsten Seite unter »Released product: IBM Cloudscape (IBM DB2 JDBC Universal Driver, for Cloudscape/Derby)« heruntergeladen werden kann. Die beiden Jar-Dateien aus dem Archiv, db2jcc.jar und db2jcc_license_c.jar, sind in den Klassenpfad aufzunehmen. Für den Client-/Server- Modus von Derby benötigen wir keine weiteren Jar-Dateien, insbesondere keine aus dem lib-Verzeichnis von Derby.

mime4j für MS-Web-Archive MHT

Während Word und Excel beim Export einzelnde (mehr oder wenig schöne) XML-Dateien erzeugen (können), macht PPT das nicht: Es generiert entweder eine Sammlung von XML/HTML/GIF/.. Dateien, oder packt alle Dateien in ein MHT-Datei. Möchte man diese Datei auseinandernehmen, kann man gut http://mime4j.sourceforge.net/apidocs/ nutzen, denn MS bündelt die Dokumente wie eine MIME-EMail. Zunächst benötigt man einen Handler:

package TEST;

import java.io.IOException;
import java.io.InputStream;

import org.mime4j.BodyDescriptor;
import org.mime4j.SimpleContentHandler;
import org.mime4j.message.Header;

public class MyHandler extends SimpleContentHandler
{
@Override
public void headers( Header header )
{
System.out.println( header );
}

@Override
public void bodyDecoded( BodyDescriptor bd, InputStream is ) throws IOException
{
System.out.println( bd );
}
}

Der macht jetzt nicht viel, man sieht aber das Prinzip.

Der Quellcode des Parsers ist auch kurz:

package TEST;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

import org.mime4j.ContentHandler;
import org.mime4j.MimeStreamParser;

public class Mime4J
{
public static void main( String[] args ) throws IOException
{
ContentHandler handler = new MyHandler();
MimeStreamParser parser = new MimeStreamParser();
parser.setContentHandler( handler );
parser.parse( new BufferedInputStream( new FileInputStream( „c:/a.mht“ ) ) );
}
}

Auf der Konsole folgen dann Ausgaben wie

MIME-Version: 1.0
Content-Type: multipart/related; boundary=“—-=_NextPart_01C617A0.DCEFFF40″

Content-Location: file:///C:/EC2C4D01/a.htm
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=“us-ascii“

text/html
Content-Location: file:///C:/EC2C4D01/a-Dateien/master29.htm
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=“us-ascii“

text/html
Content-Location: file:///C:/EC2C4D01/a-Dateien/master29.xml
Content-Transfer-Encoding: quoted-printable
Content-Type: text/xml; charset=“utf-8″

Ein Java-Programm (nicht quelloffen), was das schon alles macht — und für Mac/Linux ganz spannend ist, ist unmhtml von http://www.joecheng.com/code/.

Adjust font size in Swing applications global

Use this snippet to set the size of all fonts of the current Swing look and feel:

public static void setFontSizeGlobal( int size )
{
for ( Enumeration e = UIManager.getDefaults().keys(); e.hasMoreElements(); )
{
Object key = e.nextElement();
Object value = UIManager.get( key );

if ( value instanceof Font )
{
Font f = (Font) value;

UIManager.put( key, new FontUIResource( f.getName(), f.getStyle(), size ) );
}
}
}

Can we compact the code? Not with the extended for! It is true that UIDefaults is a subtype of Hashtable but the methods entrySet() or keySet() returns an empty collection.

JAXB 2.0 und XJC in einem Ant-Skript nutzen

Für Konfigurations-Dateien wollte ich JAXB 2.0 nutzen und es war wirklich einfach. Zuerst habe ich mit XMLBeans begonnen, was ebenfalls sehr einfach zu nutzen ist, doch die Annotationen gefielen mir bei JAXB 2.0 besser. Der Client, der auf die generierten Beans zugreift, muss nur geringfügig verändert werden, denn XMLBeans repräsentiert Unterelemente in einem Array, währenddessen JAXB eine Collection verwendet.

Nach dem Download von JAXB müssen sich für den Client im Pfad befinden: jaxb-api.jar, jaxb-impl.jar, activation.jar, jsr173_1.0.jar. Für den Ant-Task und die Generation von Klassen aus eine Schema-Datei ist jaxb-xjc.jar verantwortlich.

Für den Ant-Task meldet man (für Eclipse) am einfachsten alle genannten Java-Archive unter den Ant > Runtime > Classpath > Global Entries an. Die Ant-Datei nutzt für die Generation der Bean dann folgendes:

<project default="all" basedir=".">

<target name="all">

<xjc schema="schemas/ppt_join.xsd" target="../src/core"
package="com.javatutor.cw.ppt.jaxb.pptjoin" />

 </target>

</project>

Die Schema-Datei sieht für meine Konfigurations-Dateien so aus:

<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:po="http://javatutor.com/pptjoin"
targetNamespace="http://javatutor.com/pptjoin"
elementFormDefault="qualified">
<xs:element name="ppt-join">
<xs:complexType>
<xs:sequence>
<xs:element name="options" type="po:options" minOccurs="0" maxOccurs="1" />
<xs:element name="fileset" type="po:fileset" />
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:complexType name="fileset">
<xs:sequence>
<xs:element name="file" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="options">
<xs:sequence>
<xs:element name="removeHidden" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="destination-file" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:sequence>
</xs:complexType>

</xs:schema>

Nutze ich nun JAXB 2.0? Ehrlich gesagt nicht. Denn für kleine Konfigurationsdateien finde ich persönlich JAXB etwas zu aufwändig. Nun bin ich wieder am Anfang, bei XStream.

Spring, Hibernate und HSQLDB

Nach dem das erste Beispiel das Zusammenspiel von Spring und HSQLDB zeigte, soll das zweite Beispiel statt dem JdbcTemplate das HibernateTemplate nutzen, damit Hibernate die OR-Abbildung übernimmt. Zur Vorbereitung (und Erweiterung) des ersten Beispiels wird zunächst log4j.properties um eine Zeile erweitert:

log4j.rootCategory=ERROR, A1
log4j.logger.net.sf.ehcache=ERROR, A1

log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%m%n

Für Hibernate sind ebenso weitere Jar-Dateien nötig, so dass sich insgesamt
im Pfad befinden müssen:

  • hsqldb.jar
  • log4j-*.jar
  • spring.jar
  • common-logging.jar
  • hibernate3.jar
  • dom4j-*.jar
  • jta.jar
  • common-collections-*.jar
  • ehcache-*.jar
  • cglib-*.jar
  • asm.jar
  • antlr-*.jar

Als erstes zeigt die DAO-Implementierung die Verwendung von HibernateTemplate.

package com.javatutor.dao.hibernate;

import java.util.Collection;

import org.springframework.orm.hibernate3.HibernateTemplate;

import com.javatutor.dao.CustomerDao;
import com.javatutor.domain.Customer;

public class CustomerDaoHibernateImpl extends HibernateTemplate implements CustomerDao
{
@SuppressWarnings("unchecked")
public Collection<Customer> getCustomers()
{
return loadAll( Customer.class );
}

public Customer findCustomerById( int id )
{
return (Customer) load( Customer.class, id );
}

public void save( Customer customer )
{
saveOrUpdate( customer );
}
}

Zum Mappen der POJOs auf die Datenbank ist eine Mapping-Datei nötig, es sei denn, man arbeitet mit Java 5 Annotationen. In den Klassenpfad kommt die Datei Customer.hbm.xml.

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.javatutor.domain">

<class name="Customer" table="Customers" lazy="false">

<id name="id" column="Id">
<generator class="native" />
</id>

<property name="name" column="Name" />

</class>

</hibernate-mapping>

In der Spring XML-Datei müssen wir den CustomerDaoHibernateImpl anmelden und
mit einer SessionFactory versorgen.

<bean id="CustomerDaoHibernate" class="com.javatutor.dao.hibernate.CustomerDaoHibernateImpl">
<property name="sessionFactory">
<ref local="SessionFactory" />
</property>
</bean>

Die SessionFactory ist für Hibernate nötig und enthält den Hinweis auf die OR-Mappings und die DataSource. Die DataSource haben wir schon verwendet, doch damit alles persistent bleibt, soll HSQLDB diesmal ins Dateisystem schreiben.

<bean id="DataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="url">
<value>jdbc:hsqldb:file:/c/:Customers;shutdown=true</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value></value>
</property>
</bean>

Es ist sehr wichtig, den Parameter ;shutdown=true an die Verbindungs-URL zu setzen. Die LocalSessionFactoryBean ist der Produzent für unsere SessionFactory:

<bean id="SessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref local="DataSource" />
</property>
<property name="mappingResources">
<list>
<value>Customer.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.HSQLDialect
</prop>
<!-- <prop key="hibernate.show_sql">true</prop>-->
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
</bean>

Mit dem hibernate.hbm2ddl.auto ist dann auch in der Applikation das CREATE TABLE unnötig. Und das war’s dann auch schon.

JSmooth mit Ant Build Skript

JSmooth nutze ich schon eine ganze Zeit, um unter Windows ausführbare EXE zu
erzeugen. Statt der Gui lässt sich JSmooth aber auch über Ant steuern.

Zunächst muss ein neuer Ant-Task definiert werden:

<property name="jsmooth.dir" value="C:/Programme/JSmooth 0.9.7">
<taskdef name="jsmoothgen" classname="net.charabia.jsmoothgen.ant.JSmoothGen"
         classpath="${jsmooth.dir}/lib/jsmoothgen-ant.jar">

Jetzt kann man den neuen Task jsmoothgen nutzen:

<target name="build-exe-createmetadata">
  <jsmoothgen project="createmetadata.jsmooth"
              skeletonroot="${jsmooth.dir}/skeletons" />
</target>

Mit Spring und dem JdbcTemplate auf eine Hibernate-Datenbank

Das folgende Beispiel soll zeigen, wie man mit Spring arbeitet und Daten in einer relationalen Datenbank persistent macht. Um das Bespiel zum Laufen zu bringen müssen im Klassenpfad sein: hsqldb.jar, spring.jar, log4j-1.2.9.jar und common-logging.jar. Für die Log-Meldungen setzen wir in den Klassenpfad die Datei log4j.properties:

log4j.rootCategory=WARN, A1

log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%m%n

Beginnen wir mit unserem Geschäftsobjekt, einem Kunden:

package com.javatutor.domain;

public class Customer
{
private int id;

private String name;

public int getId()
{
return id;
}

public void setId( int id )
{
this.id = id;
}

public String getName()
{
return name;
}

public void setName( String name )
{
this.name = name;
}

@Override
public String toString()
{
return String.format( "Customer[id=%d, name=%s]", id, name );
}
}

Für diesen Kunden definieren wir eine DAO-Schnittstelle, die uns später Exemplare der Geschäftsobjekte gibt und die Speicherung ermöglichen.

package com.javatutor.dao;

import java.util.Collection;

import com.javatutor.domain.Customer;

public interface CustomerDao
{
Collection<Customer> getCustomers();

Customer findCustomerById( int id );

void save( Customer customer );
}

Zum Testen der Applikation beginnen wir mit einer einfach DAO-Implementierung, die Kunden in einer HashMap speichert.


package com.javatutor.dao.map;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.javatutor.dao.CustomerDao;
import com.javatutor.domain.Customer;

public class CustomerDaoMapImpl implements CustomerDao
{
private Map<Integer, Customer> map = new HashMap<Integer, Customer>();

public Collection<Customer> getCustomers()
{
return map.values();
}

public Customer findCustomerById( int id )
{
return map.get( id );
}

public void save( Customer customer )
{
map.put( customer.getId(), customer );
}
}

Eine Applikation wird über die Spring-Konfigurationsdatei später mit einem konkreten CustomerDao gespritzt. Die Methode haveFun() legt einige Business-Objekte an und speichert sie über das DAO.


package com.javatutor;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import com.javatutor.dao.CustomerDao;
import com.javatutor.domain.Customer;

public class Application
{
private CustomerDao customerDao;

public void setCustomerDao( CustomerDao customerDao )
{
this.customerDao = customerDao;
}

private void haveFun()
{
System.out.println( customerDao.getCustomers() );

Customer c1 = new Customer();
c1.setId( 0 );
c1.setName( "Christian Ullenboom" );
customerDao.save( c1 );

System.out.println( customerDao.findCustomerById( 0 ) );

System.out.println( customerDao.getCustomers() );

Customer c2 = new Customer();
c2.setId( 1 );
c2.setName( "Tantiana Roujitcher" );
customerDao.save( c2 );

System.out.println( customerDao.getCustomers() );
}

//

public static void main( String[] args )
{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "spring.xml" );
Application bean = (Application) context.getBean( "Application" );
bean.haveFun();
}
}

Jetzt fehlt nur noch die XML-Datei für Spring:


<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
<bean id="Application" class="com.javatutor.Application">
<property name="customerDao">
<ref local="CustomerDao" />
</property>
</bean>
<bean id="CustomerDao" class="com.javatutor.dao.map.CustomerDaoMapImpl" />
</beans>

Startet man das Programm, ist die Ausgabe


[]
Customer[id=0, name=Christian Ullenboom]
[Customer[id=0, name=Christian Ullenboom]]
[Customer[id=1, name=Tantiana Roujitcher], Customer[id=0, name=Christian Ullenboom]]

Prima. Es klappt. Jetzt kommt eine DAO-Implementierung für JDBC. Zunächst die Klasse.


package com.javatutor.dao.jdbc;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;

import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.javatutor.dao.CustomerDao;
import com.javatutor.domain.Customer;

public class CustomerDaoJdbcImpl extends JdbcTemplate implements CustomerDao
{
@SuppressWarnings("unchecked")
public Collection<Customer> getCustomers()
{
return query( "SELECT Id, Name FROM Customers", new CustomerRowMapper() );
}

public Customer findCustomerById( int id )
{
String sql = "SELECT Id, Name FROM Customers WHERE Id = ?";

try
{
return (Customer) queryForObject( sql,
new Object[] { id },
new CustomerRowMapper());
}
catch ( IncorrectResultSizeDataAccessException e ) { }

return null;
}

public void save( Customer customer )
{
if ( findCustomerById( customer.getId() ) == null )
{
Object[] args = { customer.getId(), customer.getName() };
update( "INSERT INTO Customers (Id, Name) VALUES (?,?)", args );
}
else
{
Object[] args = { customer.getName(), customer.getId() };
update( "UPDATE Customers SET Name = ? WHERE Id = ?", args );
}
}
}

class CustomerRowMapper implements RowMapper
{
public Object mapRow( ResultSet rs, int rowNum ) throws SQLException
{
Customer c = new Customer();
c.setId( rs.getInt( "Id" ) );
c.setName( rs.getString( "Name" ) );
return c;
}
}

Und in der XML-Datei ergänzen wir für den JDBC-DAO:

<bean id="CustomerDaoJdbc" class="com.javatutor.dao.jdbc.CustomerDaoJdbcImpl">
<property name="dataSource"><ref local="DataSource" /></property>
</bean>

<bean id="DataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="url">
<value>jdbc:hsqldb:mem:customers</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value></value>
</property>
</bean>

Damit unsere Applikation mit dem neuen JDBC-DAO gespritzt wird setzt man.

 <bean id="Application" class="com.javatutor.Application">
<property name="customerDao">
<ref local="CustomerDaoJdbc" />
</property>
<property name="dataSource">
<ref local="DataSource" />
</property>
</bean>

Gleichzeitig geben wir der Applikation eine DataSource, damit sie die Tabelle für die Datenbank anlegen kann.


public void setDataSource( DataSource dataSource )
{
new JdbcTemplate( dataSource ).execute( "CREATE TABLE Customers( Id INTEGER, Name VARCHAR(128) ) " );
}

Das gestartet Programm gibt nun die gleiche Ausgabe.

Buchkritik "UML Distilled"

Ein UML-Buch mit nur 180 Seiten ist in der Tat etwas besonderes, verglichen mit den 1.000 Seiten der UML 2.0 Dokumentation inklusive der vielen Zusatzdokumente wie MDA und so weiter. Dabei ist Fowlers Buch nicht oberflächlich, sondern er destilliert den Kern der UML, die Diagramme, und hält sich nicht mit Meta-Modellen auf. Vor der eigentlichen Notation fasst er auf etwa 30 Seiten Entwicklungsmethoden (wie RUP), Refactoring, Pattern zusammen. Dann folgen die bekannten UML-Diagramme, in der 3. Auflage auch die seit UML 2.0 neuen Timing-Diagramme. In der Schreibweise wählt Fowler einen persönlichen Stil, den ich besonders sympathisch finde. Er spricht häufiger in der Ich-Form und am offensten war das Satz zum Thema Umgang mit den politischen Schwierigkeiten in der Softwareentwicklung: „I can’t offer you any serious advice on this because I’m not a skilled corporate politician. I strongly suggest that you find someone who is.“ Das ist ein ganzes Kapitel!
In der Summe dürfte das Buch für diejenigen interessant sein, die einen schnellen, schmerzlosen Einstieg in UML suchen.