Spring und Hibernate steht auch noch auf der Liste. Momentan bin ich kurz davor, über eine ZK-GUI einen Datensatz mit Hibernate (auf MySQL) zu erzeugen. Wie immer gab es Myriaden fehlende Pfade zu irgendwelchen XML-Dateien etc aber ich nähere mich.
Einen CurrentSessionContext habe ich auch nicht, da muss ich momentan erstmal eine Session mit openSession aufmachen, alles mui komplex. Aber nun habe ich es geschafft, ein paar Datensätze anzulegen:
<window title="Registration" border="normal">
<zscript>{
import zutritt.model.Person;
import util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.*;
void submit() {
Person person = new Person();
person.setSmtpMail(email.value);
person.setFirstName(forename.value);
person.setLastName(surename.value);
person.setEmpNumber("12345678");
person.setCostCenter(cc.value);
try {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.saveOrUpdate(person);
tx.commit();
session.close();
HibernateUtil.getSessionFactory().close();
}catch (Exception e) {
e.printStackTrace();
alert(e.getMessage());
}
}
}
</zscript>
<grid>
<rows>
<row>Name : <textbox id="forename" value="Hans" /></row>
<row>Surename : <textbox id="surename" value="Meier" /></row>
<row>Login Name : <textbox id="nickname" /></row>
<row>Email: <textbox id="email" value="a@b.cd" /></row>
<row>Costcenter : <textbox id="cc" value="DE61000327"></textbox></row>
<row><button label="submit" onClick="submit()"/></row>
</rows>
</grid>
</window>
<?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="zutritt.model">
<class name="Person" table="PERSON">
<id name="id" type="long" column="person_id" unsaved-value="0">
<generator class="identity"/>
</id>
<property name="smtpMail" column="SMTPMAIL"/>
<property name="firstName" column="FIRST_NAME"/>
<property name="lastName" column="LAST_NAME"/>
<property name="empNumber" column="EMP_NUMBER" />
<property name="notesMail" column="NOTESMAIL" />
<property name="costCenter" column="COSTCENTER" />
</class>
</hibernate-mapping>
Mark, das ist schön.
Aber diese DAO/Service Layer Geschichten sind echt nicht zum Spaß da.
Du machst ja jetzt quasi deinen Datenbankzugriff aus dem Frontend.
Bei mir sieht (als Beispiel ein Authentifizierungsfeature) so aus:
1. jsf Seite ruft Methode userReg.login in einem JSF backing bean auf (bei Klick auf "Anmelden button")
<h:form id="login">
<table class="infoTable" width="80%">
<tr>
<td class="infoTableTD" width="30%">
<h:outputLabel for="j_username" value="#{msg.name}"/>
</td>
<td class="infoTableTD" width="70%">
<t:inputText id="j_username" forceId="true" value="#{userReg.name}" required="true" size="40" maxlength="40">
<h:message for="j_username" styleClass="message"/>
</t:inputText>
</td>
</tr>
<tr>
<td class="infoTableTD">
<h:outputLabel for="j_password" value="#{msg.pwd}"/>
</td>
<td class="infoTableTD">
<t:inputSecret id="j_password" forceId="true" value="#{userReg.pwd}" required="true" size="40" maxlength="40">
<h:message for="j_password" styleClass="message"/>
</t:inputSecret>
</td>
</tr>
<tr>
<td colspan="2" class="infoTableTD">
<h:commandButton value="#{msg.login}" action="#{userReg.login}" />
</td>
</tr>
</table>
</h:form>
Methode login in JSF Backing Bean, die wiederum die Methode login(String name, String pwd) in der Service Klasse userService aufruft.
public String login() {
user = userService.login(name, pwd);
if (user == null) {
message("j_username", "login_message_user_not_found", name); // internationalisiert
return "failure";
} else {
context().getExternalContext().getSessionMap().put("user", user);
setId(user.getId());
return "success";
}
}
Methode userService.login(String name, String pwd), die wiederum als sagen wir wichtigste Methode userDao.findByNameAndPwd(String name, String pwd) aufruft.
public User login(String name,
String pwd) {
// initDeferredClose();
User user = userDao.findByNameAndPwd(name, pwd);
user = addStockpositionsToUser(user);
user = addOffersOrdersToUser(user);
return user;
}
userDao:
/**
* @see de.aja.fussi.model.UserDao#findByNameAndPwd(java.lang.String, java.lang.String)
*/
public User findByNameAndPwd(String name, String pwd) {
log.debug("getting user for name:" + name + "and PWD " + pwd);
List<User> list = (List<User>) getHibernateTemplate()
.findByNamedQueryAndNamedParam("findUserByNameAndPwd",
new String[] { "name", "pwd" },
new Object[] { name, pwd });
if (list.size() == 1) {
return (User) list.get(0);
} else {
return null;
}
}
(der untere Teil ist zugegeben nicht so toll, weil ich nicht weiss wo ich in Spring das getUniqueObject Feature von Hibernate finde).
Sieht zuerst ein bischen nach overkill aus, bringt aber eine Menge Flexibilität und Stabilität in die Geschichte, da auch die DAO und Service Klassen alle unter Einbindung der Datenbank regressionstestbar sind.
D.h. Wenn ich da was ändere, starte ich den Test und der kann mir genau sagen, ob das noch wie erwartet funktioniert. Dabei wird die Datenbank automatisch auf einen erwünschten Zustand gebracht.
Das ganze wird mit Spring zusammengebunden. Ach ja. Die Transactions werden quasi auf einen Schlag in Spring definiert:
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- deklarative Transaktion -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="REQUIRED"/>
<tx:method name="select*" read-only="true" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txServiceMethodsUser" expression="execution(* de.aja.fussi.market.UserService*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txServiceMethodsUser"/>
</aop:config>
Separation of concern:
- JSF-> Internationalisierung
- Service Layer -> Transaktionen, Aufruf der DAO Methoden, Validierung der Eingaben, Business Logic
- DAO Layer -> Hibernate Aufrufe
Die einzelnen Methoden sind übersichtlich. Jede Klasse hat klare Aufgaben (und nicht Web-Präsentation, DB-Zugriff, Transaktionen gleichzeitig).
In den Schichten gibt es keine zyklischen Abhängigkeiten.
Die JSF-Backing Beans sprechen nur die Service Klassen an, (aber nicht die DAO Klassen).
Die Service Klassen sprechen nur die DAO Klassen an (aber nicht die JSF Backing Beans)
Die DAO Klassen kennen die Klassen der beiden anderen Layer überhaupt nicht.
Und jetzt Testen. Ich möchte sichergehen, dass ich jederzeit prüfen will, dass die Methode login(String Name, String pwd) von UserService funktioniert.
In der Klasse UserServiceTest, die eine Spring-Klasse überschreibt, die wiederum von junit-Testcase erbt habe ich diese Methode, um die Daten der Datenbank in einen sauberen Status zu bringen:
@Override
public void onSetUp() throws Exception {
jdbcTemplate = new JdbcTemplate(dataSource);
JdbcHelper.cleanDatabase(jdbcTemplate);
JdbcHelper.fillStocks(jdbcTemplate, "copa");
}
und eine Methode, die die Funktionalität testet. Hier wird erst ein User registriert und dann per log-in geprüft, ob ein login funktioniert. Das alles ohne einen Web-Container wie Tomcat zu starten:
public void testLogin() throws DataValidationException {
User user = new User();
String name = "axel";
user.setName(name);
user.setPwd("pwd");
user.setMoney(20000.00);
userService.registerNewUser(user);
User user1 = userService.login(name, "pwd");
assertEquals(user, user1);
}
Ich kann dann auch noch Testmethoden schreiben, die prüfen wie die Anwendung reagiert, wenn eine falsche User/Passwort Kombination eingegeben wurde.
Spring JDBC kann man sich etwa so vorstellen. So sieht das in einer statischen Helper Klasse für meine Integrationstests aus:
->
// das sql statement -> alle offers für einen bestimmten Preis und eine bestimmte Aktie
public static final String GET_OFFERS_FOR_STOCK_AND_PRICE = "select id, iduser, idstock, amount, price, creation from offerstock where idStock=? and price=?";
// gebe Liste für die Aktie und den Preis zurück
public static List<Offerstock> getOffersForStockAndPrice(final SimpleJdbcTemplate simpleJdbcTemplate, Stock stock, double price) {
// ok. Das ist eine anonyme innere Klasse, die unten aufgerufen wird (no panic)
ParameterizedRowMapper<Offerstock> mapper = new ParameterizedRowMapper<Offerstock>() {
// notice the return type with respect to Java 5 covariant return
// types
public Offerstock mapRow(ResultSet rs, int rowNum)
throws SQLException {
// für jedes einzelnen Wert des Resultsets wird ein Wert im Objekt gesetzt, das wiederum in die Liste kommt, die am Ende zurückgeliefert wird.
Offerstock stockOffer = new Offerstock();
stockOffer.setId(rs.getLong("id"));
int idUser = rs.getInt("idUser");
User offerer = findUserById(simpleJdbcTemplate, idUser);
stockOffer.setUser(offerer);
int idStock = rs.getInt("idStock");
Stock stock = findStockById(simpleJdbcTemplate, idStock);
stockOffer.setStock(stock);
stockOffer.setAmount(rs.getInt("amount"));
stockOffer.setPrice (rs.getDouble("price"));
stockOffer.setCreation(rs.getTimestamp("creation"));
return stockOffer;
}
};
// hier wird das alles aufrufsmässig zusammengeknotet.
return simpleJdbcTemplate.query(GET_OFFERS_FOR_STOCK_AND_PRICE, mapper,
new Object[] { stock.getId(), price });
}
Nicht so schwierig und Schema F. Spring JDBC ist eine der nicht allzu häufigen Sachen in Spring, in denen Java5 echt Vorteile bringt. Muss aber nicht.
Das ist schon die komplizierteste Methode, da es hier n:1 Beziehungen von offerstock zu user und stock gibt.
ich habe mein Portlet nun soweit funktionsfähig, dass ich mit JSF in LifeRay Datensätze in meiner MySQL-DB anlegen kann. Leider klappt die Anzeige der vorhandenen Datensätze nicht. Die gesamte Zugriffsschicht mit Hibernate generiert LifeRay selbst, beim Anlegen neuer Datensätze klappt das sehr gut. Aber beim Auflisten der vorhandenen Sätze bekomme ich eine leere Seite im Portlet. Kein Fehler, keine Überschrift mit leerem Datentable, einfach gar nichts.
Ich suche hier schon ewig rum, vergleiche das immer mit dem Beispiel 'JSF Portlet Database interaction' von LifeRay, sieht alles korrekt aus, aber klappt nicht.
Das muss doch ein gravierender Fehler sein, wenn die komplette JSF-Seite nicht angezeigt wird, es kommt aber nirgendwo eine Meldung:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<f:view>
<h1>Display Access Models</h1>
<h:dataTable headerClass="portlet-section-header"
rowClasses="portlet-section-body,portlet-section-header-alternate"
value="#{access.getAllAccessEntries}" var="accessItem">
<h:column>
<f:facet name="header">
<h:outputText value="Access Number" />
</f:facet>
<h:outputText value="#{accessItem.accessNo}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Employee ID" />
</f:facet>
<h:outputText value="#{accessItem.employeeId}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Create Date" />
</f:facet>
<h:outputText value="#{accessItem.createDate}">
<f:convertDateTime type="both" dateStyle="short" timeStyle="short" />
</h:outputText>
</h:column>
</h:dataTable>
<br />
<br />
<h:form>
<h:commandButton action="back" value="Back" />
</h:form>
</f:view>
Ich hatte auch schon probiert, die Werte mit accessItem.access.employeeId anzusprechen (analog zu anderen Beispielen) aber beides klappt nicht und nix Fehler
Wenn nichts auf der Seite steht, steht meistens was im logfile.
access muss irgendwo im Request oder Session-Scope gebunden sein.
Probeweise kannst du auch testen, ob nicht vielleicht das access Objekt null ist.
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<f:view>
<h1>Display Access Models</h1>
<h:outputText value="#{accessItem}" />
</f:view>
das access-Objekt ist eine managed-bean im request-Scope:
<managed-bean>
<managed-bean-name>access</managed-bean-name>
<managed-bean-class>com.ext.portlet.access.ui.AccessBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>accessNo</property-name>
<value><accessNo></value>
</managed-property>
</managed-bean>
Hier ist die index.jsp, da klappt alles wie gewünscht:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<f:view>
<h1><h:outputText value="Zutrittsverwaltung JSF Portlet" /></h1>
<h3><h:outputText value="Add an access entry to the database:" />
</h3>
<h:form>
<h:messages />
<br />
<h:outputText value="Access Model:" />
<h:inputText id="accessNo" value="#{access.accessNo}" required="true" />
<br />
<br />
<h:commandButton value="Add access"
actionListener="#{access.addAccess}" />
<br />
<h:commandButton action="#{access.displayAccessEntries}" value="Display Access Models" immediate="true" />
</h:form>
</f:view>
access.displayAccessEntries liefert den String "success", der wiederum zur Seite display_access_entries.jsp navigiert. Das scheint auch noch zu klappen.
Dort soll der Datentable dann so gefüllt werden:
<h:dataTable headerClass="portlet-section-header"
rowClasses="portlet-section-body,portlet-section-header-alternate"
value="#{access.getAllAccessEntries}" var="accessItem">
Die Variable accessItem kommt nur in ideser Datei vor, muss ich die noch irgendwo anders deklarieren? Ich dachte, dass der dataTable automatisch die var=-Variable verwaltet und befüllt.
Meine AccessBean sieht so aus:
...
public void addAccess(ActionEvent actionEvent) {
FacesContext facesContext = FacesContext.getCurrentInstance();
try {
AccessEntryLocalServiceUtil.addAccess(getAccessNo(), getEmployeeId());
} catch (Exception e) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error adding access.", e.toString());
facesContext.addMessage(null, message);
e.printStackTrace();
return;
}
FacesMessage message = new FacesMessage(getAccessNo() + " was added successfully.");
facesContext.addMessage(null, message);
setAccessNo("");
}
public String displayAccessEntries() {
return "success";
}
public List getAllAccessEntries() {
List accessEntries = new ArrayList();
try {
accessEntries = AccessEntryLocalServiceUtil.getAll();
} catch (Exception e) {
e.printStackTrace();
}
return accessEntries;
}
...
1.
Das Ressources Bundle hab ich im faces-config.xml deklariert:
<application>
...
<locale-config>
<default-locale>de_DE</default-locale>
<supported-locale>de</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
<message-bundle>my-messages</message-bundle>
...
</application>
my_messages.properties befindet sich dann im ROOT der Webanwendung (nicht in Web-Inf).
2. log4j.properties oder log4j.xml müssen ebenfalls im ROOT der Webanwendung liegen.
3. Debugging: Ich mach das mit myEclipse. Sollte aber auch mit Eclipse-WTP gehen.
Eine andere Möglichkeit ist das hier (hab ich auch schon gemacht):
http://tomcat.apache.org/faq/development.html