Java + jsp страницы: работа со списками

В рабочем проекте у меня часто используются всякие списки с разными объектами, поэтому мне пришлось на практике изучать, как же с ними работать во фронте, так как там я был несколько слабее, чем в бэке. Своими открытиями делюсь в этой статье: речь пойдёт о сочетании сервлетов и 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

Оставить комментарий