JSTL: Шаблоны для разработки веб-приложений в java. Часть 3

June 24, 2008

Теперь перейдем к рассмотрению методик работы с SQL-запросами в jstl. Скажу честно и прямо, этот подход ужасен (полагаю, вы все в курсе, что смешивать логику и визуализацию глупо). Хотя этими тегами я иногда пользуюсь для маленьких, секретненьких (никогда ни кому не показываемых) проектиков.

Для работы всех показанных далее тегов необходимо подключить к jsp-файлу следующую библиотеку тегов:
  1. <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
Итак, для всех следующих примеров мне понадобится база данных, таблица и записи в ней, сделаем это:
  1. CREATE TABLE `tm_user` (
  2.   `UserID` int(11) NOT NULL AUTO_INCREMENT,
  3.   `UserName` varchar(100) DEFAULT NULL,
  4.   `BirthDate` datetime DEFAULT NULL,
  5.   `Sex` enum('male','female') DEFAULT NULL,
  6.   `Comment` text,
  7.   PRIMARY KEY (`UserID`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8
Теперь заполним ее:
  1. INSERT INTO `tm_user` (username, birthdate, sex, comment) VALUES
  2.   ('Jim Tapkin', '2006.1.1', 'male', 'bla-bla-bla'),
  3.   ('Ron Baskerville', '2002.7.5', 'male', 'be-be-be'),
  4.   ('Lenka Slonova', '2009.4.1', 'female', 'wow-wow-wow')
Доступ к подключению может быть выполнен либо путем использования через JNDI соединения либо внутри самого jstl-файла нужно объявить объект соединения. Сначала первая методика, создаю для tomcat файл Context.xml и кладу его в папку META-INF/context.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Context path="/">
  3.     <Resource name="jdbc/VeloDemoDS" auth="Container" type="javax.sql.DataSource"
  4.               username="jim"
  5.               password=""
  6.               driverClassName="com.mysql.jdbc.Driver"
  7.               url="jdbc:mysql://center/obmachine_t?autoReconnect=true&useUnicode=true&characterEncoding=UTF8"
  8.             />
  9. </Context>
Теперь создаю jstl-код. В нем мне нужно будет первым шагом обратиться к JNDI и взять из нее подключение к описанному выше ресурсу. Затем я должен положить этот объект в какую-нибудь область видимости (пусть это будет pageContext).

Теперь можно попробовать выполнить запрос, для этого используется два тега: либо query либо update. Полагаю, что вы догадались, когда нужно использовать первый из них, а когда – второй. Что касается текста запроса, то его можно указать (как сделал я) непосредственно как тело тега query, либо значением атрибута sql – без разницы.
  1. <%
  2.     InitialContext co = new InitialContext();
  3.     pageContext.setAttribute("ds_jndi", co.lookup("java:comp/env/jdbc/VeloDemoDS"));
  4. %>
  5. <body>
  6.  
  7. <sql:query var="users" dataSource="${ds_jndi}">
  8.     select * from tm_user
  9. </sql:query>
  10.  
  11. <c:forEach items="${users.rows}" var="row">
  12.     <c:out value="${row.UserName}"/>
  13. </c:forEach>
Можно сделать и еще короче: здесь я вообще не обращался сам к JNDI а внутри атрибута dataSource тега непосредственно посылающего запросы к базе данных указал ее JNDI-адрес:
  1. <sql:query var="users"  dataSource="jdbc/VeloDemoDS">
  2.     select * from tm_user
  3. </sql:query>
Если писать в каждом из query JNDI-адрес не слишком удобно (также не хочется вставлять в начале страницы код извлекающий из Context-а JNDI подключение), то можно сделать так:
  1. <sql:setDataSource dataSource="jdbc/VeloDemoDS" var="velo_simple"/>
  2.  
  3. <sql:query var="users" dataSource="${velo_simple}">
  4.     select * from tm_user
  5. </sql:query>
В результате выполнения запроса у меня появилась переменная users. Однако с ней не все так просто: эта переменная играет роль контейнера для еще множества переменных (точнее полей) содержащих детальные сведения о том, что же там было отобрано с сервера.

rows – представляет собой список вида HashMap. В котором роль ключа играет имя поля, а значением является … значение поля.

rowsByIndex – список массивов. Т.е. каждый элемент списка – массив – представляет отдельную запись в таблице. Однако для доступа к полям нужно указать не имя поля, а его порядковый номер (отсчет начинается с 1).

columnNames – список имен полей.

Например, так я распечатаю все имена отобранных полей:
  1. <c:forEach items="${users.columnNames}" var="col">
  2.     <c:out value="${col}"/>
  3. </c:forEach>
rowCount – число равное количеству отобранных строк.

limitedByMaxRows – логическая переменная, признак того, что количество отобранных записей было ограничено, правда, я так и не понял чем ограничено и зачем … (ни явные команды в тексте запроса вида LIMIT “что-там-надо-ограничить”, ни дополнительные атрибуты для тега query не повлияли на эту переменную).

Последнее действия приведенного выше примера тривиально – нужно организовать цикл по списку rows, затем вывести с помощью c:out значения полей.

У тега query еще есть несколько атрибутов: scope – контекст, куда будет помещена переменная с результатом отбора данных; атрибуты maxRows и startRow служат для ограничения выборки начиная с некоторой записи и числом не более чем.

Запросы без переменных (параметров) слишком редки, чтобы разработчики jstl не предусмотрели несколько механизмов позволяющих вам использовать функциональность PreparedStatement-s. Для этого внутрь тега query кроме текста запроса необходимо поместить еще и элементы param. Каждый из них имеет атрибут value со значением, которое будет подставлено в текст запроса вместо вопросика (будьте внимательны с порядком следования параметров).
  1. <sql:query var="users" dataSource="${ds_jndi}">
  2.     select * from tm_user where UserID between ? and ?
  3.     <sql:param value="${param.minID}" />
  4.     <sql:param value="${param.maxID}" />
  5. </sql:query>
Есть подвид тега param – dateParam. Его рекомендуют применять в случаях, когда поле, которое подставляется в текст запроса, имеет тип дата/время:
  1. <sql:query var="users" dataSource="${ds_jndi}">
  2.     select * from tm_user where BirthDate = ?
  3.    <sql:dateParam value="${date}"/>
  4. </sql:query>
Второй вид запросов – на обновление данных - выполняется с помощью тега update, правила его использования полностью идентичны приведенным выше для query (нужно указать источник данных, текст запроса, можно использовать параметры). Вот только в переменную “var” помещается не выборка записей, а количество записей, которые были подвергнуты модификации.
  1. <sql:update var="qtyInsterted" dataSource="${ds_jndi}">
  2.     insert into `tm_user` (username, birthdate, sex, comment) values
  3.       ('Jim Tapkin', '2006.1.1', 'male', 'bla-bla-bla'),
  4.       ('Ron Baskerville', '2002.7.5', 'male', 'be-be-be'),
  5.       ('Lenka Slonova', '2009.4.1', 'female', 'wow-wow-wow')
  6. </sql:update>
  7.  
  8.  
  9. count affected records = <c:out value="${qtyInsterted}" />
Есть в jstl и поддержка работы с транзакциями (вот только управления транзакциями не хватает нашему гипотетическому дизайнеру, но раз есть, значит, будем разбираться). Транзакция представлена тегом “transaction”. Ее атрибутами является источник данных, для которого начата транзакция и уровень ее изоляции (isolation).
  1. <sql:transaction dataSource="${ds_jndi}">
  2.     <sql:update>
  3.         delete from tm_user
  4.     </sql:update>
  5.     <sql:update>
  6.         delee adfs adasd
  7.     </sql:update>
  8. </sql:transaction>
Обратите внимание на то, что внутри транзакции ни один из тегов query или update не содержит атрибута dataSource (что как раз таки очень логично).

Вторая команда внутри транзакции, очевидно, должна привести к ошибке – будет выброшено исключение. Визуально на экране браузера я вижу текст сообщения об ошибке, и выполнение дальнейшего кода jstl было прервано. Надо бы это поправить. Методы обработки исключений в jstl тривиальны. Если исключение произошло не внутри блока catch, то генерация страницы прерывается и с этим поделать ничего нельзя. В случае если потенциально опасные действия были внутри catch, то при генерации исключения объект, описывающий его, будет помещен внутрь переменной заданной с помощью атрибута var. Затем нужно с помощью if-а проверить чему равна эта переменная, и если она не равна null, то ошибка случилась.
  1. <c:catch var="e_sql">
  2.     <sql:transaction dataSource="${ds_jndi}">
  3.         <sql:update>
  4.             delete from tm_user
  5.         </sql:update>
  6.         <sql:update>
  7.             delee adfs adasd
  8.         </sql:update>
  9.  
  10.     </sql:transaction>
  11. </c:catch>
  12.  
  13. <c:if test="${e_sql != null}">
  14.     Произошла страшная ошибка: <c:out value="${e_sql}"/>
  15. </c:if>
Завершу рассказ о работе с базами данных, показав второй способ конфигурирования подключения к СУБД – явно, в коде jstl указать драйвер, учетные данные для доступа:
  1. <sql:setDataSource driver="com.mysql.jdbc.Driver" user="jim" password=""
  2.                    url="jdbc:mysql://center/obmachine_t?autoReconnect=true&useUnicode=true&characterEncoding=UTF8"
  3.                    var="velo_local" scope="application"
  4.         />
Теперь у нас есть объект подключения и его можно использовать точь-в-точь как полученный из jndi (в предыдущих примерах) для выполнения запросов на отбор данных или их модификацию.

Работа с форматирование, локализацией



В jstl входит набор тегов, специально предназначенный для создания локализованных приложений: представление дат, чисел в соответствии с определенными национальными настройками, также есть средства для загрузки текстовых ресурсов из Resource Boundle.

Начнем мы с того, что подключим библиотеку тегов fmt:
  1. <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
Самый часто используемый в этой библиотеке тег requestEncoding – установить значение кодировки входных данных, чтобы веб-сервер корректно произвел их декодирование:
  1. <fmt:requestEncoding value="utf-8" />
Для работы с датами потребуются два тега: formatDate (выполняет преобразование даты в строку) и parseDate (на основании строки конструируется объект Date).

Единственным необходимым атрибутом является value – значение даты, которую необходимо форматировать. Если не указать значение атрибута var, то результат форматирования будет выведен на экран, а не в промежуточную переменную. Атрибут pattern задает строку форматирования.
  1. <jsp:useBean id="beanNow" class="java.util.Date" />
  2. <fmt:formatDate value="${beanNow}" var="s_now" pattern="yyyy.MMM.dd hh:mm:ss" />
  3. <c:out value="${s_now}" />
А вот пример обратного преобразования: из строки в дату:
  1. <fmt:parseDate pattern="dd.MM.yyyy" value="12.04.2001" />
Можно выполнить “тонкий тюнинг” процесса форматирования/парсинга с помощью атрибута timeZone и parseLocal (для тега parseDate). Возможно еще поместить операцию форматирования внутрь тега timeZone, тогда форматирование будет выполняться по определенным правилам:
  1. <fmt:timeZone value="Europe/Minsk">
  2.      <fmt:formatDate value="${beanNow}" pattern="yyyy.MMM.dd hh:mm:ss" />
  3.  </fmt:timeZone>
  4.  
  5. <fmt:timeZone value="Antarctica/Casey">
  6.     <fmt:formatDate value="${beanNow}" pattern="yyyy.MMM.dd hh:mm:ss" />
  7. </fmt:timeZone>
Изменить локаль можно с помощью тега setLocale, например, так (к сожалению тега применяющего локаль на короткое время, подобно, timeZone нет):
  1. <fmt:setLocale value="ja_JP"/>
  2. <fmt:formatDate value="${beanNow}" pattern="yyyy.MMM.dd hh:mm:ss" />
Для работы с числами используются два тега с очень похожими на своих “братцев”: названиями: formatNumber и parseNumber:

Сначала из числа в строку:
  1. <fmt:formatNumber pattern="#,##0.0#" value="234234.23423" />
А теперь наоборот:
  1. <fmt:parseNumber value="234,234.23" pattern="#,##0.0#" />
Для работы с локализованными сообщениями используется два тега в паре: setBundle и message. Первый из них загружает Resource Bundle, второй – выводит сообщения на основании Resource Bundle (обратите внимание, что я при загрузке ресурсов указал название “упаковки сообщений” и именно на это имя, loc, я ссылался при выводе сообщений). У тега message обязательно должен быть указан атрибут key – это имя свойства хранящего сообщение на нужном языке. Если не указан атрибут var, то сообщение будет выведено на экран тут же, в противном случае сообщение будет помещено в промежуточную переменную.
  1. <fmt:setBundle basename="testi.messages" var="loc" />
  2.  
  3. <fmt:message bundle="${loc}" key="MSG_1" />
  4.  
  5. <fmt:message bundle="${loc}" key="MSG_2"  var="m_2"/>
  6. <c:out value="${m_2}" />
Вот вкратце и все теги, которые входят в состав jstl. Однако я продолжаю свой рассказ, т.к. необходимо рассказать еще и об функциях которые можно использовать при записи jstl-выражений. Во всех примерах выше я использовал синтаксис ${что-то-там}. В основном я обращался к переменным, несколько раз делал простые проверки равна переменная чему-то или не равна, проверял может значение переменной null (хотя в примерах и не было это показано, но возможно использовать в EL-выражении и тернарный оператор). Но это еще не все: если я подключу в заголовке файла jsp еще одну библиотеку, то возможности EL становятся шире (хотел, было написать в разы, но все же заменю нейтральным “становятся больше”).
  1. <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
Начнем разбирать функции по порядку. Первая из них “fn:contains” (имена всех функций должны предваряться префиксом “fn:” – именно его мы подключили в начале jsp-файла). Contains служит для проверки того, что внутри строки содержится другая подстрока.
  1. <c:set value="boy goes to school" var="boy" />
  2.  
  3. <c:if test="${fn:contains(boy, 'goes')}">
  4.     <h1>
  5.         He goes to School. Realy.
  6.     </h1>
  7. </c:if>
Если проверить такое условие нужно без учета регистра символов, то используйте функцию containsIgnoreCase:
  1. <c:if test="${fn:containsIgnoreCase(boy, 'GOES')}">
  2.     <h1>
  3.         He goes to School. Realy.
  4.     </h1>
  5. </c:if>
Для проверки того, начинается ли некоторая строка с заданной подстроки, используйте функцию startsWith. Очевидно, что функция endsWith служит для сходной цели, но проверяет то, что строка должна заканчиваться определенными символами:
  1. <c:if test="${fn:startsWith(boy, 'boy')}">
  2.     <h1>
  3.         Starts from BOY
  4.     </h1>
  5. </c:if>
  6.  
  7. <c:if test="${fn:endsWith(boy, 'school')}">
  8.     <h1>
  9.         Ends with SCHOOL
  10.     </h1>
  11. </c:if>
Для экранирования спец.символов некоторой строки перед выводом можно использовать функцию escapeXml (не забывайте, что в старом-добром c:out есть атрибут обеспечивающий сходную функциональность):
  1. <c:set value="boy goes � to school" var="boy" />
  2. <c:out value="${boy}" />
  3. <c:out value="${fn:escapeXml(boy)}" />
  4. <c:out value="${boy}" escapeXml="true" />
Для анализа строки есть целая пачка функций. Начнем с того что найдем в строке позицию, с которой начинается другая строка:
  1. <c:set value="boy goes to school" var="boy" />
  2. <c:out value="${fn:indexOf(boy, 'to' )}" />
Теперь вырежем из исходной строки ее часть, начиная с той позиции где началось слово “to”:
  1. <c:set value="boy goes to school" var="boy" />
  2. <c:out value="${fn:substring(boy, fn:indexOf(boy, 'to' ), 100)}" />
Как видите, мы указываем, откуда вырезать фрагмент, третьим параметром функции выступает число – до какого символа нужно вырезать фрагмент. В следующем примере я вырезаю только два символа из строки:
  1. <c:out value="${fn:substring(boy, fn:indexOf(boy, 'to' ), fn:indexOf(boy, 'to' )+2)}" />
Для того чтобы вырезать фрагмент строки с некоторой позиции до самого конца можно не заморачиваться расчетами длины строки, а указать как последний параметр функции любое отрицательное число:
  1. <c:out value="${fn:substring(boy, fn:indexOf(boy, 'to' ), -1)}" />
Сразу же пока я не забыл, расскажу, как вычислить длину строки. Для этого приходится функция length. Надо сказать что length – универсальная функция и умеет считать длину не только строки, но произвольной коллекции:
  1. <sql:query var="users"  dataSource="jdbc/VeloDemoDS">
  2.     select * from tm_user
  3. </sql:query>
  4.  
  5. <c:set value="boy goes to school" var="boy" />
Вот пример длины строки:
  1. <c:out value="${fn:length(boy)}" />
А так считаем длину списка записей (сколько записей было отобрано из таблицы)
  1. <c:out value="${fn:length(users.rows)}" />
Для работы со строками, когда вам нужно вырезать из них фрагменты наверняка пригодятся две парные функции: substringAfter и substringBefore. Обоим из них нужно указать как параметр строку, из которой вырезается фрагмент и строку-маркер. В первом случае будет вырезано все до заданной подстроки-маркера, во второй случае – все после нее.
  1. <c:set value="boy goes to school" var="boy" />
  2. After:<c:out value="${fn:substringAfter(boy, 'to')}" />
  3. Before:<c:out value="${fn:substringBefore(boy, 'to')}" />
Преобразование в верхний и нижний регистр может быть выполнено с помощью функций toLowerCase и toUpperCase:
  1. <c:set value="boy goes to school" var="boy" />
Делаем маленький регистр:
  1. <c:out value="${fn:toLowerCase(boy)}" />
А теперь большой:
  1. <c:out value="${fn:toUpperCase(boy)}" />
Для удаления символов пробелов размещенных по обоим краям строки используйте функцию trim:
  1. <c:set value="  boy goes to school   " var="boy" />
  2. <h1><c:out value="${fn:trim(boy)}"/></h1>
Для того чтобы в строке заменить некоторый символ на другой, используем функцию replace:
  1. <c:set value="boy goes to school" var="boy" />
  2. <c:out value="${fn:replace(boy, 'school', 'bar')}"/>
Последние функции, про которые я сегодня расскажу: split и join. Они занимаются сходными задачами – преобразованием строки в массив подстрок (split) и обратной операцией склейкой массива строк в одну большую строку. В первом и втором случае нужно указать параметр “разделитель”:
  1. <c:set value="1,2,3,4" var="str_digits" />
  2.  
  3. <c:set value="${fn:split(str_digits, ',')}" var="arr_digits" />
  4. <c:set value="${fn:join(arr_digits, '!')}"  var="str2_digits"/>
Массив подстрок – он, правда, будет неудобочитаемым:
  1. <c:out value="${fn:length(arr_digits)}" />
А теперь обратное преобразование:
  1. <c:out value="${str2_digits}" />

Categories: Java