
Не так давно на работе мне дали задачу, которая привела меня к вопросу: как запросить данные из базы с jsp страницы? Не, как обычно, работать с запросом (request) в сервлете, и напихивать туда данные под видом атрибутов, и лишь потом просить отобразить нужную страницу, а прямо с фронта требовать у сервлета достать нужные данные из базы. Сделать это можно с помощью ajax – слова, которое я слышал раньше (и даже видел фрагменты кода), но не особо много и усиленно с этой штукой работал. Теперь я решил наверстать и поделиться с вами примерами.
Мне нужно было разместить на панели меню счётчик уведомлений, чтобы работники не пропускали выданные им задания. Некоторые коллеги предлагали добавить СМС-уведомления, браузерные всплывающие окошки (которые я у себя никогда не включал и даже понятия не имею, как они работают), но всё это было решено оставить на потом. А начать с простой циферки на верхней панели. Поэтому эта статья идеально подойдёт для новичков. В прочем для тех, кто уже немного знает, как работать с сервлетами и знает, что такое jsp-страницы.
Проблему я осознал, когда уже добавил в Базу таблицу для хранения уведомлений, описал модель, DAO со всеми необходимыми методами. Оказалось, что я понятия не имею, как с фронта без перезагрузки страницы попросить о чём-то бэк. А нужно было это делать, независимо от того, какую страницу хочет открыть пользователь. То есть одним из решений было бы добавление атрибута с количеством уведомлений вообще везде и всюду – работу приложения это не особо замедлило бы, но вот мне бы пришлось перелопатить кучу файлов.
Лучшим решением стало использование ajax — Asynchronous JavaScript and XML. Хотя в последнее время, наверно, уместнее было бы называть его ajaj, потому что XML вытесняется jSON. И для начала мне хотелось протестировать, как вообще всё это работает на примере обычной строки.
Настройка проекта.
Работать буду с этим сервлетом:
package app.servlets.info;
@WebServlet("/notifications/*")
public class Notifications extends HttpServlet {
}
И главное не забыть добавить о нём информацию в файл web.xml:
<servlet>
<servlet-name>notifications</servlet-name>
<servlet-class>app.servlets.info.Notifications</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>notifications</servlet-name>
<url-pattern>/notifications/*</url-pattern>
</servlet-mapping>
Получаем текст из сервлета виде строки String
Итак, теперь, когда сервлет создан, можно побаловаться и повзаимодействовать с ним. Сделаем так, чтобы сервлет возвращал тестовую строку: переопределим метод doGet.
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String text = "test text";
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(text);
}
Тогда по адресу /notifications мы увидим нашу текстовую строку. Но всё же хочется оформить её посимпатичнее и выводить на jsp странице.
Вставляем строку в div в jsp-файле с помощью ajax.
Алгоритм следующий: при вызове /notifications открывается страница. После загрузки страница обращается всё к тому же сервлету и просит у него тестовую строку (это действие будет обозначать с помощью атрибута action = test). И уже тогда выводит её на экран.
Для этого в сервлете появляется проверка: если мы просто открываем страницу, то нам выдаётся наша jsp-страничка. Если же запрашиваем /notifications?action=test, то получаем тестовую строку (можно эти сложности избежать и просто обращаться к двум разным сервлетам, но мне лень создавать несколько файлов).
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (action == null) {
request.getRequestDispatcher(path + "notificationtest.jsp").forward(request, response);
}
if (action!= null && action.equalsIgnoreCase("test")) {
String text = "test text";
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(text);
}
}
Теперь время фронта: как будет выглядеть файл notificationtest.jsp?
<!DOCTYPE html>
<head>
<title>Уведомления (тест)</title>
<script>
$(document).ready(function() {
$.get("notifcations?action=test", function(responseText) {
$("#testdiv").text(responseText);
});
});
</script>
</head>
<body>
<div id="testdiv">Эта строка должна замениться на тестовую строку</div>
</body>
</html>
Разберёмся с этим примером:
$.get – говорит о том, что нам нужен метод doGet.
notifcations?action=test – указывает, какой сервлет нам нужен. После знака вопроса идёт список аргументов. В нашем случае это действие = тест.
И я так оформил адрес просто потому, что мне захотелось, но по-хорошему надо бы параметры запроса указывать вторым аргументов у $.get. На первом месте адрес, на втором — параметры, а потом функция, которая займётся обработкой ответа от сервлета. Альтернатива показана в следующем примере. Результат один и тот же, но этот вариант длиннее, ну и в таком примере нельзя сказать, что сильно нагляднее. Поэтому дальше в статье я так делать не буду 🙂
<script>
$(document).on("click", "#testbutton", function() {
var param = { action : "test" };
$.get("notifications", param, function(responseText) {
$("#testdiv").text(responseText);
});
});
</script>
И в этом примере строка запрашивается сразу после загрузки документа. Но можно сделать ситуацию несколько интереснее и дать пользователю право решать, когда он хочет увидеть тестовую строку. Для этого добавим в документ кнопку, и выведем текст только по её нажатию.
<!DOCTYPE html>
<head>
<title>Уведомления (тест)</title>
<script>
$(document).on("click", "#testbutton", function() {
$.get("notifcations?action=test", function(responseText) {
$("#testdiv").text(responseText);
});
});
</script>
</head>
<body>
<button id="testbutton">Жми</button>
<div id="testdiv">Эта строка должна замениться на тестовую строку</div>
</body>
</html>
Готово! Теперь страница загружается, а при нажатии на кнопку запись в диве заменяется на тестовую строку.
Альтернативным способом написания скрипта будет следующий вариант. Слов тут больше, но он более универсальный и для кого-то будет более наглядным. Прошлый синтаксис лучше использовать для коротких примеров, а этот подойдёт для более сложных задачек, где нужно прикреплять данные или обрабатывать ошибки и т. д
<script>
$(document).on("click", "#testbutton", function() {
var params = {
action: "test",
};
$.ajax
({
type: "GET",
data: params,
url: "/notifications",
success:function(responseText)
{
$("#testdiv").text(responseText);
},
error: function () {
alert("error");
}
});
});
</script>
Это я тоже показываю лишь для примера, чтобы в случае чего вы не пугались таких конструкций и не боялись их самим писать. Но далее в этой статье я буду всё же использовать более компактный и простой первый вариант.
Возвращаем на jsp-страницу данные из базы.
Со строкой всё понятно. Но что делать, если нам нужна информация из базы данных? Фронт остаётся всё тем же, нужно лишь немного поменять бэк. То есть вместо заданной строки брать её из базы. Для взаимодействия с базой у меня уже есть класс NotificationsDAOImpl. Так как в моём случае мне особо интересно количество уведомлений, буду работать с методом countByUser(User user).
public static long countByUser(User receiver) {
Session session = HibernateUtilSQL.getSessionFactory().openSession();
session.beginTransaction();
Query query = session.createQuery("SELECT COUNT(*) " +
"FROM Notifications " +
"WHERE reciever = :receiver");
query.setParameter("receiver", receiver);
long result = (Long)query.uniqueResult();
session.getTransaction().commit();
session.close();
return result;
}
Здесь я прошу Hibernate дать мне количество уведомлений для конкретного юзера receiver. Фокус этой статьи не гибернейт, поэтому разбирать этот метод тут не буду. Просто добавил его для полноты информации. К тому же такой синтаксис устарел, но именно он по какой-то причине до сих пор используется у меня на работе. Надо бы как-нибудь выбрать время и заменить всё устаревшее на новые аналоги…
Небольшие изменения необходимо сделать в нашем сервлете:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (action == null) {
request.getRequestDispatcher(path + "notificationtest.jsp").forward(request, response);
}
if (action!= null && action.equalsIgnoreCase("test")) {
User user = getCurrentUserFromSession();
Long count = NotificationsDAOImpl.countByUser(user);
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(count.toString());
}
}
В этом примере я сначала получаю текущего юзера из HttpSession (этот момент скрыт за методом getCurrentUserFromSession()), так как для работы с программой каждый пользователь должен авторизоваться. Соответственно, мы знаем, кто он такой и можем уже попросить у NotificationsDAOImpl количество уведомлений именно для него.
Как видите, работа с response здесь абсолютно не меняется. На самом-то деле и неважно, что и откуда вы хотите запросить: в вашей программе это могут быть совсем другие данные.
Jsp-файл тоже не меняется (см. прошлый пункт). Нажимаем на кнопку, и сервлет для нас подтягивает данные из базы. Если количество уведомлений в базе поменялось, то при повторном нажатии на кнопку мы получим новое значение. Ловко, не правда ли?
И предлагаю на этом статью прервать, чтобы она не получилась слишком уж длинной. Но продолжение следует: в следующей части займёмся извлечением списка объектов из базы и его обработкой.
1 Comment