В рабочем проекте у меня часто используются всякие списки с разными объектами, поэтому мне пришлось на практике изучать, как же с ними работать во фронте, так как там я был несколько слабее, чем в бэке. Своими открытиями делюсь в этой статье: речь пойдёт о сочетании сервлетов и jsp страниц.
Список строк.
Начну с самой простой ситуации. Дан список строк (или примитивных типов данных), необходимо по нему пробежаться на jsp странице и каждую строчку вывести на экран.
Пусть сервлет называется ExampleServlet, а jsp-страница – example.jsp.
public class ExampleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<String> list = new ArrayList<>();
list.add("first string");
list.add("second string");
list.add("one more string");
request.setAttribute("listExample", list);
request.getRequestDispatcher( "example.jsp").forward(request, response);
}
}
Итак, здесь всё легко. Создаём список, наполняем его тремя строчками, направляемся на example.jsp. Что происходит там?
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<c:forEach items="${listExample}" var="listValue">
<li>${listValue}</li>
</c:forEach>
</body>
</html>
Чтобы в цикле пробежаться по всем элементам из списка, нам нужен тэг <c:forEach> из библиотеки jstl, которую мы и подключаем в первой строчке. В теге указываем источник в атрибуте items и название переменной для каждого элемента из списка в атрибуте var.
Список объектов
Ситуация усложняется. Теперь в сервлете список наполняется не какими-нибудь строчками, а самыми настоящими объектами. С несколькими полями. Например, подобными:
@Entity
@Table(name = "example_table", schema = "demo")
public class ExampleObject {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name= "name")
private String name;
@ManyToOne
@JoinColumn(name="other_object_id")
private OtherObject otherObject;
}
Из этого класса ExampleObject я выпустил все геттеры, сеттеры и прочие стандартные методы (включая конструкторы), ведь нас сейчас интересуют только названия полей. Их не очень много: есть первичный ключ id типа int, строка name и OtherObject, который в базе связан с этой example_table отношением один-к-многим.
Доставать данные из базы помогает Hibernate. Предположим, что в классе ExampleObjectDAOImpl найдётся метод getAll(), который вполне ожидаемо вернёт список всех объектов из таблицы example_table. То есть получаем на выходе List<ExampleObject>.
В сервлете тогда будет такой код:
public class ExampleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<ExampleObject> list = ExampleObjectDAOImpl.getAll();
request.setAttribute("listExample", list);
request.getRequestDispatcher( "example.jsp").forward(request, response);
response.flushBuffer();
}
}
А в jsp странице мы, наверно, захотим вывести данные в таблицу.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<table>
<thead>
<tr>
<th>id</th>
<th>Имя</th>
<th>id другого объекта</th>
<th>Имя другого объекта</th>
</tr>
</thead>
<tbody>
<c:forEach items="${listExample}" var="listValue">
<tr>
<td>${listValue.id}</td>
<td>${listValue.name}</td>
<td>${listValue.otherObject.id}</td>
<td>${listValue.otherObject.name}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
Доступ к каждому полю каждого объекта из списка осуществляется через точку. Кроме полей самого ExampleObject здесь мы ещё влезаем в OtherObject и достаём оттуда id и name.
Список недообъектов
Не знаю, как нормально описать эту ситуацию, но в рабочем проекте есть не особо приятные моменты со стиранием типов и недообъектами. Предположим, что мы не хотим выводить поля id всё тех же ExampleObject и OtherObject из прошлого примера. Хотим только имена.
getAll() из ExampleObjectDAOImpl будет тогда выглядеть следующим образом:
public static List getAll(int rawId) {
Session session = HibernateUtilSQLMain.getSessionFactory().openSession();
session.beginTransaction();
Query query = session.createQuery("SELECT o.name, o.otherObject.name " +
"FROM ExampleObject o " +
"LEFT JOIN FETCH e.otherObject");
List list = query.list();
session.getTransaction().commit();
session.close();
return list;
}
С помощью SELECT мы запрашиваем лишь определённые поля, и так как соответствующего объекта на случай такой избирательности у нас нет, то стираем все типы и используем олдскульный List. Просто List.
Да, это ужасно и плохо. Да, так код не пишут. Но вместо того, чтобы возмущаться и хмурить брови, лучше посочувствуйте, что мне приходится работать в таких условиях. И да, я собираюсь рефакторить это всё, только пока не знаю, когда. Буду создавать какие-нибудь DTO на этот случай.
Но речь не об этом. Речь о том, что такое вопиющее преступление против чистого кода подкинуло мне проблему: как же тут доставать данные на jsp странице?
Учитывая, что сервлет не изменился (см. предыдущий пункт), во фронте получаем такой код:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<table>
<thead>
<tr>
<th>Имя</th>
<th>Имя другого объекта</th>
</tr>
</thead>
<tbody>
<c:forEach items="${listExample}" var="listValue">
<tr>
<td>${listValue[0]}</td>
<td>${listValue[1]}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
То есть теперь мы обращаемся к полям по индексу, как будто мы получаем элементы массива, а не по имени через точку. Такое же решение подойдёт, например, к запросам с Group By.
Менее вопиющей причиной для такого подхода будет «ручной» сбор списка списков в сервлете или каком-нибудь связанном с ним сервисе. Нужно провести доп расчёты и преобразовать список, полученный напрямую из базы? Нужно собрать данные из файла? В общем, опять нужно без создания класса и соответствующих объектов передать какие-то данные во фронт.
Java и jQuery: как жить без кавычек?
Допустим, вы не хотите выводить список на экран. Потому что надо перебрать данные и что-нибудь с ними сделать. С этим может справиться jQuery, но как в него засунуть список?
Вернёмся к простому случаю. У нас снова есть простенький список строк.
<script>
$(document).ready(function() {
var list = ${listExample};
$.each(list, function( index, value ) {
alert( index + ": " + value );
});
});
</script>
Но такой код не сработает. Java не ставит каждое значение в кавычки, поэтому jQuery не сможет с ним справиться.
Можно конечно всё же вывести все данные с помощью <c:forEach> в скрытую таблицу с display: none, а потом уже из неё доставать всё с jQuery. А можно просто преобразовать список в сервлете в json формат, который во фронте легко поймут.
public class ExampleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<String> list = new ArrayList<>();
list.add("first string");
list.add("second string");
list.add("one more string");
String json = new Gson().toJson(list);
request.setAttribute("listExample", list);
request.getRequestDispatcher( "example.jsp").forward(request, response);
}
}
И всё заработает!
В этом примере я использовал Gson, и чтобы он сработал, то нужно добавить в pom.xml зависимость:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<scope>compile</scope>
</dependency>
Подробнее об этом инструменте можно почитать у разработчиков: github. Там и примеры использования есть. А ещё можно почитать мои статьи про совмещение ajax и java – в принципе связанная с этой статьёй тема.
1 Comment