Mit Spring und dem JdbcTemplate auf eine Hibernate-Datenbank
6 Kommentar(e). Veröffentlicht von Christian Ullenboom am Samstag, Dezember 17, 2005.
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:
Beginnen wir mit unserem Geschäftsobjekt, einem Kunden:
Startet man das Programm, ist die Ausgabe
Prima. Es klappt. Jetzt kommt eine DAO-Implementierung für JDBC. Zunächst die Klasse.
Gleichzeitig geben wir der Applikation eine DataSource, damit sie die Tabelle für die Datenbank anlegen kann.
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;Für diesen Kunden definieren wir eine DAO-Schnittstelle, die uns später Exemplare der Geschäftsobjekte gibt und die Speicherung ermöglichen.
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 );
}
}
package com.javatutor.dao;Zum Testen der Applikation beginnen wir mit einer einfach DAO-Implementierung, die Kunden in einer HashMap speichert.
import java.util.Collection;
import com.javatutor.domain.Customer;
public interface CustomerDao
{
Collection<Customer> getCustomers();
Customer findCustomerById( int id );
void save( Customer 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.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 );
}
}
Jetzt fehlt nur noch die XML-Datei für Spring:
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();
}
}
<?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.
Und in der XML-Datei ergänzen wir für den JDBC-DAO:
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;
}
}
<bean id="CustomerDaoJdbc" class="com.javatutor.dao.jdbc.CustomerDaoJdbcImpl">Damit unsere Applikation mit dem neuen JDBC-DAO gespritzt wird setzt man.
<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>
<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.
Das gestartet Programm gibt nun die gleiche Ausgabe.
public void setDataSource( DataSource dataSource )
{
new JdbcTemplate( dataSource ).execute( "CREATE TABLE Customers( Id INTEGER, Name VARCHAR(128) ) " );
}
Labels: Spring

Danke für das Tutorial.
Muss es für das neue JDBC-DAO nicht genau anderesrum heissen?
property name="customerDao"
ref local="CustomerDaoJdbc"
anstelle von
property name="customerDaoJdbc"
ref local="CustomerDao"
Die property customerDaoJdbc gibt es in der Application Bean ja nicht.
Ja. Ist korrigiert.
Ist das jetzt eine 3-Schichten Architektur? Wenn Ja wo sind die einzelnen Schichten gegliedert?
Nö. Man könnte aber Application als Presentation-Tier sehen. Es fehlt dann eine Service-Schicht (Business-Tier), die das DAO injiziert bekommt. Der DAO selbst wäre auf der Integration-Tier.
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.