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.
Ich hätte eine Frage zu dem "new CustomerRowMapper()" welches in der Methode findCustomerById aufgerufen wird.
1. Was bringt mir das ganze, da die Klasse CustomerRowMapper nur einen leeren Konstruktor und die Methode mapRow hat, die jedoch nicht angetastet wird?
2. Aus der ersten Frage resultiert dann
return (Customer) queryForObject( sql,
new Object[] { id }, new CustomerRowMapper());
Im String sql ist bereits integriert, welche ich dann wiederum an die Methode queryForObject übergebe und danach nach dem Kommata auch wiederum?
3. Eine letzte Java-Frage noch hier
Object[] args = { customer.getId(), customer.getName() };
update( "INSERT INTO Customers (Id, Name) VALUES (?,?)", args );
Kann ich problemlos ein Object-Array wie String erzeugen, woe ich dann wie im oberen Fall ID und Name reinspeichere, ohne, dass ich die Klasse Object erzeugen muss. D.h., wenn ich das Objekt wie hier als args übergebe:
update( "INSERT INTO Customers (Id, Name) VALUES (?,?)", args );
Dann kann das Fragezeichen problemlos die zwei Werte im Object (ohne eine Klasse und Auslesemethoden angelegt zu haben) dann auslesen bzw. für das Fragezeichen übernehmen?
Ich hoffe, die Fragen sind nicht all zu doof, ich bin erst auf Seite 300 im JavaInsel 🙂
Grüße
EinStudent
Oho. Den Kommentar habe ich irgendwie übersehen. Heutzutage würde man das auch ab Spring 2 anders machen, da die Unterstützung für Generics jetzt besser ist. Statt JdbcTemplate käme dann SimpleJdbcTemplate zu Zug. Aber für die Frage ist das egal:
1. Schaut man sich
public Collection getCustomers()
{
return query( "SELECT Id, Name FROM Customers", new CustomerRowMapper() );
}
an, kann man sehen, dass ein Objekt vom Typ CustomerRowMapper an die query()-Methode übergeben wird. Daher muss schon einmal ein Standard-Konstruktor da sein. Es ist query(), die dann mapRow() aufruft. Dass es mapRow() gibt, weil query() aufgrund des Typs von CustomerRowMapper, das ein RowMapper ist, uns somit mapRow() vorschreibt.
2. queryForObject bekommt nur den SQL-String im ersten Parameter. In den Argumeten new Object[] { id } und new CustomerRowMapper() steckt der SQL-String nicht drin. Das zweite Argument ist der Platzhalter für das Prepared-Statement und das dritte Argument der RowMapper, der die Datenbankzeile auf ein Objekt überträgt.
3. Ja. Das macht der JDBC-Treiber und die Datenbank. Die Datenbank bekommt den String mit dem ? und die Argumente und führt damit die Datenbankoperationen durch.
vielen vielen dank, das ist sehr lieb.
nur noch eine kleine Ergänzung für Punkt 3:
"
Object[] args = { customer.getName(), customer.getId() };
update( "UPDATE Customers SET Name = ? WHERE Id = ?", args
"
Hier erhält er ja kein String als Argument, sondern ein Objekt. Wobei, String/Objekt unterscheidet sich ja nicht wirklich.
Ich hatte nur etwas verzwickt nachgedacht und mir überlegt, wie man ohne bsp. Get-Methode von dem Objekt, wenn man Sachen reinspeichert, die ohne Probleme auslesen kann, bzw. wenn man es als argument jemand anders übergibt, er dann diese Daten ausliest 🙂
Oki, dann hätten sich all meine Fragen geklärt.