Spring Framework
soll ist eine Art glue-Framework für alles mögliche andere (Frameworks, Komponenten auf AppServern wie EJB, v.a. mehr), egal. Durch einen sogenannten Bean Container, der nach dem Inversion of Control (oder Dependency Injection) Prinzip funktioniert, kann die Initialisierung von Java Klassen in eine xml-Konfig-Datei ausgelagert werden.
Wer jemals code von einem Java-Programmierer mit einem Mindestmaß an Selbstrespekt gesehen hat, wird wissen, dass dort eine Menge Verbindungscode (Singleton-Factories, etc.) vorhanden ist, um die Objekte zu initialisieren (und gegebenfalls die einzelnen Klassen schnell austauschen zu können).
Mit diesem Inversion of Control tut man das alles einfach in einem xml-File beschreiben.
Hier ist mein Versuch (Auszug):
<beans>
<!-- DB2 Datenquelle -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>com.ibm.db2.jcc.DB2Driver</value>
</property>
<property name="url">
<value>jdbc:db2:fussi2</value>
</property>
<property name="username">
<value>db2admin</value>
</property>
<property name="password">
<value>kennwort</value>
</property>
</bean>
<!-- ibatis sqlmap config file -->
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation">
<value>SqlMapConfig.xml</value>
</property>
<property name="dataSource"><ref bean="dataSource"/></property>
</bean>
<!-- transaction stuff -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource"><ref bean="dataSource"/></property>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
</bean>
<!-- DaoFactories Ibatis -->
<bean id="userDAO" autowire="byName" class="de.aja.fussi.dao.ibatis.UserDaoIbatis">
<!-- injected property: sqlMapClient -->
</bean>
<bean id="stockDescrDAO" autowire="byName" class="de.aja.fussi.dao.ibatis.StockDescrDaoIbatis">
<!-- injected property: sqlMapClient -->
</bean>
<bean id="stockUserDAO" autowire="byName" class="de.aja.fussi.dao.ibatis.StockUserDaoIbatis">
<!-- injected property: sqlMapClient -->
</bean>
<!-- Business Facades or singletons-->
<bean id="userRegistrationPolicy" class="de.aja.fussi.UserRegistrationPolicySimple">
<!-- wie war das nochmal mit value. Setze value HIER -->
</bean>
<bean id="userManager" class="de.aja.fussi.UserManagerImpl">
<property name="txTemplate"><ref bean="transactionTemplate"/></property>
<property name="userDao"><ref bean="userDAO"/></property>
<property name="stockDescrDao"><ref bean="stockDescrDAO"/></property>
<property name="stockUserDao"><ref bean="stockUserDAO"/></property>
<property name="userRegistrationPolicy"><ref bean="userRegistrationPolicy"/></property>
</bean>
</beans>
Ich brauche dann z.B. in der Klasse UserManagerImpl nur noch ein paar setter zu schreiben. Die restlichen Informationen holt er sich aus diesem xml-File.
Also z.B. Es gibt in UserManagerImpl ein property mit dem Namen userDao.
Dieses wird durch das Bean userDAO bestimmt (ref bean=)
Hier steht, dass es durch eine Instanz der Klasse UserDaoIbatis gesetzt wird.
Um diese Beziehung in dem Objekt der Klasse UserManagerImpl zu initialisieren benötigte ich dort nur noch den folgenden äusserst komplizierten code:
private userDao;
private setUserDao(userDao dao) {
this.userDao = dao;
}
Eine Instanz des Typs UserManager (UserManagerImpl als Klasse erhalte ich dann einfach so:
UserManager uManager = (UserManager) factory.getBean("userManager");
Spring enthält nicht nur diesen IOC Container sondern außerdem noch eine Menge an Zusatzklassen, um typische Enterprise-Features wie Transaktionen, Persistenz, Security, Scheduling, Webframeworks und vieles, vieles sich einfach aus dem Framework zu holen.
Das ganze führt zu wirklich wenig code und darüber hinaus zeigt das xmlConfig File noch die Struktur des Programms quasi als doku an (wenn man da ein bischen dran gewöhnt, klappt das sehr gut).
Man programmiert automatisch sehr viel gegen Interfaces. Dies führt dazu, dass die implementierenden Klassen in den Layern sehr leicht durch andere ersetzt werden können. Wenn ich keinen Bock mehr habe auf IBatis für den Zugriff auf die Datenbank, dann konfiguriere ich eben Hibernate, jakarta.ObjectRelationalBridge oder was sonst so angeboten wird und schreib halt die Klassen für Hibernate, wobei ich da sowieso auf Spring Hibernate Integrationsklassen zugreifen kann.
Hier die Schritte im Code, um ein
transaktionales Insert gegen 2 Tabellen in DB2 mit zugegebenermassen nicht sehr smarten SQL-Code durchzuführen: (dies kann aus Tomcat, Websphere, einer Client Anwendung oder was auch immer aufgerufen werden)
// client code
UserManager uManager = (UserManager) factory.getBean("userManager"); // hole Bean aus IOC Container
User anUser = new User();
anUser.setNameUser("Waldi Wuff");
anUser.setPwd("kennwort");
anUser.setEmail("waldi.wuff@gmail.com");
try {
uManager.requestRegistration(anUser);
} catch (InvalidDataException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
In UserManager.requestRegistration steht:
public User requestRegistration(User user) throws InvalidDataException {
userRegistrationPolicy.validate(user);
userRegistrationPolicy.processRegistrationRequest(user);
saveUser(user);
return user;
}
Die userRegistrationPolicy wurde über das xml-Config Dokument (s.o) automatisch in das Objekt gehauen.
Die Methoden validate() und processRegistrationRequest von userRegistrationPolicy sind erstmal nicht so wichtig. Das ist einfach nur so ein bischen Template GoF Pattern (oder ist es Strategy. Egal). Jedenfalls sind das auch jeweils 2 bis 3 Zeilen code.
In saveUser wird nun die Transaktion gegen die Datenbank durchgeführt. Ich kann da verschiedenes setzen wie das Isolationslevel der Transaktion oder ob das nur eine Read-Only Transaktion ist. Ich hab mich erstmal mit dem Default begnügt (Default Einstellungen der Datenbank).
public void saveUser(final User user) {
// starte neue Transaktion --> wurde auch im xml-Config File reingeschossen.
txTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
user.setId(userDao.insertUser(user)); // mache Datenbank insert. Über iBatis
// wenn Registrierungsstatus genehmigt, dann iteriere alle Aktien auf dem Markt und tue die auch direkt in die Datenbank
if (user.getRegStatus() == UserRegistrationPolicy.REG_STATUS_APPLIED) {
Iterator itStockDescrAll = stockDescrDao.getAllStockDescr()
.iterator();
// insert all StockUser
while (itStockDescrAll.hasNext()) {
Integer idStockDescr = ((StockDescr) itStockDescrAll
.next()).getId();
StockUser stockUser = new StockUser(user.getId()
.longValue(), idStockDescr.intValue(), 300);
// schreibe StockUser in die Datenbank
Long idStockUser = stockUserDao
.insertStockUser(stockUser);
stockUser.setId(idStockUser);
user.addStockUser(stockUser);
}
}
}
});
}
Bezüglich rollback und commit kümmert sich das Framework. Ach ja. Wo ist das SQL?
Das läuft wie gesagt über IBatis.
Das ist in IBatis xml-Konfigurationsdateien (ausserhalb von kompilierten code) definiert. Z.B. hier:
<insert id="insertUser" parameterClass="User">
INSERT INTO
Person (nameUser, pwd, email, regStatus) Values
(#nameUser#, #pwd#,#email#, #regStatus#)
<selectKey resultClass="long" keyProperty="id" >
Values(identity_val_local())
</selectKey>
</insert>
Die aufgerufene Methode von UserDAOIBatis sieht einfach so aus:
public Long insertUser(User user) {
return (Long) getSqlMapClientTemplate().insert("insertUser", user);
}
Zu Spring gibt es mittlerweile 3 gute Bücher (und das von Rod Johnson + Mannschaft kommt dann im Sommer. Die Fehlermeldungen treiben mir fast Tränen in die Augen. Ich sehe in der ersten Zeile was los ist. Jeder der um 2:00 Uhr nachts von JBoss oder auch IBatis Stacktraces angegriffen wurde, weiss das zu würdigen wissen.