Если извлечь строку таблицы в объект, а потом применить метод delete на этот объект, то строка, которую мы извлекали удалиться в базе данных. Таким образом с помощью метода deleteможно удалять одну строку таблицы.
package HibernateApps;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
SessionFactory sessionfactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Book.class)
.buildSessionFactory();
Session session = sessionfactory.getCurrentSession();
try {
//Delete одной строки таблицы с помощью delete
session = sessionfactory.getCurrentSession();
session.beginTransaction();
//Извлекаем строку с ключем 2 в объект Book
Book Hobbitbook = session.get(Book.class, 2);
//С помощью delete удаляем строку с ключем 2
//из таблицы через объект Hobbitbook.
session.delete(Hobbitbook);
//Извлекаем все книги в таблице
List books = session.createQuery(
“from Book”).getResultList();
session.getTransaction().commit();
//выведем их
showBooks(books);
}
catch (Exception e) {
//откатываем изменения в бд
//если при транзакции случилась ошибка
session.getTransaction().rollback();
e.printStackTrace();
}
finally{
//по окончании работы с сессией закрываем ее
session.close();
}
}
private static void showBooks(List books) {
for(Book tempBook : books) {
System.out.println(tempBook.toString());
}
}
}
Давайте запустим нашу программу.
Видим, что в таблице осталась одна строка, строку с ключом 2 мы успешно удалили.
Delete с помощью запроса
Также можно удалять строки таблицы запросом. Благодаря чему мы можем удалять сразу несколько строк таблицы одним запросом.
package HibernateApps;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
SessionFactory sessionfactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Book.class)
.buildSessionFactory();
Session session = sessionfactory.getCurrentSession();
try {
//Delete нескольких строк таблицы запросом.
session = sessionfactory.getCurrentSession();
session.beginTransaction();
//Добавим еще одну книгу в таблицу
session.save(new Book(“LOTR”));
//Удаляем строки с id равными 1 и 3.
//То есть ту которая была в таблице и
//новую добавленную.
session.createQuery(“delete from Book where id=1″
+” or id=3″).executeUpdate();
//Извлечем все книги в таблице
List books = session.createQuery(
“from Book”).getResultList();
session.getTransaction().commit();
//проверим содержимое books
showBooks(books);
}
catch (Exception e) {
//откатываем изменения в бд
//если при транзакции случилась ошибка
session.getTransaction().rollback();
e.printStackTrace();
}
finally{
//по окончанию работы с сессией закрываем ее
session.close();
}
}
private static void showBooks(List books) {
for(Book tempBook : books) {
System.out.println(tempBook.toString());
}
}
}
Давайте запустим нашу программу.
Как видим, результатом select запроса оказался пустой список, раз ничего не вывелось в конце. Значит обе строки таблицы были удалены успешно.
Если мы извлекли из таблицы строку в объект, а потом изменили его с помощью например сеттера, то после коммита транзакции он измениться и в базе.
package HibernateApps;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
SessionFactory sessionfactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Book.class)
.buildSessionFactory();
Session session = sessionfactory.getCurrentSession();
try {
session = sessionfactory.getCurrentSession();
session.beginTransaction();
//извлекаем строку с ключем 2 в объект Book
Book LOTRBook = session.get(Book.class, 2);
//меняем имя книги с помощью сеттера
LOTRBook.setBookName(“Lord Of The Rings”);
//и сразу коммитим. после этого строка
//таблицы с ключем 2 тоже должна измениться
session.getTransaction().commit();
//создаем новую сессию и транзакцию
session = sessionfactory.getCurrentSession();
session.beginTransaction();
//извлекаем еще раз
Book LOTRBook1 = session.get(Book.class, 2);
//выведем извлеченную книгу на консоль
System.out.println(LOTRBook1.toString());
session.getTransaction().commit();
} catch (Exception e) {
//откатываем изменения в бд
//если при транзакции случилась ошибка
session.getTransaction().rollback();
e.printStackTrace();
} finally{
//по окончании работы с сессией закрываем ее
session.close();
}
}
private static void showBooks(List books) {
for(Book tempBook : books) {
System.out.println(tempBook.toString());
}
}
}
Давайте запустим нашу программу.
В последней строке видим название 2 книги изменилось после первого коммита и в базе. То есть мы просто меняем объект, который мы предварительно извлекли из базы и после коммита он меняется и в базе.
Довольно удобно, в sql пришлось бы писать команду update
Update таблицы с помощью запроса
Также можно обновлять строки таблицы запросом. То есть нам, например, может понадобиться обновить несколько строк таблицы и не очень будет удобно доставать эти строки, помещать их в объекты изменять их, а потом коммитить, чтобы они изменились в базе, как в прошлом примере. Проще сделать один запрос.
package HibernateApps;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
SessionFactory sessionfactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Book.class)
.buildSessionFactory();
Session session = sessionfactory.getCurrentSession();
try {
//Update нескольких строк таблицы запросом.
session = sessionfactory.getCurrentSession();
session.beginTransaction();
// устанавливаем значение bookName равным Hobbit
// в строках таблицы где id равен 1 и 2
session.createQuery(“update Book set bookName='” +
“Hobbit’ where id=1 or id=2”).executeUpdate();
//извлечем строки таблицы для проверки
List books3 = session.createQuery(
“from Book”).getResultList();
session.getTransaction().commit();
//проверим содержимое books3
showBooks(books3);
}
catch (Exception e) {
//откатываем изменения в бд
//если при транзакции случилась ошибка
session.getTransaction().rollback();
e.printStackTrace();
}
finally {
//по окончании работы с сессией закрываем ее
session.close();
}
}
private static void showBooks(List books) {
for(Book tempBook : books) {
System.out.println(tempBook.toString());
}
}
}
Давайте запустим нашу программу.
Как видим, обе строки таблицы были изменены успешно с помощью Update запроса.
HQL работает с таблицей через класс, который связан с ней.
То есть в запросе к БД мы указываем не название таблицы, и ее аттрибутов, а название класса и его полей. Таким образом HQL является более объектно-ориентированным
Работа с HQL.
Давайте сделаем пару запросов на языке HQL.
В принципе он очень схож с SQL, поэтому трудностей для его понимания возникать не должно у тех кто знаком с SQL.
package HibernateApps;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
SessionFactory sessionfactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Book.class)
.buildSessionFactory();
Session session = sessionfactory.getCurrentSession();
try {
session = sessionfactory.getCurrentSession();
session.beginTransaction();
// Добавим еще одну книгу в таблицу
session.save(new Book(“LOTR”));
System.out.println(“--------------------”);
// Ниже происходит запрос выборки данных
// из таблицы books через класс Book.
// Вместо select * from books будет просто
// from Book (где Book – имя класса).
List books = session.createQuery(
“from Book”)
.getResultList();
System.out.println(“--------------------”);
// Извлечем теперь две конкретных книжки
// для демонстрации умного ключевого выражения.
// Здесь bookName — поле класса, вместо атрибута
// name таблицы.
// Как видим, язык во многом похож на SQL
List books2 = session.createQuery(
“from Book b where ” +
“b.bookName=’Harry Potter’ ” +
“or b.bookName=’LOTR'”)
.getResultList();
System.out.println(“---------------------“);
session.getTransaction().commit();
// Проверим содержимое списков books и books2,
// в которых хранятся результаты запросов.
// Для этого создана функция showBooks ниже.
// Она просто проходит циклом по List.
showBooks(books);
System.out.println(“---------------------“);
showBooks(books2);
} catch (Exception e) {
// Откатываем изменения в БД,
// если при транзакции случилась ошибка
session.getTransaction().rollback();
e.printStackTrace();
} finally {
// По окончании работы с сессией закрываем ее
session.close();
}
}
private static void showBooks(List books) {
for (Book tempBook : books) {
System.out.println(tempBook.toString());
}
}
}
Проверка работы Hibernate приложения.
Давайте запустим нашу программу.
Как видим, произошло три запроса. Один добавляет книгу в таблицу, Другие два производят выборку книг из таблицы.
Как видим, результаты запросов выборки были успешны. Первый запрос на выборку всех книг выбрал все книги и второй запрос на выборку двух конкретных книг выбрал эти две книги.
Благодаря ORM можно сохранить объект в базу данных.
При сохранении какого-либо объекта в базу данных, объект будет превращен в строку таблицы базы данных.
Значения же полей этого объекта будут значениями аттрибутов этой строки таблицы. То есть поля объектаассоциированы с аттрибутами таблицы, а класс объекта ассоциирован с конкретной таблицей в БД.
То есть если, например, есть класс "Пользователь" с определенными полями – имя, фамилия, возраст и мы хотим поместить объект этого класса в БД, то там должна быть соответствующая этому классу таблица с аттрибутами имя, фамилия, возраст, в которой и будут храниться объекты класса "Пользователь".
Hibernate также помогает сократить много jdbc кода (внутри Hibernate выполняется весь jdbc код, который нам пришлось бы писать) и избавиться от low-level SQL запросов.
Также необходимо скачать файлики dom4j-2.1.3.jar и mysql-connector-j-8.0.32.jar. Их можно скачать на mavenrepository.com.
Далее создаем обычный Java проект с main. Для того чтобы демонстрировать азы Hibernate простого main проекта будет достаточно.
Чтобы создать проект для начала сменим режим нашей ide, для этого переходим Window -> Perspective -> Open Perspective -> Other -> Java.
Теперь создаем проект. Создается он по пути File -> New -> Java Project. Даем имя проекту и нажимаем Finish.
Теперь в созданном проекте создаем папку lib и закидываем в нее все файлы из папки required в архиве Hibernate Orm. Также закидываем туда другие два jar, которые были скачаны отдельно.
Теперь необходимо вручную добавить их в classpath. Для этого кликаем правой кнопкой мыши по проекту и выбираем -> properties -> Java Build Path -> Add Jars и выбираем все jar файлы в папке lib. Теперь можем писать код.
Структура проекта такая:
Как видим, в проект также были добавлены три файлика. Их разберем далее.
Работа с Hibernate
Давайте для начала sql запросом создадим простую таблицу "books" с одним аттрибутом "name". Ну и аттрибутом id само собой.
Мы создали таблицу. Теперь давайте же создадим класс Books, который будет связан с созданной таблицей. Связь происходит с помощью аннотаций. Имя этот класс может иметь любое и поля в нем тоже могут иметь любые имена. Нам главное связать класс с таблицей и поля класса с аттрибутами таблицы.
package HibernateApps;
import javax.persistence.Column;
// Класс, который будет связан с таблицей в БД
// Помечается аннотацией @Entity
@Entity
// Связываем этот класс с таблицей books
// Аннотацией @Table.
// Передаем в нее имя таблицы.
@Table(name = “books”)
public class Book {
// Свяжем поле id класса с ключевым атрибутом
// в таблице.
// Для этого для начала помечаем
// его аннотацией @Id.
@Id
// Аннотацией @GeneratedValue можем указать,
// каким образом будут генерироваться ключи
// в таблице.
// identity – значение по умолчанию.
// Означает, что мы оставляем генерацию значений
// ключей на выбор используемой стратегии.
// То есть БД будет автоматически
// инкрементировать значение ключа
// при вставке новой записи в таблицу.
// Также, например, можем создать собственную
// стратегию генерации и указать здесь,
// но мы оставим identity.
@GeneratedValue(strategy = GenerationType.IDENTITY)
// С помощью Column связываем поля
// класса с соответствующими столбцами таблицы,
// передавая в аннотацию имена этих столбцов.
@Column(name = “id”)
private int id;
@Column(name = “name”)
private String bookName;
public Book() {
}
public Book(String bookName) {
super();
this.bookName = bookName;
}
// Также обязательно создаем геттеры и сеттеры
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
// И переопределим метод toString, чтобы он
// выводил содержимое объекта класса.
// Пригодится.
@Override
public String toString() {
return “Book [id=” + id + “, bookName=” + bookName + “]”;
}
}
Теперь нам нужно настроить подключение Hibernate к maysql и базе данных.
Теперь собственно перейдем к созданию Hibernate программы. В ней мы просто создаем объект класса Books, сохраняем его в таблицу books в виде строки, потом эту же строку, которую мы туда сохранили извлекаем из таблицы в новый объект.
package HibernateApps;
import java.util.List;
public class HibernateApp {
public static void main(String[] args) {
// Объект SessionFactory анализирует конфигурационный
// файл, совершает подключение к БД, анализирует классы
// с аннотациями и создает объекты Session.
// Session объект является оберткой над ОДНИМ
// jdbc подключением к базе, то есть можно создать
// несколько сессий, а значит несколько подключений.
// Этот объект используется для взаимодействия с базой.
// Короткоживущий объект создается для каждой транзакции.
// То есть создали Сессию -> совершили действия с базой
// в пределах транзакции -> закоммитили транзакцию ->
// объект сессии умер.
// Как видим, мы передаем объекту SessionFactory имя файла
// конфигурации и имя класса с аннотациями для связи с БД.
SessionFactory sessionfactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Book.class)
.buildSessionFactory();
Session session = sessionfactory.getCurrentSession();
try {
// Создадим объект класса Book, который
// как мы помним, связан с таблицей books
// поэтому мы можем добавить этот объект
// в эту таблицу как строку таблицы.
Book book = new Book(“Harry Potter”);
// Выведем на консоль id. Он пока ноль
System.out.println(book.toString());
// В Hibernate мы работаем с транзакциями
// точнее с последовательностями запросов,
// которые можно откатить, поэтому начинаем
// транзакцию с помощью beginTransaction.
session.beginTransaction();
// Сохраняем объект в таблицу, связанную
// с его классом с помощью аннотаций.
session.save(book);
// Сохраняем изменения в базе с помощью commit.
session.getTransaction().commit();
// После коммита id объекта book уже будет
// не ноль. После коммита полю объекта book,
// помеченному как ключ аннотацией @Id,
// присваивается автоинкрементированное
// базой значение.
System.out.println(book.toString()); // id уже 1
// Новая сессия создается для каждой новой транзакции
session = sessionfactory.getCurrentSession();
session.beginTransaction();
// С помощью get можем извлечь объект book
// из базы в новый объект.
// Передаем в метод get имя класса объекта,
// который мы извлекаем, и ключ строки извлекаемого объекта
// в таблице.
Book harrypotterbook = session.get(Book.class, book.getId());
session.getTransaction().commit();
// Мы извлекли ранее созданный объект book
// в новый объект harrypotterbook, значит id
// у него тоже будет 1
System.out.println(harrypotterbook.toString());
} catch (Exception e) {
// Откатываем изменения в БД
// если при транзакции случилась ошибка
session.getTransaction().rollback();
e.printStackTrace();
} finally {
// По окончании работы с сессией закрываем ее
session.close();
}
}
}
Давайте запустим нашу программу. Для запуска main приложения в eclipse просто достаточно нажать зеленую кнопку в панели сверху.
Первая строка это первый вывод на консоль в программе с помощью ToString. Как уже было сказано, id у объекта пока ноль.
Далее insert запросом происходит добавление объекта в базу.
Второй вывод на консоль содержимого объекта показывает, что после вызова save над объектом, который мы сохраняли в БД, поле id этого объекта теперь имеет значение. То есть метод saveзаписал в поле id объекта book значение.
Далее происходит запрос на выборку только что добавленной строки в таблицу в новый объект.
В последнем выводе на консоль видим, что мы успешно извлекли из таблицы ее строку в новый объект содержимое, которого мы выводим.
Также давайте добавим две новые странички в проект. Так же как и в прошлом проекте,одна страница с формой, а другая, на которой пользователю будут выводиться значения, которые он ввел
Давайте теперь дополним класс SomeUser. В этом классе создадим два новых поля и сгенерируем геттеры сеттеры для них. Эти новые поля будут связаны со своим текстовым полем в форме.
Содержимое этих полей будет валидироваться в соответствии с указанными аннотациями над этими полями.
То есть пользователь будет вводить значения в текстовые поля, после этого отправлять форму, в результате чего данные текстовых полей будут записываться в связанные с ними поля объекта в аттрибуте, далее содержимое полей в этом объекте помеченных аннотациями валидации будет собственно валидироваться, то есть проверяться соответствует ли то, что ввел пользователь в поля объекта тому, что может в нем быть, и если он ввел то, что в нем не может быть, ему выводиться сообщение о том, что он ввел что-то не так.
Итак разберем аннотации валидации:
package classes;
import java.util.LinkedHashMap;
@Component
public class SomeUser {
// Ниже используются аннотации валидации над полем.
// В нашем приложении текстовое поле формы будет связано
// с полем userName объекта в аттрибуте.
// Если поле помечено @NotNull значит оно будет проверяться
// на пустоту при отправке формы. То есть, если пользователь
// ничего не ввел в текстовое поле, которое связано с полем
// userName и отправил форму, то отправка формы не будет
// успешной, пока пользователь не введет значение
// в текстовое поле.
// message — это сообщение, которое будет
// показано пользователю, если он не ввел ничего в поле.
// С помощью @Size можно указать минимальное количество
// символов, которые пользователь должен ввести
// в текстовое поле.
@NotNull(message = “field is required!!!”)
@Size(min = 5, message = “field is required!!!”)
private String userName;
private String userCountry;
private String userLanguage;
private String[] userBrowser;
// Также форма будет содержать текстовое поле, которое
// будет связано с полем userAge объекта в атрибуте.
// С помощью @Min задаем минимальное число, которое
// можно указать в текстовом поле.
// С помощью @Max задаем максимальное число, которое
// можно указать в текстовом поле.
@Min(value = 0, message = “must be more than 0”)
@Max(value = 100, message = “must be less than 100”)
private Integer userAge;
// Также форма будет содержать текстовое поле, которое
// будет связано с полем userEmail объекта в атрибуте.
// С помощью @Pattern можно задать регулярное выражение,
// по которому будет проверяться содержимое текстового
// поля. Регулярные выражения мы пока не проходили.
// Но если вкратце, то это некоторое правило
// (оно может быть достаточно сложным), по которому
// валидируется содержимое поля.
// В нашем случае мы задали, что текстовое поле должно
// содержать 30 символов и может содержать
// такие-то символы.
@Pattern(regexp = “^[a-zA-Z0-9]{30}”, message = “must be 30 characters”)
private String userEmail;
public SomeUser() { }
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserCountry() {
return userCountry;
}
public void setUserCountry(String userCountry) {
this.userCountry = userCountry;
}
public String getUserLanguage() {
return userLanguage;
}
public void setUserLanguage(String userLanguage) {
this.userLanguage = userLanguage;
}
public String[] getUserBrowser() {
return userBrowser;
}
public void setUserBrowser(String[] userBrowser) {
this.userBrowser = userBrowser;
}
public Integer getUserAge() {
return userAge;
}
public void setUserAge(Integer userAge) {
this.userAge = userAge;
}
public String getUserEmail() {
return userEmail;
}
public void setUserEmail(String userEmail) {
this.userEmail = userEmail;
}
}
Давайте теперь свяжем новые поля в объекте в аттрибуте username c новыми тегами в форме на странице. Также с помощью тега form:errors будут выводиться сообщения, которые мы писали в message в классе у полей. Этот тег пишем там, где должно появляться это сообщение на странице.
Страница с формой:
<%@ page language="java" contentType="text/html;
charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="form"
uri="http://www.springframework.org/tags/form"%>
<%@ page isELIgnored="false" %>
Insert title here
username:
Age:
Email:
Как и в прошлом уроке, создадим два обработчика. Один для перехода на страницу с формой, другой же будет в зависимости от того ввел пользователь валидные данные или нет либо переводить пользователя обратно на страницу с формой, либо перенаправлять его на страницу на которой будет отображено пользователю то, что он вводил в поля.
package classes;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping(“/FirstController”)
public class MVCcontroller {
@RequestMapping(“/FirstJSP”)
public String FirstJSP() {
return “JSPpage”;
}
@RequestMapping(“/SecondJSP”)
public String SecondJSP() {
return “JSPpage1”;
}
@RequestMapping(“/FormPage”)
public String FormPage() {
return “FormPage”;
}
@RequestMapping(“/FormProcessingController”)
public String FormProcessingController(
HttpServletRequest req, Model model) {
model.addAttribute(“resposeMsg”,
(req.getParameter(“username”) + ” entered!”));
return “FormProcessingPage”;
}
@RequestMapping(“/FormTagPage”)
public String FormTagPage(Model model) {
model.addAttribute(“someuser”, new SomeUser());
return “FormTagPage”;
}
@RequestMapping(“/FormTagProcessor”)
public String FormTagProcessor(
@ModelAttribute(“username”) SomeUser someUser) {
System.out.println(someUser.getUserName());
System.out.println(someUser.getUserCountry());
System.out.println(someUser.getUserLanguage());
System.out.println(someUser.getUserBrowser());
return “FormTagProcessingPage”;
}
// Новый обработчик
// он просто переводит на страницу с формой,
// которая будет валидироваться.
@RequestMapping(“/FormValidPage”)
public String FormValidPage(Model model) {
model.addAttribute(“someuser”, new SomeUser());
return “FormValidPage”;
}
// Новый обработчик
@RequestMapping(“/FormValidProcessor”)
// аннотацией @Valid указываем что пришедший атрибут
// из формы в обработчик подлежит валидации и в нем
// будут проверены поля с аннотациями валидации
public String FormValidProcessor(
@Valid @ModelAttribute(“someuser”) SomeUser someUser,
BindingResult mybindingResult) {
// Результат же валидации атрибута
// записывается в mybindingResult.
// проверяем bindingResult на наличие ошибок валидации
if (mybindingResult.hasErrors()) {
// если есть ошибки валидации отправляем уже
// валидированный атрибут обратно в форму где
// ошибка валидации будет отображена тегом form:errors
return “FormValidPage”;
} else {
// если их нет то отправляем атрибут
// на результирующую страницу где из него
// выведем содержимое нужной переменной
return “FormValidProcessingPage”;
}
}
}
На странице на которую перенаправляет пользователя обработчик (если данные в объекте из аттрибута оказались валидными) извлечем выбранные пользователем значения из объекта в аттрибуте username.
Перейдем на страницу с формой через обработчик по пути /FormValidPage. Первое текстовое поле давайте оставим пустым, во втором введем число 144, в третьем введем меньше 30 символов.
и после нажатия Submit:
Видим, что вывелись сообщения, которые мы писали в message в аннотациях о том, что пользователь ввел неверные данные. О том, что пользователь оставил поле пустым, а оно не должно быть пустым, о том, что он ввел число выше ста и о том, что поле должно содержать ровно 30 символов.
То есть очевидно аннотации NotNull, Max и Patternработают.
Давайте теперь проверим аннотации Size и Min. Введем в перовое поле меньше 5 символов, а во второе поле число меньше 0.
После нажатия Submit:
Видим, что сообщения, которые мы писали в аннотациях Size и Minвывелись. Значит, что аннотации работают.
Теперь давайте введем правильные данные, то есть в перове поле больше 5 букв, во второе поле число между 0 и 100, в третье поле ровно 30 символов.
Вот так:
Нажимаем Submit:
Как видим, обработчик успешно перевел нас на вторую страницу и отобразил на ней введенные данные поскольку введенные данные были валидными.
Это были основные аннотации валидации. Есть и другие конечно.
Также можно создавать еще и свои собственные аннотации валидации. Умение их создавать тоже может пригодиться, но в принципе в большинстве случаев свою какую-то сложную валидацию можно задать с помощью регулярных выражений, поэтому не будем заострять внимание.
Помимо тега form:input, рассмотренного в прошлом уроке, есть также и другие теги с которыми мы можем связывать поля объекта в аттрибуте.
В этом уроке рассмотрим теги form:select, form:radio и form:checkbox.
Эти теги аналоги всем известных html тегов, которые предоставляют пользователю варианты выбора.
Напомним как они выглядят:
Добавим в класс объекта, который будет в аттрибуте, новые поля. Эти поля будут связываться с новыми тегами в форме так же, как в прошлом уроке связывался form:input с полем объекта.
package classes;
import java.util.LinkedHashMap;
import org.springframework.stereotype.Component;
@Component
public class SomeUser{
private String userName;
//создадим поле, в которое будет записываться
//то что выбрал пользователь в form:select
private String userCountry;
//в это поле сохр. значение form:radiobutton
private String userLanguage;
//в этот массив сохраняются
//значения form:checkbox. Массив это
//потому что пользователь может выбрать
//несколько значений checkbox.
private String[] userBrowser;
public SomeUser() {
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserCountry() {
return userCountry;
}
public void setUserCountry(String userCountry) {
this.userCountry = userCountry;
}
public String getUserLanguage() {
return userLanguage;
}
public void setUserLanguage(String userLanguage) {
this.userLanguage = userLanguage;
}
public String[] getUserBrowser() {
return userBrowser;
}
public void setUserBrowser(String[] userBrowser) {
this.userBrowser = userBrowser;
}
}
Давайте теперь свяжем новые поля в объекте в аттрибуте username c новыми тегами в форме на странице.
Страница с формой:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ page isELIgnored="false" %>
Insert title here
username:
English
Ukrainian
Russian
Mozilla
Chrome
Edge
Как и в прошлом уроке, во втором обработчике выводятся на консоль значения, которые выбрал пользователь и происходит перенаправление на вторую страницу на которой будут отображены выбранные пользователем значения.
package classes;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping(“/FirstController”)
public class MVCcontroller{
@RequestMapping(“/FirstJSP”)
public String FirstJSP() {
return “JSPpage”;
}
@RequestMapping(“/SecondJSP”)
public String SecondJSP() {
return “JSPpage1”;
}
@RequestMapping(“/FormPage”)
public String FormPage() {
return “FormPage”;
}
@RequestMapping(“/FormProcessingController”)
public String FormProcessingController(
HttpServletRequest req, Model model) {
model.addAttribute(“resposeMsg”,
(req.getParameter(“username”)+” entered!”));
return “FormProcessingPage”;
}
@RequestMapping(“/FormTagPage”)
public String FormTagPage(Model model) {
model.addAttribute(“someuser”,new SomeUser());
return “FormTagPage”;
}
@RequestMapping(“/FormTagProcessor”)
public String FormTagProcessor(
@ModelAttribute(“username”) SomeUser someUser) {
System.out.println(someUser.getUserName());
//теперь выведем и выбранные пользователем
//значения в консоль.
System.out.println(someUser.getUserCountry());
System.out.println(someUser.getUserLanguage());
System.out.println(someUser.getUserBrowser());
//а также они будут отправлены на страницу
//FormTagProcessorPage.jsp и выведены там
return “FormTagProcessingPage”;
}
}
На странице на которую перенаправляет пользователя обработчик извлечем выбранные пользователем значения из объекта в аттрибуте username.
Заметьте, что все браузеры, которые выбрал пользователь хранятся в массиве и чтобы их всех вывести был использован тег библиотеки JSTL, который мы проходили ранее.
В спринг есть специальные теги, которые упрощают нам разработку.
В этом уроке рассмотрим специальный тег form:form.
Тег form:form обеспечивает прямую привязку полей объекта модели вашего приложения к полям ввода HTML-формы.
Давайте создадим простой класс SomeUser с одним полем, геттерами и сеттерами.
package classes;
import org.springframework.stereotype.Component;
@Component
public class SomeUser {
// определим поле, которое в объекте SomeUser
// будет напрямую привязано к текстовому
// полю в форме. и сеттеры геттеры для него
private String userName;
public SomeUser() { }
}
Как видим, пока сеттеров в классе нет. Это было сделано специально, чтобы продемонстрировать один важный прием работы в Eclipse, который нужно знать, поскольку благодаря нему программисту не придется писать большое количество рутинного кода.
Так вот, Eclipse может сам генерировать геттеры и сеттеры для полей в классе. Для этого нужно нажать правой кнопкой мыши в любом месте класса без кода и выбрать Generate Getters and Setters.
Отмечаем поле для которого нужно сгенерировать геттер сеттер и нажимаем generate.
Как видим, магическим образом в классе появились геттер и сеттер для поля userNameи писать вручную ничего не пришлось.
package classes;
import org.springframework.stereotype.Component;
@Component
public class SomeUser {
// определим поле которое в объекте SomeUser
// будет напрямую привязано к текстовому
// полю в форме, и сеттеры геттеры для него
private String userName;
public SomeUser() { }
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
Генерировать можно не только геттеры и сеттеры.
Можно, например, сгенерировать переопределения методов Оbject для данного класса, таких как ToString, equals и hashCode, что очень удобно.
Итак класс, объект которого мы будем создавать и связывать этот объект с формой мы написали. Теперь давайте же создадим этот объект. Создаваться он будет в обработчике и помещаться в аттрибут в Model.
То есть правильно будет сказать, что спринг форма будет напрямую связана с объектом, который находиться в аттрибуте.
Этот обработчик помимо того, что создает аттрибут c объектом SomeUser будет перенаправлять на страницу с формой, где этот объект в аттрибуте будет связываться с формой.
Новый обработчик:
package classes;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping(“/FirstController”)
public class MVCcontroller{
@RequestMapping(“/FirstJSP”)
public String FirstJSP() {
return “JSPpage”;
}
@RequestMapping(“/SecondJSP”)
public String SecondJSP() {
return “JSPpage1”;
}
@RequestMapping(“/FormPage”)
public String FormPage() {
return “FormPage”;
}
@RequestMapping(“/FormProcessingController”)
public String FormProcessingController(
HttpServletRequest req, Model model) {
model.addAttribute(“responseMsg”,
(req.getParameter(“username”)+” entered!”));
return “FormProcessingPage”;
}
// Новый обработчик
@RequestMapping(“/FormTagPage”)
public String FormTagPage(Model model) {
// здесь создаем аттрибут, передавая в него
// экземпляр, с которым будет связана форма
// на странице FormTagPage
model.addAttribute(“someuser”,new SomeUser());
return “FormTagPage”;
}
}
Давайте же свяжем форму на странице на которую переводит только что определенный обработчик /FormTagPageи объект в аттрибуте в модели.
Страница с формой:
<%@ page language="java" contentType="text/html;
charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="form"
uri="http://www.springframework.org/tags/form"%>
<%@ page isELIgnored="false" %>
Insert title here
Теперь давайте создадим этот обработчик, который был прописан в теге action у формы.
м
package classes;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping(“/FirstController”)
public class MVCcontroller{
@RequestMapping(“/FirstJSP”)
public String FirstJSP() {
return “JSPpage”;
}
@RequestMapping(“/SecondJSP”)
public String SecondJSP() {
return “JSPpage1”;
}
@RequestMapping(“/FormPage”)
public String FormPage() {
return “FormPage”;
}
@RequestMapping(“/FormProcessingController”)
public String FormProcessingController(
HttpServletRequest req, Model model) {
model.addAttribute(“responseMsg”,
req.getParameter(“username”)+” entered!”);
return “FormProcessingPage”;
}
@RequestMapping(“/FormTagPage”)
public String FormTagPage(Model model) {
model.addAttribute(“someuser”,new SomeUser());
return “FormTagPage”;
}
//Новый обработчик
@RequestMapping(“/FormTagProcessor”)
//с помощью аннотации @ModelAttribute принимаем атрибут
//посланный формой в этот обработчик и помещаем
//экземпляр из него в новый экземпляр someUser
public String FormTagProcessor(
@ModelAttribute(“username”) SomeUser someUser) {
//Воспользуемся этим объектом. Проверим
//содержится ли в поле userName этого объекта то
//что вводилось в текстовое поле в форме.
//Выведем на консоль томcat содержимое поля
//userName в новом объекте someUser
System.out.println(someUser.getUserName());
//Далее переданынй в тот обработчик объект(аттребут)
//доступен на jsp странице, на которую
//происходит переход из этого обработчика.
//Тоесть на FormTagProcessingPage.
return “FormTagProcessingPage”;
}
}
Теперь извлечем на странице на которую переводит только что определенный обработчик поле объекта в аттрибуте. В этом поле, ясное дело, содержится то, что вбивал в текстовое поле формы пользователь.
м
<%@ page isELIgnored="false" %>
Auth Form
${username.userName}
Проверка работы приложения.
Перейдем на страницу с формой через обработчик по пути /FormTagPage.
Введем в поле текст и нажмем submit.
Видим в консоли Tomcat, что новый объект в обработчике /FormTagProcessor содержит то, что вводил в текстовое поле формы пользователь.
Видим, что обработчик успешно перенаправил на вторую страницу и объект, который содержит то, что писал пользователь в текстовые поля формы был успешно передан этим обработчиком на эту страницу.
этот обработчик принимает эти данные и перенаправляет пользователя на другую страницу, при этом отправляя пользователю некоторые данные на эту страницу.
Для начала, нужно создать страницу формы через которую пользователь будет отправлять данные в обработчик и страницу на которую этот обработчик будет перенаправлять пользователя и на которую этот обработчик будет отправлять данные.
Страница формы очень простая. Она передает параметр username, содержащий введенные данные пользователем в текстовое поле, в обработчик по пути /FormProcessingController имя которого указывается в action. Передача происходит get запросом.
Страница с формой:
Auth Form
Теперь необходимо создать два обработчика. Один нужен просто чтобы перейти на страницу с формой, другой будет принимать данные этой формы и перенаправлять на другую страницу.
package classes;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping(“/FirstController”)
public class MVCcontroller{
@RequestMapping(“/FirstJSP”)
public String FirstJSP() {
return “JSPpage”; //имя jsp
}
@RequestMapping(“/SecondJSP”)
public String SecondJSP() {
return “JSPpage1”; //имя jsp
}
//FormPage обработчик нужен просто чтобы
//перейти на страницу на которой будет форма,
//в которую клиент сможет вводить данные
@RequestMapping(“/FormPage”)
public String FormPage() {
return “FormPage”; //имя формы
}
//Название обработчика ниже как мы помним
//было указано в теге action у формы.
//Этому обработчику будет передаваться параметр
//и он будет перенаправлять на вторую страницу
//на которой будет выводиться переданный
//через форму параметр.
@RequestMapping(“/FormProcessingController”)
//метод-обработчик может иметь разные параметры
//например HttpServletRequest req – это тот самый req
//что и в сервлетах и работать с ним мы можем так же
//как и в сервлетах. Будем извлекать из него параметры.
//а в Model можем передавать данные как
//в response в сервлете. То есть как и в response
//в сервлетах можем положить в model аттрибут, который
//будет доступен на странице на которую этот
//обработчик перенаправляет.
public String FormProcessingController(
HttpServletRequest req, Model model) {
//Ниже всем известный из сервлетов getParameter.
//Получаем имя параметра переданный сюда из формы
//и как в response создаем аттрибут в модели
//значение которого будет принятый параметр
//плюс дополнительная строка entered!.
model.addAttribute(“responseMsg”,
(req.getParameter(“username”)+” entered!”));
//Ниже имя jsp на которую отправляется
//параметр в аттрибуте модели
return “FormProcessingPage”;
}
}
На странице на которую перенаправляет обработчик просто выведем с помощью EL выражения положенный в модель аттрибут. Раньше таким образом мы уже выводили аттрибуты переданные из сервлета в resp.
<%@ page isELIgnored="false" %>
Form
${resposeMsg}
Перейдем на страницу с формой через обработчик по пути /FormPage.
Введем в поле текст и нажмем submit.
Видим, что обработчик успешно перенаправил на вторую страницу и параметр был успешно передан этим обработчиком на эту страницу.
Можно увидеть в адресной строке переданный параметр username=Mike.
Как мы помним, параметр видно в адресной строке, потому что совершался get запрос, но желательно передавать post запрос, чтобы параметра не было видно в адресной строке.
Классов контроллеров в MVC-приложении может быть много. Давайте создадим еще один контроллер – MVController2.
В адресной строке браузера можно указать не только обработчик, к которому должен совершиться переход, но и конкретный классконтроллер, к которому должен совершаться переход.
То есть у нас в проекте сейчас два класса контроллера и в адресной строке можно указать, чтобы производился поиск метода обработчика не во всех контроллерах, которые у нас есть, а только в конкретном.
Для этого RequestMapping нужно прописать над классом контроллером и передать этой аннотации путь к этому классу контроллеру также, как мы писали эту аннотацию над методами обработчиками.
Первый класс контроллер:
package classes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
//Внизу пишем путь к этому классу контроллеру.
//Теперь если клиент захочет
//перейти например к первому обработчику в этом
//контроллере ему нужно будет перейти по адресу:
//http://localhost:8080/SpringMVCap/FirstController/FirstJSP
@RequestMapping(“/FirstController”)
public class MVCcontroller{
@RequestMapping(“/FirstJSP”)
public String FirstJSP() {
return “JSPpage”; //имя jsp
}
@RequestMapping(“/SecondJSP”)
public String SecondJSP() {
return “JSPpage1”; //имя jsp
}
}
Второй класс контроллер:
package classes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
//Внизу пишем путь к этому классу контроллеру.
//Теперь если клиент захочет клиент захочет
//перейти например к первому обработчику в этом
//контроллере ему нужно будет перейти по адресу:
//http://localhost:8080/SpringMVCap/SecondController/FirstJSP
@RequestMapping(“/SecondController”)
public class MVCcontroller2{
@RequestMapping(“/FirstJSP”)
public String FirstJSP() {
return “JSPpage”; //имя jsp
}
@RequestMapping(“/SecondJSP”)
public String SecondJSP() {
return “JSPpage1”; //имя jsp
}
}
Можно увидеть, что методы обработчики в обоих классах имеют одинаковые адреса
То есть видим, что и в первом классе и во втором есть метод помеченный @RequestMapping("/FirstJSP").
Если бы мы не указали над классами RequestMapping, то DispatcherServlet не понял бы какой из методов обработчиков, помеченных путем /FirstJSP вызывать.
Перейдем к FirstJSP через первый контроллер.
Перейдем к FirstJSP через второй контроллер.
Как видим, обе jsp страницы открылись успешно через разные классы контроллеры.
Это набор средств для построения веб-приложений. Подобных тем приложениям, которые мы уже разрабатывали, но в отличии от разработки с помощью сервлетов разрабатывать веб-приложения с помощью Spring MVC намного удобнее и эти все удобства будут рассмотрены далее.
Для понимания того, как работает Spring MVC необходимо вспомнить, что такое паттерн Front Controller.
Помним, что в соответствии с этим паттерном все запросы от клиентов обрабатываются централизованно. То есть все запросы должны в первую очередь приходить на один и тот же обработчик запросов, и далее этот обработчик перенаправляет клиента в другой обработчик, а в какой именно решается исходя из запроса клиента.
В Spring MVC этим центральным обработчиком запросов является специальный сервлет, он называется DispatcherServelet. Этот сервлет есть в спринг библиотеке, которую мы уже добавили в наш проект в прошлом уроке, нам нужно будет лишь настроить его в web.xml.
Если подробнее то Spring MVC работает так:
Все запросы сначала идут на DispatcherServlet (Front Controller).
Model – это то, в чем хранятся данные при передаче в DispatcherServet и из него.
DispatcherServlet исходя из запроса клиента решает в какой другой контроллер перенаправить данные клиента.
В контроллере, в который данные клиента были перенаправлены, эти данные обрабатываются, и новые сформированные данные в этом контроллере сохраняются в Model и передаются в DispatcherServlet.
DispatcherServlet перенаправляет клиента, который послал запрос, на View, название которого также было передано ему из контроллера.
Как видим,все запросы и ответы идут черезFront Controller.
Также, исходя из описания работы выше, и, собственно, из названия "Spring MVC", очевидно, что Spring MVC тоже реализует архитектру MVC, которую мы уже рассматривали ранее.
Давайте же сконфигурируем этот DispatcherServlet в web.xml. Конфигурируется он как самый обычный сервлет. Мы это уже делали раньше.
Давайте теперь добавим в проект некоторые файлы и папки.
В папке classes создадим класс MVCController (имя может быть любым). В этом классе как раз и будут находиться обработчики куда DispatcherServlet перенаправляет запросы.
В папку WEB-INF добавим папку jsp, в которой будут храниться страницы на которые будет перенаправлять клиента DispatcherServlet.
И также добавим, конечно же, applicationContext.xml, в котором определяются бины.
Теперь необходимо сконфигурировать applicationContext.xml. DispatcherServlet будет считывать этот файл и создавать бины.
/WEB-INF/jsp/.jsp
Теперь давайте создадим класс с обработчикамизапросов, которым DispatcherServlet передаёт запросы и которые в ответ возвращают в DispatcherServlet имя страницы, на которую нужно перевести клиента или данные, которые запросил клиент.
package classes;
import java.io.*;
// Помечаем этот класс как контроллер.
// То есть здесь будут находиться обработчики,
// на которые DispatcherServlet будет перенаправлять
// запросы клиентов.
// В applicationContext мы указали, чтобы
// пакет classes анализировался на наличие аннотаций.
// DispatcherServlet будет создавать внутри себя
// особый бин на основе класса, помеченного @Controller,
// который будет передавать запросы пользователя
// и возвращать название страницы
// или объект класса Model с данными.
@Controller
public class MVCcontroller {
// В этом классе можно увидеть два обработчика.
// Если клиент вобьет у себя в браузере адрес
// http://localhost:8080/SpringMVCap/FirstJSP
// то вызовется первый метод в этом классе,
// если http://localhost:8080/SpringMVCap/SecondJSP
// то второй. Очевидно, что в @RequestMapping
// указывается путь, по которому вызывается
// обработчик. Можно вспомнить, что
// путь к обработчику указывается в аннотации
// @WebServlet над классом сервлета.
@RequestMapping(“/FirstJSP”)
// Название метода обработчика может быть любым.
// Возвращать этот метод может Model
// или название какой-то вьюхи.
// Как уже было сказано, это название
// возвращается из этого метода внутри
// DispatcherServlet, и DispatcherServlet ищет
// страницу с таким названием в папке, которая
// ему была указана в applicationContext.xml.
public String FirstJSP() {
// имя jsp, на которую
// переводит этот обработчик
return “JSPpage”;
}
@RequestMapping(“/SecondJSP”)
public String SecondJSP() {
// имя jsp, на которую
// переводит этот обработчик
return “JSPpage1”;
}
// Как видим, для всех этих методов
// есть общая область видимости в пределах класса,
// что очень удобно, то есть можно создать поле
// в классе и использовать его
// в обоих методах-обработчиках.
// Раньше такой общей области видимости не было,
// поскольку каждый сервлет писался
// в отдельном классе.
}
Помним, что мы добавили в папку jsp два файла страницы. Давайте же перейдем по путям созданных обработчиков и проверим открываются ли эти страницы.
Для начала, запускаем сервер и перейдем в браузере по пути /FirstJSP первого обработчика.
Теперь перейдем в браузере по пути /SecondJSP второго обработчика.
Как видим, обе jsp страницы открылись успешно. Значит и обработчики, и DisparcherServlet, и всё остальное работает корректно.