Разобравшись со всеми навалившимися делами и задачами, спешу опубликовать 3-ю завершающую статью из серии “Разработка веб-приложений на Spring”, которая полностью посвящается непосредственно реализации поставленной задачи в предыдущих статьях: Разработка веб-приложений на Spring. Часть 1 (Проектирование), Разработка веб-приложений на Spring. Часть 1 (Конфигурирование).
В этой статье нам предстоит сделать:
- Сервис и DAO для работы с данными
- Контроллер обработки формы
- Валидатор формы
- JSP страницу
Одним словом – ерунда осталась
Сервисы и DAO
Для начала разберёмся, для чего нужно и то и другое, ведь можно обойтись без сервисов и работать просто с DAO. Да, можно. Однако, прежде всего это более правильный подход или архитектура, называйте как угодно. Уровень сервисов включает в себя не только выполнение таких простых действий как сохранить и получить объект, но и содержит в себе часть логики, необходимую для выполнения действий над данными перед сохранением или получением из БД. Тобишь, говоря более наглядно, метод отвечающий, например, за сохранение нового пользователя в БД на уровне DAO будет скорее всего называться save, а на уровне сервиса register. Улавливаете разницу? Во вторых, на уровне сервисов в нашем приложении будет происходить управление транзакциями.
Переходя от нудной теории к практике создадим сервис, обеспечивающий добавление нового подписчика в БД.
InviteSubscribersService.java
@Transactional(readOnly = true)
public class InviteSubscribersService {
private InviteSubscribersDAO dao;
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void create(InviteSubscriber subscriber) throws DataAccessException, ServiceException {
if (subscriber == null) {
throw new ServiceException(”Subscriber cannot be null or empty”);
}
getInviteSubscribersDAO().save(subscriber);
}
public void setInviteSubscribersDAO(InviteSubscribersDAO dao) {
this.dao = dao;
}
public InviteSubscribersDAO getInviteSubscribersDAO() {
return dao;
}
}
Разберём по порядку, что тут к чему.
Аннотация @Transactional(readOnly = true)перед объявлением класса говорит о том, что все методы содержащиеся в классе расcчитаны по умолчанию на чтение данных из БД. Далее сследует объявление DAO, с которым будет работать сервис и непосредственно сам метод, выполняющий создание нового подписчика в БД.
Аннотация @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)говорит о том, что этот метод транзакционный. Фокус в том, что вам не нужно вручную делать commit и rollback, как это приходилось делать нашим предкам. Об этом позаботится менеджер транзакций встроенный в Spring, который мы успешно сконфигурировали в предыдущей статье. В случае, если произойдёт exception, менеджер автоматически выполнит rollback, в противном случае commit. При желании, можно указать на какие конкретно типы Exception должен срабатывать rollback. Об этом более детально смотрите документацию Spring.
Далее, как вы уже догадались, следует создать DAO.
InviteSubscribersDAO.java
public class InviteSubscribersDAO extends HibernateDaoSupport {
public void save(InviteSubscriber subscriber) throws DataAccessException {
getHibernateTemplate().save(subscriber);
}
}
Здесь всё просто. Работа с Hibernate происходит через специальный слой HibernateDaoSupport встроенный в Spring. Зачем использовать какой-то слой, если можно работать с Hibernate на прямую? Очень просто – этот слой автоматически следит за созданием и закрытием сессий, как следствие – у вас меньше головной боли.
Контроллер обработки формы
Создадим контроллер, который будет обрабатывать нашу форму подписки.
InviteActionController.java
public class InviteActionController extends SimpleFormController {
private InviteSubscribersService subscribersService;
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response,
Object command,
BindException errors) throws Exception {
InviteFormBean form = (InviteFormBean)command;
InviteSubscriber subscriber = new InviteSubscriber();
subscriber.setEmail(form.getEmail());
getSubscribersService().create(subscriber);
request.setAttribute(”afterprocess_message”, “Thank you! You have joined to the AllOfRSS users.”);
return super.onSubmit(request, response, form, errors);
}
public InviteSubscribersService getSubscribersService() {
return subscribersService;
}
public void setSubscribersService(InviteSubscribersService subscribersService) {
this.subscribersService = subscribersService;
}
}
Здесь вообще всё проще пареной репы. Метод onSubmit срабатывает при сабмите формы. Command – это объект формы, преобразуем его в соответсвующий бин и создаём объект для сохранения в БД. Объект errors содержит ошибки, возвращаемые валидатором. Создаём валидатор и не забываем, что он уже прописан у нас в конфиге, а значит больше ни о чём думать не надо, всё будет работать автоматически.
Валидатор
InviteFormValidator.java
public class InviteFormValidator implements Validator {
public boolean supports(Class candidate) {
return InviteFormBean.class.isAssignableFrom(candidate);
}
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, “email”, “required”, “To proccess this action you need to fill your e-mail!”);
InviteFormBean form = (InviteFormBean)obj;
Pattern pattern = Pattern.compile(”.+@.+\\.[a-z]+”);
Matcher matcher = pattern.matcher(form.getEmail());
if (!matcher.matches()) {
errors.rejectValue(”email”, “required”, “Ivalid e-mail address!”);
}
}
}
Осталось создать JSP страничку с формой.
JSP страница
invite.jsp
<%@ page language=”java” contentType=”text/html; charset=utf-8″ pageEncoding=”utf-8″%>
<%@ taglib prefix=”c” uri=”http://java.sun.com/jsp/jstl/core” %>
<%@ taglib prefix=”form” uri=”http://www.springframework.org/tags/form” %>
<%@ taglib prefix=”spring” uri=”http://www.springframework.org/tags” %>
<c:set var=”successmessage” value=”<%= request.getAttribute(”afterprocess_message”)%>”/>
<form:form commandName=”invite”>
<form:input path=”email” id=”email” cssClass=”text”/>
<input type=”submit” value=”?”/>
${successmessage}
<spring:hasBindErrors name=”invite”>
<form:errors path=”email” />
</spring:hasBindErrors>
</form:form>
Я даже не буду здесть ничего объяснять. Просто внимательно посмотрите на форму, а затем на конфигы, созданные в предыдущей статье и у вас не останется ни одного вопроса.
Надеюсь у вас не возникнет проблем с реализацией, а вопросовы пишите в комментарии. С удовольствием отвечу.
Развлекайтесь и больше бывайте на свежем воздухе…
А вот тут не соглашусь с тобой на счет того, что иметь слой служб – это архитектурно правильнее. Все зависит от конкретного приложения. Если у тебя работа с серверное частью, например, идет как напрямую через веб-часть, так и через RPC (Corba или там SOAP/XML-RPC) — тогда слой служб необходим, чтобы не дублировать один и тот же код в разных местах. Если же у тебя всегда одно представление — слой служб будет просто бюрократией.
Да, для приведённого тут примера в принципе не нужны. Мне возможно стоило это опустить, но в принципе в AllOfRss он всё равно присутствует, так-как там будет как-раз тот случай, о котором ты написал.
Здравствуйте, да написано коротко и ясно, но всегда возникает следующий вопрос. Мы рассмотиваем всё на уровне единственной реализации DAO с минимальным (simple) количеством методов, Хотелось бы посмотреть реализацию сразу нескольких DAO, с полным конфигурированием через struts. То есть по всем правилам паттерна DAO, с созданием интерфейсов, имплементацией этих интерфейсов, с использованием TransactionProxyFactoryBean. Если поможете разобраться буду признателен. Спасибо
Да забыл добавить, вы просто не внесли ни одного маппинга доменных объектов, могут возникнуть вопросы. Я бы описал пару объектов, с маппингами. Всё таки мне кажется, что вы не полноценно используете DAO, так как с помощью TransactionProxyFactoryBean можно настроить DAO на любой DataSource, при этом просто реализовав соответствующий интерфейс.
Я постараюсь рассмотреть TransactionProxyFactoryBean в следующих статьях. Однако, ничего кардинально отличающегося от менеджера транзакций описанного тут вы не найдёте. Просто там больше прописывается в конфиге, а здесь это решается с помощью AOP.
По поводу нескольких датасорсов, но ведь и в моей реализации число датасорсов не ограничивается. Просто делаете несколько sessionFactory с разными датасорсами вот и всё… Если я правильно понял
Да я немного прочитал ещё по этой связке, и осознал всю прелесть 1 единственного ApplicationServiceManager-а (можно сделать для удобства интерфейс), в котором сосредоточена вся бизнес-логика (или логика полученя данных). И просто как пропертю передавать этот менеджер в контоллер, далее на страничку. Все сведется к простой проверке правильности выполнения эти сервисов, и переход на соответствующую страничку согласно сценарию. Уровень бизнес-логики становится интуитевно понятнее и ближе к теоретическому описаний его функциональности. Про DataSource вопрос снимаю – разобрался. Буду ждать более глубоких ваших размышлений.
>>методы содержащиеся в классе расчитаны по умолчанию
раcсчитаны
Неплохо было бы “извлечь” интерфейсы у DAO и Service сущностей. Такой подход более привычный для Spring-а.
И, возможно я не прав, но в вашем случае иметь отдельные классы данных для форм и для Hibernate entity не слишком “‘экономное” решение.
А по поводу оформления – код ну очеень трудно читается. Может вот это поможет решить это неудобство http://alexgorbatchev.com/wiki.....ter:Usage.
Не совсем понятно насчет класса com.allofrss.beans.form.InviteFormBean, на который ссылка в allofrss-context.xml. Вы его здесь не описали почему то.
Пишет, что не может найти где объявлено InviteSubscriber в классе InviteSubscriberService.java. Так где все таки оно объявляется? Надо еще какой то класс подключать, или какую то библиотеку я не подключил?