Spring Tutorial

Z FI WIKI
Přejít na: navigace, hledání

V tomto tutoriále si ukážeme, ako vytvoriť jednoduchú webovú aplikáciu v Springu. Spring napojíme na databázu a JPA, toto napojenie otestujeme a nakoniec vytvoríme jednoduchú skladovú aplikáciu. Oproti PA165/Cvičení_Spring si ukážeme ako používať dependency injection pomocou xml konfiguračného systému, do databáze sa pripojíme pomocou JPA, toto pripojenie otestujeme bez toho, aby sme boli nútení niekam našu aplikáciu nasadiť, úvod do správneho návrhu enterprise aplikácii[1], a vyžitie výhod Spring MVC.

Aby ste sa naučili čo najviac, je dobré aby ste už ovládali Maven,JPA, jednotkové testy, vedeli niečo o JSP, JSTL a EL.

Na začiatok si stiahnite Maven projekt, kde sa maven postará o všetky závislosti, ktoré budeme potrebovať. Takisto je tam už vytvorená jedna entita. Stiahnuť si ju môžete na [2].

Pri programovaní môžete použiť ľubovoľné IDE. Ja osobne používam IDEA[3], pretože je najpokročilejšia a určite má najdokonalejšiu podporu Springu. Každopádne problém naprogramovať túto aplikáciu by ste nemali ani na iných IDE, ako Netbeans či Eclipse. IDEA má v sebe zahrnutý pokročilé doplňovanie naprieč všetkými konfiguračnými súbormi springu, pridávanie závislostí beanov, JPQL, mapovania pomocou JPA anotácií a kopu ďalších vychytávok. Konkrétne na vývoj webových aplikácií je nutné použiť Ultimate edition, na ktorú má však Masarykova Univerzita študentskú licenciu, takže odskúšať si ju môžte zadarmo. Z mavenu si môžete vytvoriť projekt pre ideu pomocou príkazu mvn idea:idea.

Čo sa týka požiadavkov na systém, ja som použil Tomcat 6 a MS SQL Server. Každopádne toto nie je pevné a môžete použiť ľubovoľný servlet container a SQL databázu. Akurát musíte patrične potom zmeniť konfiguračné súbory.

Životný cyklus objektov a pripojenie na databázu

Prvé čo musíme spraviť, je vytvorenie DAO (Data Access Object) pre našu entitu. Vytvoríme teda balík cz.muni.fi.pa165.springtutorial.dao . V tomto balíku vytvoríme triedu ItemDaoImpl. Mala by vypadať takto:

 
package cz.muni.fi.pa165.springtutorial.dao;
 
import cz.muni.fi.pa165.springtutorial.domain.Item;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
 
public class ItemDaoImpl {
 
    @PersistenceContext
    private EntityManager entityManager;
 
    public EntityManager getEntityManager() {
        return entityManager;
    }
 
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
 
    private EntityManager em() {
        if (entityManager == null) {
            throw new IllegalStateException("the entity manager is not set");
        }
        return entityManager;
    }
 
    public Item create(Item item) {
        em().persist(item);
        return item;
    }
 
    public Item update(Item item) {
        em().merge(item);
        return item;
    }
 
    public Item getById(long id) {
        return em().find(Item.class, id);
    }
 
    public void remove(Item item) {
        em().remove(item);
    }
 
    public List<Item> getAll() {
        return em().createQuery("select i from Item i").getResultList();
    }
}

Následné z tejto triedy extrahujeme rozhranie ItemDao, ktoré bude obsahovať všetky metódy, ktoré pracujú s entitou Item. To robíme z toho dôvodu, pretože Spring si pri dependency injection(ďalej v tutoriali už len DI) vytvára proxy objekty, ak aby daný objekt nemal interface, mohli by vzniknúť problémy.

Pri atribúte entityManager sme použili anotáciu z JPA @PersistentContext. Tá hovorí, že o nastavenie sa má starať aplikačný kontajner(v našom prípade Spring) využívajúc vlastnú EntityManagerFactory. To odkiaľ Spring túto továrnu triedu zoberie si povieme neskôr.

Z hľadiska správneho objektového návrhu, by sme ale pri persistovaní nášho objektu Item nemali používať priamo DAO, ale služba, ktorá používa jeho rozhranie. Je to výhodné, pretože potom môžme ľubovolne meniť jeho implementáciu tak ako nám to vyhovuje, raz do databázy, raz do súboru, ... . Takáto služba môže vyzerať takto:

 
package cz.muni.fi.pa165.springtutorial.service;
 
import cz.muni.fi.pa165.springtutorial.dao.ItemDao;
import cz.muni.fi.pa165.springtutorial.domain.Item;
import org.springframework.transaction.annotation.Transactional;
 
import java.util.List;
 
@Transactional
public class ItemServiceImpl implements ItemService {
 
    private ItemDao itemDao;
 
    public Item create(Item item) {
        return itemDao.create(item);
    }
 
    public Item update(Item item) {
        return itemDao.update(item);
    }
 
    public Item getById(long id) {
        return itemDao.getById(id);
    }
 
    public void remove(Item item) {
        itemDao.remove(item);
    }
 
    public void removeById(long id){
        Item item = getById(id);
        remove(item);
    }
 
 
    public List<Item> getAll() {
        return itemDao.getAll();
    }
 
    public ItemDao getItemDao() {
        return itemDao;
    }
 
    public void setItemDao(ItemDao itemDao) {
        this.itemDao = itemDao;
    }
}

Znovu si extrahujeme rozhranie tejto triedy. Trieda je zaanotovaná anotáciou @Transactional. Táto anotácia hovorí Springu, že volanie jednotlivých služieb má byť uzavreté do transakcií. To ako konfigurovať Spring, aby túto transakciu spracoval si ukážeme neskôr. Môžeme ňou anotovať buď celú triedu, tedy budú v transakcii všetky metódy tejto triedy, alebo len pred jednotlivé metódy.

Teraz už by sme mali pripravené triedy, tak môžme začať používať IoC kontajner. Tento kontajner využíva techniku DI na to, aby spravoval životný cyklus objektov. Objekty, o ktoré sa stará budeme ďalej nazývať beany. Konfiguráciu týchto beanov môžme robiť pomocou anotácií, alebo ich nastavujeme v xml súbore. My použijeme xml súbor. Vytvoríme teda v adresári resources xml súbor application-context.xml s takýmto obsahom.

 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <!--hovorí, že bean s id itemDao má byť objektom triedy ItemDaoImpl-->
    <bean id="itemDao"
          class="cz.muni.fi.pa165.springtutorial.dao.ItemDaoImpl"/>
 
 
    <!--hovorí, že bean s id itemService má byť objektom triedy ItemServiceImpl,
        a do atribútu itemDao má byť pomocou settera nastavený bean itemDao-->
    <bean id="itemService"
          class="cz.muni.fi.pa165.springtutorial.service.ItemServiceImpl">
        <property name="itemDao" ref="itemDao"/>
    </bean>
</beans>

Takto vyzerá jednoduché použitie IoC. IoC je však veľmi komplexný a jeho pododrobný manuál nájdete v dokumentácii Springu [4]

Teraz nastal čas konfigurovať pripojenie do databázy a nastavenie spracovania transakcií. Mohli by sme to doplniť do predchádzajúceho xml súboru, ale keďže chceme udržať prehľadnosť, tak vytvoríme osobitný konfiguračný súbor data-access.xml. Jeho obsah bude takýto.

 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/tx
      http://www.springframework.org/schema/tx/spring-tx.xsd">
 
    
    <!--umožní nám čítať nastavenia z properties súboru-->
    <bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location" value="classpath:/config.properties" />
	</bean>
 
    <!-- vytvorí nám datasource, ktorý sa vytiahne z jndi kontajneru 
    a jeho názov je definovaný v properties súbore  -->
    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean" scope="singleton">
        <property name="jndiName" value="${db.jndiName}"/>
        <property name="resourceRef" value="true"/>
    </bean>
 
    
    <!-- JPA EntityManagerFactory, určíme jej aký datasource má použiť,
     aký persitenceUnit sa má použiť, aký JPA framework používam, 
     akú databázu a iné konfigurácie JPA, práve táto továrna trieda
     bude využitá na nastavenie EntityManger do nášho DAO objektu-->
    <bean id="entityManagerFactory"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="springTutorial"/>
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter">
            <bean
                    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="false"/>
                <property name="generateDdl" value="true"/>
                <property name="databasePlatform" value="${db.dialect}"/>
            </bean>
        </property>
    </bean>
 
    <!-- Manažér transakcií pre entity manager factory, alternatíva k JTA -->
    <bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
 
    <bean id="jdbcTemplate"
          class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
        <constructor-arg index="0" ref="dataSource"/>
    </bean>
 
    <!--procesor anotácií JPA-->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
 
    <!--procesor prekladu výnimiek spojených s JPA-->
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
 
    <!--hovorí, že jednotlivé transakcie sú označované anotáciami-->
    <tx:annotation-driven/>
</beans>

V tomto konfiguračnom súbore používame properties súbor config.properties, preto ho musíme tiež vytvoriť.

db.dialect=org.hibernate.dialect.SQLServerDialect
db.jndiName=jdbc/tutDB

Jeho obsah hovorí, že budeme používať MS SQL Server databázu, a že datasource má JNDI názov jdbc/tudDB. Samozrejme, dialekt zvoľte podľa vami použitej databázy. Týmto máme konfiguráciu IoC a pripojenia na databázu hotové.

Testovanie spojenia s databázou

Pre testovanie budeme potrebovať špeciálnu konfiguráciu, ktorá bude platiť len v testoch. Konkrétne potrebujeme mať pripojenie na inú databázu. Pre testy je najvhodnejšia nejaká runtime databáza. My použijeme HSQLDB. Vytvoríme teda v adresari test/resources súbor test-application-context.xml a jdbc-hsql.properties.

 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/tx
      http://www.springframework.org/schema/tx/spring-tx.xsd">
 
    <!-- importujeme konfiguráciu DAO a služby -->
    <import resource="application-context.xml"/>
 
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location" value="jdbc-hsql.properties" />
	</bean>
 
	
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName"
			value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>
 
	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="springTutorial" />
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter">
			<bean
				class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="showSql" value="false" />
				<property name="generateDdl" value="true" />
				<property name="databasePlatform"
					value="${jdbc.databasePlatform}" />
			</bean>
		</property>
	</bean>
 
	<bean id="transactionManager"
		class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory"
			ref="entityManagerFactory" />
	</bean>
 
	<bean
		class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
 
	<bean
		class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
 
 
	<tx:annotation-driven />
 
</beans>
jdbc.databasePlatform=org.hibernate.dialect.HSQLDialect
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:mem:springtutorial
jdbc.username=sa
jdbc.password=

Využívame tu podobné pripojenie na databázu ako v súbore data-access.xml, s tým rozdielom, že datasource si nevyhľadáme pomocou JNDI, ale vytvoríme ho pomocou JDBC driveru, url, ... .

Konfigurácia je hotová, môžme pristupiť k písaniu samotného testu. Spring poskytuje podporu pre testovanie JPA, my rozšírime triedu AbstractJpaTests. Tá nám poskyktuje možnosť automatického vytvorenia a nastavenia potrebných beanov, spustenie sql skriptu, ktorý nám vytvorí požadovaný stav databázy, umožní vykonávanie SQL dotazov nad databázou... Vytvoríme teda trieda ItemServiceTest.

 
package cz.muni.fi.pa165.springtutorial.service;
 
import cz.muni.fi.pa165.springtutorial.domain.Item;
import org.junit.Test;
import org.springframework.test.jpa.AbstractJpaTests;
 
public class ItemServiceTest extends AbstractJpaTests {
 
    private ItemService itemService;
 
    public ItemServiceTest() {
        //automatické nastavenie atribútov bude fungovať podľa mena beanov
        setAutowireMode(AUTOWIRE_BY_NAME);
    }
 
    @Override
    protected void onSetUpInTransaction() throws Exception {
        super.onSetUpInTransaction();
        //spustí sa skript data.sql vždy na začiatku transakcie
        executeSqlScript("data.sql", false);
    }
 
    @Override
    protected String[] getConfigLocations() {
        //vráti názov súboru, kde je konfigurácia spring beanov
        return new String[]{"test-application-context.xml"};
    }
 
    public ItemService getItemService() {
        return itemService;
    }
 
    public void setItemService(ItemService itemService) {
        this.itemService = itemService;
    }
 
    @Test
    public void testGetById() {
        //jednoduché testovanie tak ako by sme testovali pomocou JUnit
        assertNull(itemService.getById(2));
        Item item = itemService.getById(1);
        assertNotNull(item);
        assertEquals("item1", item.getName());
        assertEquals("category", item.getCategory());
        assertEquals(3, item.getCountOnStock());
    }
 
    @Test
    public void testCreate() {
        Item item = new Item();
        item.setName("createdItem");
        item.setCategory("myCategory");
        item.setCountOnStock(10);
        itemService.create(item);
 
        //flush cachovaných dát do databázy
        sharedEntityManager.flush();
 
        //musíme testovať priamo obsach databázy, pretože JPA má dáta cachované, ak ak by sme len spravili getById, 
        //v databáze by v skutočnosti nemuselo byť nič
        assertEquals(10, getJdbcTemplate().queryForInt(
                "select countOnStock from Item where id=?", new Object[]{item.getId()}));
        assertEquals("createdItem", getJdbcTemplate().queryForObject(
                "select name from Item where id=?", new Object[]{item.getId()}, String.class));
        assertEquals("myCategory", getJdbcTemplate().queryForObject(
                "select category from Item where id=?", new Object[]{item.getId()}, String.class));
    }
}

V tomto príklade využívame všetky spomenuté nástroje. Používame v ňom skript data.sql, preto ho vytvoríme v test/resources

 
INSERT INTO Item(id, name, category, countOnStock) VALUES(1,'item1','category',3);

Tento skript je veľmi jednoduchý, v praxi samozrejme budeme potrebovať oveľa zložitejší.

V rámci samostatného tréningu si skúste vytvoriť testy ostatných metód ItemService.

Prezentačná vrstva pomocou Spring MVC

Výhodou Springu oproti napr. EJB 3.0 je, že jeho súčasťou je aj MVC framework na tvorbu webových aplikácií. Máme tak všetko pohromade. Spring samozrejme nevylučuje použitie iných MVC rámcov.

Základom každej webovej aplikácie v Jave je konfiguračný súbor web.xml. Ten umiestnime do adresára WEB-INF. Rovnako je to aj pri použití Spring MVC. Jeho obsah má byť nasledovný.

 
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>Spring tutorial</display-name>
    <description>Short tutorial to spring showing some logic with stock items</description>
 
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
 
    <resource-ref>
        <description>DB Connection</description>
        <res-ref-name>jdbc/tutDB</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
 
    <!--
        - zapojenie log4j na logovanie
       -->
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
 
 
    <!--
     - na jednom kontanjneri môžu bežať len aplikácie s rôznym webAppRootKey, preto  je potrebné túto hodnotu nastaviť
    -->
    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>springTutorial</param-value>
    </context-param>
 
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>WEB-INF/log4j.properties</param-value>
    </context-param>
 
 
    <!--filter, ktorý nám všetky prichádzajúce správy prekóduje do UTF-8-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
 
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <servlet-name>tutorial</servlet-name>
    </filter-mapping>
 
    <!--
        servlet, ktorý všetky požiadavky, ktoré k nemu prídu spracováva a
        predá ich Spring MVC, a ten uz si s nimi spraví, čo potrebuje
    -->
    <servlet>
        <servlet-name>tutorial</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
 
    <!--chceme, aby dispatcher spracovával všetky kontextové adresy, ktoré majú koncovku .htm-->
    <servlet-mapping>
        <servlet-name>tutorial</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
</web-app>

Najdôležitejšou časťou je zadefinovanie servletu DispatcherServlet, ktorý sa nám postará, aby sa všetky requesty so sufixom .htm dostali do Spring MVC. Túto koncovku sme si vybrali, pretože je to štandardná koncovka html súborov. To že za týmito stránkami tak ostane ukryté pred užívateľom. Mohli by sme si zvoliť akúkoľvek koncovku, akú by sme chceli.

Zadefinovali sme aj používanie log4j, preto musíme do adresáru WEB-INF pridať súbor log4j.properties. Jeho obsah môže byť nasledovný. V reálnom použitií by sme ho ďalej definovali podľa vlastných potrieb.

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c - %m%n
log4j.logger.org.apache.velocity.app.VelocityEngine=WARN
log4j.logger.org.codehaus.xfire.util.LoggingHandler=OFF
log4j.logger.org.hibernate=WARN
log4j.logger.org.hibernate.dialect.Dialect\ =INFO
log4j.logger.org.hibernate.tool.hbm2ddl=WARN
log4j.logger.org.hibernate.tool.hbm2ddl.SchemaUpdate=INFO
log4j.logger.org.hibernate.validator.ClassValidator=ERROR
log4j.logger.org.springframework=WARN
log4j.logger.org.springframework.aop.framework.autoproxy=WARN
log4j.rootLogger=INFO, stdout

Konfigurácia Spring MVC sa musí nachádzať v súbore tutorial-servlet.xml, kde tutorial je názov servletu, ktorý sme zadefinovali vo web.xml. K samotnému obsahu sa dostaneme neskôr.

Spracovanie samotného požiadavku funguje asi tak, že DispatcherServlet sa spýta nastavenej implementácie HandlerMapping, aký kontroler sa má použiť, ten spracuje požiadavok, vygeneruje odpoveď a deleguje jej zobrazenie na nejaký view template, najčastejšie to je nejaký JSP súbor.

Spracovanie požiadavku v Spring MVC

Napíšme si teda náš prvý kontroler. V Spring MVC všetky kontrolery implementujú rozhranie Controller. Existujú rôzne implementácie tohto rozhrania, ktoré nám umožnia spracovávať jednoduché dotazy, formuláre, príkazy či dokonca postupnosť formulárov(tzv. wizardov).

Prvý kontroler, ktorý si spracujeme nám bude zobrazovať všetky položky, ktoré máme v databáze. Bude dediť z abstraktnej triedy AbstractController a vyzerať asi takto.

 
package cz.muni.fi.pa165.springtutorial.web.controllers;
 
import cz.muni.fi.pa165.springtutorial.service.ItemService;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class HomeController extends AbstractController {
 
    private ItemService itemService;
 
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        return new ModelAndView("home", "items", itemService.getAll());  
    }
 
    public ItemService getItemService() {
        return itemService;
    }
 
    public void setItemService(ItemService itemService) {
        this.itemService = itemService;
    }
}

V triede AbstractController je abstraktná metóda handleRequest, ktorú sme museli imlementovať. My sme ju implemntovali velmi jednoducho, v podstate hned delegujeme zobrazenie (ďalej už len view) s názvom home, a predáme mu dáta o položkách v databázi, ktoré sme získali pomocou služby, ktorá bude tomuto kontoleru automaticky nastavená.

Teraz vytvoríme v adresári WEB-INF súbor tutorial-servlet.xml, a vytvoríme v ňom bean nášho kontroleru.

 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <import resource="classpath:/application-context.xml"/>
    <import resource="classpath:/data-access.xml"/>
 
    <bean id="homeController"
          class="cz.muni.fi.pa165.springtutorial.web.controllers.HomeController">
        <property name="itemService" ref="itemService"/>
    </bean>
 
</beans>

To by ale nestačilo. Už sme zmienili rozhranie HandlerMapping. Jeho implementácie vedia podľa url rozpoznať, ktorý request majú preposlať na aký kontroler. Sú rôzne implementácie, ktoré to rozhodujú podľa mena beanu či na základe predom určených properties. My použijeme SimpleUrlHandlerMaping, ktorá určuje obsluhujúci kontroler práve podľa vopred určených properties. Do súboru tutorial-servlet.xml preto pridáme definíciu beanu.

 
<bean id="simpleUrlMapping"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
         <props>
            <prop key="home.htm">homeController</prop>
         </props>
    </property>
</bean>

Tu hovoríme, že url home.htm sa má presmerovať na náš homeController.

Spring MVC už vie, komu má preposielať požiadavky, ale ešte stále nevie, ako má zobrazovať odpovede. Na to nám slúžia implementácie rozhrania ViewResolver. Tie môžu vrátiť jednak html stránku, obrázok, súbor či napr. RSS kanál. My budeme potrebovať ten, čo zobrazuje html stránky. Doplníme teda opäť do súboru tutorial-servlet.xml definíciu nasledovného beanu.

 
<bean id="viewResolver"
      class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/jsp/</value>
    </property>
    <property name="suffix">
        <value>.jsp</value>
    </property>
</bean>

Prvá property hovorí, kde sa majú jednotlivé šablóny generujúce html stránku nachádzať, teda vlastne prefix, kde sa nachádza view, v našom prípade home. Druhá property naznačuje typ, resp. koncovku súboru, v našom prípade JSP súbory. Náš kontroler teda vlastne deleguje zobrazenie na view home, ktoré viewResolver preloží na súbor home.jsp, ktorý sa nachádza v adresári /WEB-INF/jsp/.

Keď už aplikácia vie, kde má view hľadať, tak ho vytvoríme. V adresári WEB-INF/jsp/ vytvoríme súbor home.jsp s nasledovným obsahom.

 
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<body>
<h1>Items</h1>
<table>
    <tr>
        <th>name</th><th>category</th><th>count on stock</th>
    </tr>
    <c:forEach items="${items}" var="item">
        <tr>
            <td>${item.name}</td>
            <td>${item.category}</td>
            <td>${item.countOnStock}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>

Ten nám vytvorí html stránku, kde jednotlivé položky uloží do html tabuľky. Na vygenerovanie HTML kódu sme použili tagy JSTL a EL.

Teraz je už naša aplikácia pripravená na nasadenie na ľubovoľný servlet kontajner. Na tomto servlet kontajneri však nesmieme zabudnúť zadefinovať JNDI datasource. Kedže my použijeme Apache Tomcat, za týmto účelom môžme použiť nasledovnú konfiguráciu. Do adresáru META-INF, ktorý sa nachádza na rovnakej úrovni ako WEB-INF, vytvoríme súbor context.xml.

 
<?xml version='1.0' encoding='utf-8'?>
<Context>
  <Resource
    name="jdbc/tutDB"
    auth="Container"
    type="javax.sql.DataSource"
    maxActive="100"
    maxIdle="3"
    maxWait="1000"
    username="sa"
    password=""
    driverClassName="net.sourceforge.jtds.jdbc.Driver"
    url="jdbc:jtds:sqlserver://localhost:1433/springtutorial"
  />
</Context>

Keď sme konfigurovali pripojenie Springu na databázu, použili sme práve JNDI zdroj práve s týmto menom. V tomto prípade sa pripájame na databázu springtutorial na MS SQL Server databázi, ktorý nám beží na rovnakom stroji, ako aj aplikácia. Túto konfiguráciu môžme ľubovoľne upraviť na pripojenie na hocijakú databázu. Nesmieme však zabudnúť potom patrične zmeniť dialekt v konfigurácii Hibernate.

Teraz už môžme naozaj víťazoslávane našu aplikáciu na Tomcat nasadiť. Za predpokladu, že máme Tomcat lokálne u seba na počítači načúvajúcom http požiadavkam na porte 8080, tak na url http://localhost:8080/springTutorial/home.htm si môžeme pozrieť výsledok našej doterajšej práce. Bohužial je naša databáza prázdna, preto žiadne položky nevidíme.

Vytvoríme teda formulár, ktorým si budeme môcť do systému vložiť nové položky. Vytvoríme ho všeobecne tak, aby sa dal potom použiť aj na editáciu už existujúcich položiek. Kontroler, ktorý bude tento formulár obsluhovať nazveme EditItemController, a bude dediť triedu SimpleFormController, ktorá nám uľahčuje prácu s formulármi. Tá funguje tak, že obsluhuje jednak html stránku s formulárom, ktorú zobrazí pri GET požiadavku, a zadané dáta aj spracuje, ak požiadavok použil metódu POST.

Všekty kontrolery sú v springu navrhnuté tak, že v nich nemôžeme použiť riadenie transakcií. Je to z dôvodu, keď pozmeníme nejaký údaj z databáze, ale ten následne neprejde validáciou, tak po následnom zrušení EntityManager-a by sa na základe jeho fungovania všetky zmeny vykonané na entitách poslali do databáze, čo by bolo samozrejme nežiaduce. O validácii môže nájsť viac v dokumentácii Spring MVC [5].

Z tohto dôvodu teda musíme implementovať do služby ItemService metódu createOrUpdate, ktorá môže vypadať nasledovne.

 
   public Item createOrUpdate(Item item){
        Long itemId = item.getId();
        if(itemId != null){
            Item dbItem = getById(itemId);
            if (dbItem != null) {
                dbItem.setName(item.getName());
                dbItem.setCategory(item.getCategory());
                dbItem.setCountOnStock(item.getCountOnStock());
                return update(item);
            }
            return null;
        }else{
            return create(item);
        }
    }

Keď máme toto hotové, vytvoríme triedu EditItemContoller.

 
package cz.muni.fi.pa165.springtutorial.web.controllers;
 
import cz.muni.fi.pa165.springtutorial.domain.Item;
import cz.muni.fi.pa165.springtutorial.service.ItemService;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
 
import javax.servlet.http.HttpServletRequest;
 
public class EditItemController extends SimpleFormController {
 
    private ItemService itemService;
 
    public EditItemController() {
        setCommandClass(Item.class);
        setCommandName("item");
    }
 
    @Override
    protected ModelAndView onSubmit(Object command) throws Exception {
        itemService.createOrUpdate((Item)command);
        return super.onSubmit(command);
    }
 
    @Override
    protected Object formBackingObject(HttpServletRequest request) throws Exception {
        String itemId = request.getParameter("itemId");
        if(itemId != null){
            return itemService.getById(Long.parseLong(itemId));
        }
        return super.formBackingObject(request);
    }
 
    public ItemService getItemService() {
        return itemService;
    }
 
    public void setItemService(ItemService itemService) {
        this.itemService = itemService;
    }
}

V konštruktore určíme, inštanciou akej triedy bude objekt, s ktorým budeme pracovať vo formulári, a ako túto inštanciu nazveme. Metóda onSubmit nám hovorí, čo sá má so zadanými dátami spraviť, výsledok následne prepošle na view, ktorý je definovaný ako úspešný. Toto nastavenie vykonáme pri definovaní beanu. Metóda formBackingObject nám na základe požiadavku vytvorí objekt a ďalej ho používa. Pri vytváraní novej položky použijeme defaultnú implementáciu, pri jej editácii vytiahneme dáta z databázy, kedže chceme užívateľovi zobrazi stávajúce dáta vo formulári. SimpleFormController definuje rôzne varianty týchto metód, použijeme vždy tú ktorá nám najviac vyhovuje.

Zajímavou metódou, ktorú sme nepoužili by mohla byť metóda referenceData, ktorá poskytné view, ktorý zobrazuje stránku formuláru, potrebné dáta, ktoré nie sú obsiahnuté v COMMAND objekte. Mohli by to byť napr. prístupné hodnoty pre nejaký atribút typu enum, či dáta ktoré nie sú potrebne pre samotný formulár, ale len čisto pre html stránku, na ktorej tento formulár je.

Teraz prídáme do tutorial-servlet.xml definíciu beanu nášho kontrolera

 
    <bean id="editItemController"
          class="cz.muni.fi.pa165.springtutorial.web.controllers.EditItemController">
        <property name="itemService" ref="itemService"/>
        <property name="formView" value="editItemForm"/>
        <property name="successView" value="redirect:home.htm"/>
    </bean>

a do simpleUrlMapping pridáme property hovoriacu, aká url sa má preposlať na tento kontroler.

 
<prop key="editItem.htm">editItemController</prop>

V definícii kontroleru sme nastavili view, ktorý má zobraziť formulár, a view, ktorý sa má zobraziť pri úspešnom prevedení formuláru. Tu sme použili formulku redirect:home.htm, ktorá hovorí, že po vykonaní submitu sa má spraviť redirect na url home.htm .

Posledná vec, ktorú musíme spraviť, je vytvoriť súbor editItemForm.jsp .

 
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<body>
<form:form action="editItem.htm" method="post" commandName="item">
    <form:hidden path="id"/>
    <table>
        <tr>
            <td>Name</td>
            <td><form:input path="name"/></td>
        </tr>
        <tr>
            <td>Category</td>
            <td><form:input path="category"/></td>
        </tr>
        <tr>
            <td>CountOnStock</td>
            <td><form:input path="countOnStock"/></td>
        </tr>
        <tr>
            <td><input type="submit" value="OK"/></td>
        </tr>
    </table>
</form:form>
</body>
</html>

Tu okrem JSTL tagov používame aj špeciálne tagy zadefinované v Spring MVC, ktoré nám umožňujú prácu s formulármi. Ich použitie je jednoduché, zaujímavé je namapovanie jednotlivých inputov vo formuláre na jednotlivé atribúty objektu. Na to nám práve služí COMMAND, ktorý sme už spomínali predtým. Vo formuláre sa odkážeme práve na názov tohoto commandu a jednotlivé inputy potom pomocou atribútu path spojíme s atribútmi tohto COMMANDU.

Na úplný záver ešte do skriptu home.jsp pridáme odkaz na vytvorenie novej položky

 
<a href="editItem.htm">New item</a>

a ku kazdemu riadku tabuľky pridáme odkaz na editáciu danej položky

 
<td><a href="editItem.htm?itemId=${item.id}" >Edit</a></td>

Teraz už máme kompletnú aplikáciu. Tú nájdete na [6]. War archív môžete získať pomocou príkazu mvn install alebo mvn package.