Pull to refresh

Легкий старт: Spring + MongoDB

Reading time 16 min
Views 52K


Поискал на хабре схожие статьи, нашел только Morphia — легкий ORM для MongoDB, управляемый аннотациями, ничего по связке Spring Data + MongoDB не нашлось, в связи с этим решил написать пост из раздела «для самых маленьких» по настройке и использованию связки Spring + MongoDB.

Что будет из себя представлять само приложение:
Сделаем самый простой менеджер контактов, для того, чтобы попробовать на примере элементарные операции CRUD.

Используемые библиотеки:
  • Spring IoC, MVC, Data (Mongo)
  • Mongo Driver
  • Log4j через sl4j
  • ну и немножко дополнительных, которые опишу уже в конфигурационном файле

Собирать проект будет Maven, а код, лично я, пишу в Intellij IDEA (думаю, это лучшая IDE для Java). Кстати говоря, о преимуществах этой среды в своё время рассказывал asolntsev в посте Почему IDEA лучше Eclipse.

Конфигурация проекта

Для начала опишем конфигурацию Maven:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>war</packaging>

    <groupId>habra</groupId>
    <artifactId>habr</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- На момент написания статьи, версии библиотек являются последними -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <version.jdk>1.6</version.jdk>
        <version.spring>4.0.2.RELEASE</version.spring>
        <version.spring.mongodb>1.4.0.RELEASE</version.spring.mongodb>
        <version.jackson>1.9.13</version.jackson>
    </properties>

    <dependencies>
        <!-- Все, что нужно для Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${version.spring}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${version.spring}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${version.spring}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${version.spring}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
            <version>${version.spring.mongodb}</version>
        </dependency>

        <!-- MongoDB драйвер -->
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>2.11.4</version>
        </dependency>

        <!--
        Jackson JSON Mapper
        Тащу его всегда, когда нужно писать API на базе JSON объектов.
        В нашем случае можете эту библиотеку не тянуть.
        -->
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>${version.jackson}</version>
        </dependency>

        <!-- Servlet Api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- Логгирование -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>

        <!-- TEST -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!--
        Apache Commons, тяну пратически в каждый свой проект из-за их полезности.
        Можете также их пропустить.
        -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Теперь, когда все библиотеки описаны, можно приступить к описанию конфигурационных файлов Spring.
Для этого я обычно создаю папку «spring» в src/main/resources, некоторые хранят файлы конфигурации Spring в webapp/WEB-INF/*, но это уже кому как удобно. Скажу сразу, конфигурационных файла будет 2, по крайне мере я считаю, что это наиболее верный вариант описания конфигурации. 1-й файл будет включать конфигурацию по созданию бинов, подключение к БД, и пр., так сказать конфигурация контекста всего приложения. 2-й файл — это описание работы DispatcherServlet, в общем все, что связано уже с отображением страниц, и вообще c Spring MVC.

Начнем с описания контекста всего приложения:

src/main/resources/spring/applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mongo="http://www.springframework.org/schema/data/mongo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/data/mongo
        http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">

    <!-- Включаем контекстные аннотации типа @Service, @Controller, @Repository... -->
    <context:annotation-config/>

    <!--
    Указываем Springу пакет, в котором он будет искать классы,
    помеченные аннотациями @Service, @Repository, и создавать их бины, но исключать он будет @Controller,
    т.к. эти классы нам нужны будут в другом месте.
    -->
    <context:component-scan base-package="ru.habrahabr.sm">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--
    Загружает properties файл в конфигурацию Spring (т.е. сюда).
    Переменные из файла можно будет использовать как ${mongo.host} (пример см. ниже)
    -->
    <context:property-placeholder location="classpath:database.properties"/>

    <!-- Создаем бин 'mongo' -->
    <mongo:mongo host="${mongo.host}" port="${mongo.port}"/>

    <!--
    Создаем бин 'mongoDbFactory'.
    Если MongoDB не требует авторизации, то поля username, password можно убрать
    -->
    <mongo:db-factory
            username="${mongo.username}"
            password="${mongo.password}"
            dbname="${mongo.db}"
            mongo-ref="mongo"/>

    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
    </bean>
</beans>

Описание контекста Spring MVC:

src/main/resources/spring/dispatcherServlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- Включаем MVC аннотации -->
    <mvc:annotation-driven/>

    <!--
    Использование MVC Resources
    Проще говоря, все файлы из папки webapp/resources/ будут доступны по адресу: localhost/resources/
    -->
    <mvc:resources mapping="/resources/**" location="/resources/"/>

    <!-- Указываем Spring MVC где искать классы-контроллеры -->
    <context:component-scan base-package="ru.habrahabr.sm">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <!-- Указываем Spring MVC где будут лежать наши Viewшки, в данном случае это "/WEB-INF/pages/" -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

Отлично, Spring настроен, но если вы внимательно читали конфигурационные файлы, то заметили, что в applicationContext.xml мы тянули "classpath:database.properties". Это значит, что нужно создать файл «database.properties» со следующим содержанием:

src/main/resources/database.properties
mongo.host=localhost
mongo.port=27017
mongo.db=mydb
mongo.username=username
mongo.password=password

Разумеется, нужно заменить данные после знака '=' своими. Заметьте, что поля username, password необязательные, и если у вас не нужна авторизация для получения доступа к MongoDB, загляните в applicationContext.xml, там указано какие поля нужно убрать.
Сразу создадим конфигурационный файл для системы логгирования:

src/main/resources/log4j.properties
log4j.rootLogger=INFO, stdout

# Direct log messages to stdout
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{dd.MM.yy HH:mm:ss} %5p - %m%n
log4j.appender.stdout.encoding=UTF-8

Наконец можно перейти к завершающему этапу конфигурирования Java Web приложения — описание web.xml:

src/main/webapp/WEB-INF/web.xml
<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">

    <!-- Spring Application Context -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/applicationContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    <!-- /Spring Application Context -->

    <!-- Spring Dispatcher Servlet Context -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/dispatcherServlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- /Spring Dispatcher Servlet Context -->

    <!-- Filters -->

    <!-- Character Filter -->
    <filter>
        <filter-name>encodingFilter</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>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- /Character Filter -->

    <!-- /Filters -->

</web-app>

На данном этапе у Вас уже должна быть следующая структура файлов в проекте:

В папку «src/webapp/resources/» можете класть любой статический контент (картинки, стили, js скрипты, и т.д.).

Написание кода
Наконец-то проект сконфигурирован, и можно приступить к написанию самого кода приложения. Начнем с описания модели и реализации слоя по работе с БД. Хочу отметить следующее: в MongoDB нет целочисленного AUTO_INCREMENT PK поля, а объекту по умолчанию присваивается ID такого вида: ObjectId(«5326b46f44ae9e6328b4566c»). Spring Data понимает этот ID как объект типа String. Проще говоря, если вам нужно сделать так, чтобы у вас ID объекта было целочисленным и авто увеличивающимся, то придется над этим поработать самостоятельно. Но на самом деле в этом нет ничего сложного и не стоит этого бояться, а я сейчас опишу как это делается! Если же Вас устраивает и длинный String в качестве ID объекта, то пропустите все классы (и соответственно их использование) в которых встречается слово «Sequence».

Для начала создаем коллекцию в БД с именем sequences. И руками вносим туда объект (insert):
{
    "_id" : "contacts",
    "sequence" : 0
}
В этой коллекции мы будем хранить пару: «имя таблицы» — «последний ID», это значит, что для каждой таблицы(коллекции), в которой Вы хотите использовать нашу самописную AUTO_INCREMENT реализацию, вам нужно внести новую запись, как указано выше, только вместо «contacts» введите имя нужной Вам коллекции. Кстати, если еще не знаете какой инструмент(management-tool) использовать для MongoDB, то рекомендую Robomongo.

Теперь создаем класс-обертку для этой коллекции:

Sequence.java
package ru.habrahabr.sm.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

/**
 * Date: 26.03.2014
 * Time: 15:38
 *
 * @author Ruslan Molchanov (ruslanys@gmail.com)
 */
@Document(collection = Sequence.COLLECTION_NAME)
public class Sequence {
    public static final String COLLECTION_NAME = "sequences";

    @Id
    private String id;
    private Long sequence;

    public Sequence() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Long getSequence() {
        return sequence;
    }

    public void setSequence(Long sequence) {
        this.sequence = sequence;
    }
}

Обратите внимание на запись "@Document(collection = Sequence.COLLECTION_NAME)" на самом деле можно было бы написать "@Document(collection = «sequences»)", но это уже скорее дело вкуса. Плюс есть одно явное достоинство у такого метода, дальше поймете какое.

Теперь опишем слой по работе с БД для класса-обертки "Sequence", скажу сразу, чтобы не растягивать статью, я не буду создавать интерфейсы для сервисов и DAO, буду пользоваться уже реализациями объектов (хотя это вроде не очень хорошо), надеюсь, что Вы понимаете о чем идет речь.

SequenceDao.java
package ru.habrahabr.sm.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;
import ru.habrahabr.sm.exceptions.SequenceException;
import ru.habrahabr.sm.model.Sequence;

/**
 * Код взят с замечательного ресурса www.mkyong.com
 * http://mkyong.com/mongodb/spring-data-mongodb-auto-sequence-id-example/
 */
@Repository
public class SequenceDao {
    @Autowired private MongoOperations mongoOperations;

    public Long getNextSequenceId(String key) {
        // получаем объект Sequence по наименованию коллекции
        Query query = new Query(Criteria.where("id").is(key));

        // увеличиваем поле sequence на единицу
        Update update = new Update();
        update.inc("sequence", 1);

        // указываем опцию, что нужно возвращать измененный объект
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.returnNew(true);

        // немного магии :)
        Sequence sequence = mongoOperations.findAndModify(query, update, options, Sequence.class);

        // if no sequence throws SequenceException
        if(sequence == null) throw new SequenceException("Unable to get sequence for key: " + key);

        return sequence.getSequence();
    }
}

Теперь класс исключения, которое выбрасывается, если в нашей коллекции `sequences` не будет соответствующей записи в БД (о которой я писал выше):

SequenceException.java
package ru.habrahabr.sm.exceptions;

/**
 * Date: 26.03.2014
 * Time: 16:09
 *
 * @author Ruslan Molchanov (ruslanys@gmail.com)
 */
public class SequenceException extends RuntimeException {
    public SequenceException(String message) {
        super(message);
    }
}

Теперь создаем в БД коллекцию `contacts`, и класс-обертку:

Contact.java
package ru.habrahabr.sm.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

/**
 * Date: 26.03.2014
 * Time: 15:29
 *
 * @author Ruslan Molchanov (ruslanys@gmail.com)
 */
@Document(collection = Contact.COLLECTION_NAME)
public class Contact implements Serializable {
    public static final String COLLECTION_NAME = "contacts";

    @Id
    private Long id;
    /* *******************************************************
     Если вы хотите, чтобы ID объекта была автогенерируемая
     строка (об этом я писал в посте), то опишите поле ID так:
     @Id
     private String id;
     ********************************************************* */

    private String name;
    private String number;
    private String email;

    public Contact() {
    }

    public Contact(String name, String number, String email) {
        this.name = name;
        this.number = number;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Теперь опишем DAO слой для нашего класса «Contact»:

ContactDao.java
package ru.habrahabr.sm.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Repository;
import ru.habrahabr.sm.model.Contact;

import java.util.List;

/**
 * Date: 26.03.2014
 * Time: 19:13
 *
 * @author Ruslan Molchanov (ruslanys@gmail.com)
 */
@Repository
public class ContactDao {
    @Autowired private MongoOperations mongoOperations;

    public void save(Contact contact) {
        mongoOperations.save(contact);
    }

    public Contact get(Long id) {
        return mongoOperations.findOne(Query.query(Criteria.where("id").is(id)), Contact.class);
    }

    public List<Contact> getAll() {
        return mongoOperations.findAll(Contact.class);
    }

    public void remove(Long id) {
        mongoOperations.remove(Query.query(Criteria.where("id").is(id)), Contact.class);
    }
}

Хотелось бы отметить, что обновить запись в БД можно на уровне DAO следующим образом:
mongoOperations.updateFirst(query, update, Contact.class);
Разобраться с этим самостоятельно проще простого и не составит для Вас трудностей. Но хотелось бы отметить, что если вы выполните:
mongoOperations.save(contact);
на объект, у которого ID уже выставлен, и существует в БД, то произойдет перезапись объекта в БД под указанным ID.

Теперь опишем слой бизнес-логики для класса «Contact»:

ContactService.java
package ru.habrahabr.sm.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.habrahabr.sm.dao.ContactDao;
import ru.habrahabr.sm.dao.SequenceDao;
import ru.habrahabr.sm.model.Contact;

import java.util.List;

/**
 * Date: 26.03.2014
 * Time: 20:09
 *
 * @author Ruslan Molchanov (ruslanys@gmail.com)
 */
@Service
public class ContactService {
    @Autowired private SequenceDao sequenceDao;
    @Autowired private ContactDao contactDao;
    
    public void add(Contact contact) {
        contact.setId(sequenceDao.getNextSequenceId(Contact.COLLECTION_NAME));
        contactDao.save(contact);
    }
    
    public void update(Contact contact) {
        contactDao.save(contact);
    }
    
    public Contact get(Long id) {
        return contactDao.get(id);
    }
    
    public List<Contact> getAll() {
        return contactDao.getAll();
    }
    
    public void remove(Long id) {
        contactDao.remove(id);
    }
}

Ну вот, собственно и все, все, что касается связки Spring + MongoDB мы проделали, т.е. теперь Вы можете пользоваться ORM Spring Data с уже подключенной к проекту MongoDB, но раз я обещал на примере справочника показать работу с БД, то осталось написать еще класс-контроллер:

MainController.java
package ru.habrahabr.sm.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import ru.habrahabr.sm.model.Contact;
import ru.habrahabr.sm.services.ContactService;

/**
 * Date: 26.03.2014
 * Time: 20:30
 *
 * @author Ruslan Molchanov (ruslanys@gmail.com)
 */
@Controller
public class MainController {
    @Autowired private ContactService contactService;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView showAll() {
        ModelAndView modelAndView = new ModelAndView("all");

        modelAndView.addObject("contacts", contactService.getAll());

        return modelAndView;
    }

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public ModelAndView showAddForm() {
        return new ModelAndView("add_form", "contact", new Contact());
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String addContact(@ModelAttribute("contact") Contact contact) {
        if(contact.getId() == null) contactService.add(contact);
        else contactService.update(contact);

        return "redirect:/";
    }

    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    public ModelAndView showEditForm(@RequestParam(required = true) Long id) {
        return new ModelAndView("add_form", "contact", contactService.get(id));
    }

    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    public String deleteContact(@RequestParam(required = true) Long id) {
        contactService.remove(id);

        return "redirect:/";
    }
}

Ну и вьюшки:

src/webapp/WEB-INF/pages/add_form.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Добавить контакт</title>
</head>
<body>
<form:form method="POST" action="/add" modelAttribute="contact">
    <form:hidden path="id" />
    <table>
        <tr>
            <td>Name:</td>
            <td><form:input path="name" /></td>
        </tr>
        <tr>
            <td>Number:</td>
            <td><form:input path="number" /></td>
        </tr>
        <tr>
            <td>E-mail:</td>
            <td><form:input path="email" /></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" />
            </td>
        </tr>
    </table>
</form:form>
</body>
</html>

Еще одна:

src/webapp/WEB-INF/all.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head>
    <title>Все контакты</title>
</head>
<body>
<table width="600px">
    <tr>
        <td><b>ID</b></td>
        <td><b>Name</b></td>
        <td><b>Number</b></td>
        <td><b>E-mail</b></td>
        <td><b>Action</b></td>
    </tr>
    <c:forEach var="contact" items="${contacts}">
        <tr>
            <td>${contact.id}</td>
            <td>${contact.name}</td>
            <td>${contact.number}</td>
            <td>${contact.email}</td>
            <td><a href="/edit?id=${contact.id}">Edit</a> | <a href="/delete?id=${contact.id}">Delete</a></td>
        </tr>
    </c:forEach>
    <tr>
        <td colspan="5">
            <a href="/add">Добавить запись</a>
        </td>
    </tr>
</table>
</body>
</html>


Ну вот и все, собираем проект, запускаем, и вуаля:


Ссылка на исходники: github.com/ruslanys/sample-spring-mongodb

P.S. Буду счастлив, если кому-нибудь пригодится этот пост. Буду благодарен за подсказки и исправления.

UPD. По рекомендации товарищей с Хабра, занес все листинги под спойлер.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+19
Comments 11
Comments Comments 11

Articles