ЛЕКЦ 12: ВЭБ ПРОГРАМЧЛАЛ (Java Web Programming)
Хичээлийн зорилго: HTTP протокол, Servlet, JSP, MVC загвар, Spring Boot Web (Thymeleaf + REST), CRUD үйлдлүүд, Session/Cookie удирдлага, Form validation, аюулгүй байдал, вэб аппликейшний шилдэг туршлагуудыг эзэмшүүлэх.
Хамрах хүрээ: HTTP Request/Response, Servlet Lifecycle, JSP (Expression Language, JSTL), MVC Pattern, Spring Boot Web, Thymeleaf, Form Handling, CRUD Operations, Session & Cookie, CSRF Protection, Filter & Interceptor, Error Handling, Static Resources, Deployment.
Эх сурвалж: Lectures Web I-III, Lecture 07 (Servlets & JSP), Lecture 08 (Web CRUD), Lecture 09 (State Management & Security), Lecture 11 (Web App Best Practices), Tim Downey — Guide to Web Development with Java.
ХЭСЭГ 1: ОНОЛЫН СУУРЬ (Theory & Foundations)
1.1 HTTP Протокол — Вэбийн суурь
💡 Зүйрлэл: HTTP = Шуудангийн систем. Client (та) = Захидал бичигч. Server = Шуудангийн алба. Request = Захидал. Response = Хариу захидал. URL = Хаяг.
HTTP Request бүтэц:
┌─────────────────────────────────────────┐
│ GET /api/students/1 HTTP/1.1 │ ← Request Line (Method + URL + Version)
│ Host: localhost:8080 │ ← Headers
│ Accept: text/html │
│ Cookie: JSESSIONID=abc123 │
│ │
│ (Body — GET-д ихэвчлэн байхгүй) │ ← Request Body
└─────────────────────────────────────────┘
HTTP Response бүтэц:
┌─────────────────────────────────────────┐
│ HTTP/1.1 200 OK │ ← Status Line
│ Content-Type: text/html; charset=UTF-8 │ ← Headers
│ Set-Cookie: JSESSIONID=abc123 │
│ │
│ <html><body>Hello</body></html> │ ← Response Body
└─────────────────────────────────────────┘
HTTP Method-ууд:
| Method | Зорилго | Idempotent | Body |
|---|---|---|---|
| GET | Өгөгдөл АВАХ (унших) | ✅ Тийм | ❌ Байхгүй |
| POST | Өгөгдөл ҮҮСГЭХ (бичих) | ❌ Үгүй | ✅ Байна |
| PUT | Өгөгдөл бүрэн ШИНЭЧЛЭХ | ✅ Тийм | ✅ Байна |
| DELETE | Өгөгдөл УСТГАХ | ✅ Тийм | ❌ Байхгүй |
| PATCH | Өгөгдөл хэсэгчлэн шинэчлэх | ❌ Үгүй | ✅ Байна |
HTTP Status Code:
| Код | Утга | Жишээ |
|---|---|---|
| 200 | OK — Амжилттай | GET хариу |
| 201 | Created — Үүсгэсэн | POST хариу |
| 301 | Moved Permanently — Шилжсэн | Redirect |
| 302 | Found — Түр шилжүүлэх | Login дараа redirect |
| 400 | Bad Request — Буруу хүсэлт | Validation алдаа |
| 401 | Unauthorized — Нэвтрэлтгүй | Login хэрэгтэй |
| 403 | Forbidden — Эрхгүй | Зөвшөөрөлгүй |
| 404 | Not Found — Олдсонгүй | URL буруу |
| 500 | Internal Server Error — Серверийн алдаа | Exception |
1.2 Servlet — Java Web-ийн суурь
Servlet гэж юу вэ?
Servlet = Java CLASS бөгөөд HTTP хүсэлт хүлээн авч, боловсруулж, хариу буцаана. Java web-ийн хамгийн доод түвшний технологи.
Client (Browser)
│
│ HTTP Request
▼
┌──────────────────┐
│ Web Server │
│ (Tomcat) │
│ │
│ ┌────────────┐ │
│ │ Servlet │ │ ← Java class
│ │ Container │ │
│ └────────────┘ │
│ │
└──────────────────┘
│
│ HTTP Response
▼
Client (Browser)
Servlet Lifecycle (Амьдралын мөчлөг):
1. Loading: Web server → Servlet class АЧААЛАХ
2. init(): Servlet ЭХЛҮҮЛЭХ (нэг удаа)
3. service(): Хүсэлт бүрт → doGet() / doPost() ДУУДАХ (олон удаа)
4. destroy(): Servlet УСТГАХ (нэг удаа)
@WebServlet("/students")
public class StudentServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// Нэг удаа — DB холболт, тохиргоо
System.out.println("Servlet initialized!");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// GET хүсэлт — Оюутнуудын жагсаалт
List<Student> students = studentDAO.findAll();
request.setAttribute("students", students);
request.getRequestDispatcher("/WEB-INF/views/students.jsp")
.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// POST хүсэлт — Шинэ оюутан нэмэх
String name = request.getParameter("name");
String email = request.getParameter("email");
studentDAO.save(new Student(name, email));
response.sendRedirect("/students"); // PRG Pattern
}
@Override
public void destroy() {
// Нэг удаа — Нөөц чөлөөлөх
System.out.println("Servlet destroyed!");
}
}
Servlet тохиргоо:
| Арга | Тайлбар |
|---|---|
| @WebServlet("/path") | Annotation-аар тохируулах (шинэ) |
| web.xml | XML файлаар тохируулах (хуучин) |
<!-- web.xml тохиргоо (хуучин арга) -->
<servlet>
<servlet-name>studentServlet</servlet-name>
<servlet-class>com.example.StudentServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>studentServlet</servlet-name>
<url-pattern>/students</url-pattern>
</servlet-mapping>
1.3 JSP (JavaServer Pages)
JSP гэж юу вэ?
JSP = HTML дотор JAVA КОД бичих технологи. Servlet-ийн VIEW хэсэг. Server дээр HTML болж хөрвөнө.
┌─────────────────────────┐
│ students.jsp │
│ │
│ <html> │
│ <h1>${title}</h1> │ ← Expression Language (EL)
│ <c:forEach ...> │ ← JSTL Tag
│ ${student.name} │
│ </c:forEach> │
│ </html> │
└─────────────────────────┘
│
│ Server дээр хөрвөнө
▼
┌─────────────────────────┐
│ HTML (browser-д) │
│ │
│ <html> │
│ <h1>Оюутнууд</h1> │
│ <p>Бат</p> │
│ <p>Сараа</p> │
│ </html> │
└─────────────────────────┘
JSP Syntax:
| Syntax | Зорилго | Жишээ |
|---|---|---|
<%= ... %> | Expression (утга хэвлэх) | <%= student.getName() %> |
<% ... %> | Scriptlet (Java код) | <% for(...) { } %> |
<%! ... %> | Declaration (field, method) | <%! int count = 0; %> |
${...} | EL (Expression Language) | ${student.name} ← ЗӨВЛӨМЖТЭЙ |
<c:forEach> | JSTL (Tag library) | Давталт, нөхцөл |
JSP + JSTL жишээ:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head><title>Оюутнууд</title></head>
<body>
<h1>Оюутнуудын жагсаалт</h1>
<table border="1">
<tr>
<th>ID</th>
<th>Нэр</th>
<th>Email</th>
<th>Үйлдэл</th>
</tr>
<c:forEach var="student" items="${students}">
<tr>
<td>${student.id}</td>
<td>${student.name}</td>
<td>${student.email}</td>
<td>
<a href="/students/edit?id=${student.id}">Засах</a>
<a href="/students/delete?id=${student.id}">Устгах</a>
</td>
</tr>
</c:forEach>
</table>
<h2>Шинэ оюутан нэмэх</h2>
<form action="/students" method="POST">
<input type="text" name="name" placeholder="Нэр" required>
<input type="email" name="email" placeholder="Email" required>
<button type="submit">Нэмэх</button>
</form>
</body>
</html>
1.4 MVC Pattern — Вэб аппын бүтэц
MVC гэж юу вэ?
┌──────────────────────────────────────────────────────┐
│ MVC Pattern │
│ │
│ ┌─────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ VIEW │◄───│ CONTROLLER │───►│ MODEL │ │
│ │ (JSP/ │ │ (Servlet/ │ │ (Entity/ │ │
│ │Thymeleaf)│ │ Controller) │ │ Service) │ │
│ └─────────┘ └──────┬───────┘ └──────────────┘ │
│ │ │
│ HTML харуулах Хүсэлт удирдах Бизнес логик + DB │
└──────────────────────────────────────────────────────┘
Browser → Controller → Service → Repository → DB
↓
View (HTML)
↓
Browser
| Хэсэг | Үүрэг | Жишээ |
|---|---|---|
| Model | Өгөгдөл + бизнес логик | Student entity, StudentService |
| View | Харуулалт (UI) | JSP, Thymeleaf template |
| Controller | Хүсэлт хүлээн авч, удирдах | Servlet, @Controller |
1.5 Spring Boot Web — Орчин үеийн Java Web
Яагаад Spring Boot?
| Servlet/JSP | Spring Boot Web |
|---|---|
| web.xml тохиргоо | Автомат тохиргоо |
| Tomcat тусад deploy | Embedded Tomcat (JAR дотор) |
| Manual dependency | Spring Starter |
| Servlet extend | @Controller annotation |
| JSP (хуучин) | Thymeleaf (орчин үе) |
Spring Boot Web dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
@Controller vs @RestController:
| Annotation | Буцаах зүйл | Хэрэглээ |
|---|---|---|
| @Controller | VIEW нэр (HTML template) | Вэб хуудас (Thymeleaf) |
| @RestController | JSON/XML (Response body) | REST API |
// @Controller — HTML хуудас буцаана
@Controller
@RequestMapping("/students")
public class StudentController {
@GetMapping
public String list(Model model) {
model.addAttribute("students", studentService.findAll());
return "students/list"; // → templates/students/list.html
}
}
// @RestController — JSON буцаана
@RestController
@RequestMapping("/api/students")
public class StudentApiController {
@GetMapping
public List<StudentResponse> list() {
return studentService.findAll(); // → JSON
}
}
1.6 Thymeleaf — Орчин үеийн Template Engine
Thymeleaf гэж юу вэ?
Thymeleaf = Server-side HTML template engine. JSP-ийн ОРЧИН ҮЕИЙН ОРЛУУЛАГЧ. HTML файлыг browser-д шууд нээж болно (Natural Template).
Thymeleaf vs JSP:
| Шинж | JSP | Thymeleaf |
|---|---|---|
| Файл | .jsp | .html |
| Syntax | <%= %>, <c:forEach> | th:text, th:each |
| Browser | Шууд нээхгүй | ✅ Шууд нээж болно |
| Spring Boot | Тусдаа тохиргоо | Автомат |
| Байршил | /WEB-INF/views/ | /templates/ |
Thymeleaf syntax:
<!-- templates/students/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Оюутнууд</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<h1 th:text="${title}">Оюутнуудын жагсаалт</h1>
<!-- Хүснэгт -->
<table>
<thead>
<tr>
<th>ID</th>
<th>Нэр</th>
<th>Email</th>
<th>GPA</th>
<th>Үйлдэл</th>
</tr>
</thead>
<tbody>
<tr th:each="student : ${students}">
<td th:text="${student.id}">1</td>
<td th:text="${student.name}">Бат</td>
<td th:text="${student.email}">bat@test.com</td>
<td th:text="${student.gpa}">3.5</td>
<td>
<a th:href="@{/students/edit/{id}(id=${student.id})}">Засах</a>
<a th:href="@{/students/delete/{id}(id=${student.id})}"
onclick="return confirm('Устгах уу?')">Устгах</a>
</td>
</tr>
</tbody>
</table>
<!-- Хоосон байвал -->
<p th:if="${#lists.isEmpty(students)}">Оюутан бүртгэгдээгүй байна.</p>
<a th:href="@{/students/new}">+ Шинэ оюутан нэмэх</a>
</body>
</html>
Thymeleaf гол attribute-ууд:
| Attribute | Зорилго | Жишээ |
|---|---|---|
| th:text | Текст харуулах | th:text="${student.name}" |
| th:each | Давталт | th:each="s : ${students}" |
| th:if / th:unless | Нөхцөл | th:if="${students.size() > 0}" |
| th:href | Link үүсгэх | th:href="@{/students/{id}(id=${s.id})}" |
| th:action | Form action | th:action="@{/students}" |
| th:object | Form object bind | th:object="${student}" |
| th:field | Form field bind | th:field="*{name}" |
| th:value | Утга оноох | th:value="${student.name}" |
| th:src | Image src | th:src="@{/images/logo.png}" |
| th:class | CSS class | th:class="${error ? 'error' : ''}" |
| th:fragment | Дахин ашиглах хэсэг | Layout, header, footer |
1.7 CRUD — Web Form ашиглан
Full CRUD Flow:
CREATE: GET /students/new → Form харуулах
POST /students → Хадгалах → Redirect
READ: GET /students → Жагсаалт
GET /students/{id} → Дэлгэрэнгүй
UPDATE: GET /students/edit/{id} → Form + одоогийн утга
POST /students/edit/{id} → Шинэчлэх → Redirect
DELETE: GET /students/delete/{id} → Устгах → Redirect
Controller — Full CRUD:
@Controller
@RequestMapping("/students")
public class StudentController {
private final StudentService studentService;
// 1. READ — Жагсаалт
@GetMapping
public String list(Model model) {
model.addAttribute("students", studentService.findAll());
return "students/list";
}
// 2. CREATE — Form харуулах
@GetMapping("/new")
public String showCreateForm(Model model) {
model.addAttribute("student", new StudentForm());
return "students/form";
}
// 3. CREATE — Хадгалах
@PostMapping
public String create(@Valid @ModelAttribute("student") StudentForm form,
BindingResult result,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "students/form"; // Алдаатай бол form руу буцах
}
studentService.create(form);
redirectAttributes.addFlashAttribute("message", "Амжилттай нэмлээ!");
return "redirect:/students"; // PRG Pattern
}
// 4. UPDATE — Form + одоогийн утга
@GetMapping("/edit/{id}")
public String showEditForm(@PathVariable Long id, Model model) {
StudentForm form = studentService.getFormById(id);
model.addAttribute("student", form);
model.addAttribute("editMode", true);
return "students/form";
}
// 5. UPDATE — Шинэчлэх
@PostMapping("/edit/{id}")
public String update(@PathVariable Long id,
@Valid @ModelAttribute("student") StudentForm form,
BindingResult result,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "students/form";
}
studentService.update(id, form);
redirectAttributes.addFlashAttribute("message", "Амжилттай шинэчиллээ!");
return "redirect:/students";
}
// 6. DELETE — Устгах
@GetMapping("/delete/{id}")
public String delete(@PathVariable Long id, RedirectAttributes redirectAttributes) {
studentService.delete(id);
redirectAttributes.addFlashAttribute("message", "Амжилттай устгалаа!");
return "redirect:/students";
}
}
Form Template (Create + Edit нэгтгэсэн):
<!-- templates/students/form.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Оюутан</title></head>
<body>
<h1 th:text="${editMode} ? 'Оюутан засах' : 'Шинэ оюутан'">Form</h1>
<form th:action="${editMode} ? @{/students/edit/{id}(id=${student.id})} : @{/students}"
th:object="${student}" method="POST">
<div>
<label>Нэр:</label>
<input type="text" th:field="*{name}">
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="error"></span>
</div>
<div>
<label>Email:</label>
<input type="email" th:field="*{email}">
<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="error"></span>
</div>
<div>
<label>GPA:</label>
<input type="number" step="0.1" th:field="*{gpa}">
<span th:if="${#fields.hasErrors('gpa')}" th:errors="*{gpa}" class="error"></span>
</div>
<button type="submit" th:text="${editMode} ? 'Шинэчлэх' : 'Нэмэх'">Submit</button>
<a th:href="@{/students}">Буцах</a>
</form>
</body>
</html>
PRG Pattern (Post-Redirect-Get):
POST /students (form submit)
│
│ Хадгалах
│
▼
302 Redirect → GET /students
│
│ Жагсаалт харуулах
│
▼
200 OK (HTML)
Яагаад PRG?
- Хэрэглэгч F5 (refresh) дарвал → POST давтагдахгүй!
- POST → Redirect → GET = Давхар бичлэг ҮҮСЭХГҮЙ
1.8 Form Validation
StudentForm + Validation:
public class StudentForm {
private Long id;
@NotBlank(message = "Нэр хоосон байж болохгүй")
@Size(min = 2, max = 50, message = "Нэр 2-50 тэмдэгт байна")
private String name;
@NotBlank(message = "Email хоосон байж болохгүй")
@Email(message = "Email формат буруу")
private String email;
@NotNull(message = "GPA хоосон байж болохгүй")
@DecimalMin(value = "0.0", message = "GPA 0.0-аас багагүй")
@DecimalMax(value = "4.0", message = "GPA 4.0-аас ихгүй")
private Double gpa;
// getters, setters
}
Validation алдаа Thymeleaf-д:
<!-- Бүх алдааг нэг дор харуулах -->
<div th:if="${#fields.hasAnyErrors()}" class="alert alert-danger">
<ul>
<li th:each="err : ${#fields.allErrors()}" th:text="${err}"></li>
</ul>
</div>
<!-- Field бүрийн алдаа -->
<input type="text" th:field="*{name}" th:classappend="${#fields.hasErrors('name')} ? 'is-invalid'">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
1.9 Session & Cookie — State Management
HTTP = Stateless → Session/Cookie-аар state удирдах
┌────────┐ ┌────────┐
│ Client │ ── Request + Cookie ───► │ Server │
│ │ ◄── Response + Set-Cookie│ │
│ │ │ │
│ Cookie:│ │Session:│
│ JSESSIONID=abc123 │ abc123:│
│ │ user: "Бат"│
│ │ role: "ADMIN"│
└────────┘ └────────┘
Session vs Cookie:
| Шинж | Session | Cookie |
|---|---|---|
| Хадгалах | SERVER дээр | CLIENT (browser) дээр |
| Хэмжээ | Хязгааргүй | 4KB |
| Аюулгүй | Илүү аюулгүй | XSS-д эмзэг |
| Хугацаа | Timeout (30 мин) | Тохируулах боломжтой |
| Хэрэглээ | Login, cart | Remember me, preferences |
HttpSession ашиглах:
@Controller
public class AuthController {
// Login
@PostMapping("/login")
public String login(@RequestParam String email,
@RequestParam String password,
HttpSession session,
RedirectAttributes redirectAttributes) {
User user = userService.authenticate(email, password);
if (user != null) {
session.setAttribute("currentUser", user);
session.setAttribute("role", user.getRole());
return "redirect:/dashboard";
}
redirectAttributes.addFlashAttribute("error", "Email эсвэл password буруу!");
return "redirect:/login";
}
// Logout
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // Session бүрэн устгах
return "redirect:/login";
}
// Dashboard — Session шалгах
@GetMapping("/dashboard")
public String dashboard(HttpSession session, Model model) {
User user = (User) session.getAttribute("currentUser");
if (user == null) {
return "redirect:/login"; // Нэвтрээгүй бол
}
model.addAttribute("user", user);
return "dashboard";
}
}
Cookie ашиглах:
// Cookie тохируулах
@GetMapping("/set-theme")
public String setTheme(@RequestParam String theme, HttpServletResponse response) {
Cookie cookie = new Cookie("theme", theme);
cookie.setMaxAge(30 * 24 * 60 * 60); // 30 хоног
cookie.setHttpOnly(true); // XSS хамгаалалт
cookie.setSecure(true); // HTTPS only
cookie.setPath("/");
response.addCookie(cookie);
return "redirect:/";
}
// Cookie унших
@GetMapping("/")
public String home(@CookieValue(value = "theme", defaultValue = "light") String theme,
Model model) {
model.addAttribute("theme", theme);
return "home";
}
1.10 Filter & Interceptor — Хүсэлт шүүх
Filter (Servlet түвшин):
@Component
@Order(1)
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
long start = System.currentTimeMillis();
chain.doFilter(request, response); // Дараагийн filter/servlet руу
long duration = System.currentTimeMillis() - start;
System.out.println(req.getMethod() + " " + req.getRequestURI() + " → " + duration + "ms");
}
}
Interceptor (Spring MVC түвшин):
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("currentUser") == null) {
response.sendRedirect("/login");
return false; // Controller руу ДАМЖУУЛАХГҮЙ
}
return true; // Controller руу дамжуулах
}
}
// Interceptor бүртгэх
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/dashboard/**", "/students/**")
.excludePathPatterns("/login", "/register", "/css/**", "/js/**");
}
}
Filter vs Interceptor:
| Шинж | Filter | Interceptor |
|---|---|---|
| Түвшин | Servlet (доод) | Spring MVC (дээд) |
| Хамрах | Бүх хүсэлт (static ч) | Controller хүсэлт |
| Тохиргоо | @Component | WebMvcConfigurer |
| Хэрэглээ | Logging, CORS, encoding | Auth, role шалгалт |
1.11 Thymeleaf Layout — Дахин ашиглах
Fragment ашиглах:
<!-- templates/fragments/header.html -->
<header th:fragment="header">
<nav>
<a th:href="@{/}">Нүүр</a>
<a th:href="@{/students}">Оюутнууд</a>
<span th:if="${session.currentUser != null}">
<span th:text="${session.currentUser.name}">User</span>
<a th:href="@{/logout}">Гарах</a>
</span>
<span th:unless="${session.currentUser != null}">
<a th:href="@{/login}">Нэвтрэх</a>
</span>
</nav>
</header>
<!-- templates/fragments/footer.html -->
<footer th:fragment="footer">
<p>© 2024 Student Management System</p>
</footer>
<!-- templates/students/list.html — Fragment ашиглах -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Оюутнууд</title></head>
<body>
<div th:replace="~{fragments/header :: header}"></div>
<main>
<!-- Амжилтын мэдэгдэл -->
<div th:if="${message}" class="alert alert-success" th:text="${message}"></div>
<h1>Оюутнуудын жагсаалт</h1>
<!-- Хүснэгт -->
</main>
<div th:replace="~{fragments/footer :: footer}"></div>
</body>
</html>
1.12 Static Resources & Error Handling
Static файлууд:
src/main/resources/
├── static/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── app.js
│ └── images/
│ └── logo.png
├── templates/
│ ├── students/
│ │ ├── list.html
│ │ └── form.html
│ ├── fragments/
│ │ ├── header.html
│ │ └── footer.html
│ ├── error/
│ │ ├── 404.html
│ │ └── 500.html
│ └── index.html
└── application.yml
Error page (Custom):
<!-- templates/error/404.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>404 - Олдсонгүй</title></head>
<body>
<h1>404</h1>
<p>Хайсан хуудас олдсонгүй.</p>
<a th:href="@{/}">Нүүр хуудас руу буцах</a>
</body>
</html>
Global Exception Handler:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(StudentNotFoundException.class)
public String handleNotFound(StudentNotFoundException ex, Model model) {
model.addAttribute("error", ex.getMessage());
return "error/404";
}
@ExceptionHandler(Exception.class)
public String handleGeneral(Exception ex, Model model) {
model.addAttribute("error", "Серверийн алдаа гарлаа.");
return "error/500";
}
}
1.13 Вэб аппын шилдэг туршлагууд (Best Practices)
| # | Зарчим | Тайлбар |
|---|---|---|
| 1 | PRG Pattern | POST → Redirect → GET (давхар submit хамгаалалт) |
| 2 | Input Validation | Server-side @Valid + Client-side HTML5 |
| 3 | CSRF Protection | Spring Security CSRF token (form-д) |
| 4 | XSS Prevention | Thymeleaf автомат escape (th:text) |
| 5 | Session Timeout | server.servlet.session.timeout=30m |
| 6 | HttpOnly Cookie | cookie.setHttpOnly(true) |
| 7 | Error Page | Custom 404, 500 хуудас |
| 8 | Fragment/Layout | Header, footer дахин ашиглах |
| 9 | Flash Message | RedirectAttributes амжилт/алдаа мэдэгдэл |
| 10 | Separation | Controller → Service → Repository давхаргалах |
| 11 | Static Resources | CSS, JS, Image → /static/ |
| 12 | Pagination | Их өгөгдлийг хуудаслах (Pageable) |
| 13 | Responsive | Mobile-friendly дизайн (Bootstrap) |
| 14 | Logging | Хүсэлт, алдаа бүрийг лог бичих |
| 15 | HTTPS | Production-д заавал HTTPS |
ХЭСЭГ 2: ТҮЛХҮҮР ҮГ БА МЭРГЭЖЛИЙН НЭР ТОМЬЁО (Keywords & Glossary)
| # | Англи нэр томьёо | Монгол утга | Дэлгэрэнгүй тайлбар |
|---|---|---|---|
| 1 | HTTP | Гипертекст дамжуулах протокол | Client-Server хоорондын хүсэлт/хариу дамжуулах протокол. |
| 2 | Servlet | Серовлет | HTTP хүсэлт хүлээн авч, боловсруулж, хариу буцаах Java class. |
| 3 | JSP | Java серверийн хуудас | HTML дотор Java код бичих template технологи. |
| 4 | Thymeleaf | Таймлиф | Орчин үеийн server-side HTML template engine — Natural template. |
| 5 | MVC | Загвар-Харагдац-Удирдлага | Model-View-Controller — Вэб аппын бүтэц хуваах загвар. |
| 6 | Controller | Удирдагч | HTTP хүсэлт хүлээн авч, Service дуудаж, View буцаах. |
| 7 | Model | Загвар | Өгөгдөл + бизнес логик (Entity, Service). |
| 8 | View | Харагдац | Хэрэглэгчид харуулах UI (HTML template). |
| 9 | CRUD | Үүсгэх-Унших-Шинэчлэх-Устгах | Create, Read, Update, Delete — Өгөгдлийн 4 үндсэн үйлдэл. |
| 10 | Session | Сессия | Server дээр хэрэглэгчийн state хадгалах механизм. |
| 11 | Cookie | Күүки | Client (browser) дээр жижиг өгөгдөл хадгалах механизм. |
| 12 | JSESSIONID | Сессийн ID | Tomcat-ийн session cookie — Хэрэглэгчийг таних. |
| 13 | PRG Pattern | Post-Redirect-Get | POST → 302 Redirect → GET — Давхар submit хамгаалалт. |
| 14 | CSRF | Хуурамч хүсэлт | Cross-Site Request Forgery — Хэрэглэгчийн нэрээр хуурамч хүсэлт. |
| 15 | XSS | Хөндлөн скрипт | Cross-Site Scripting — Хортой JavaScript оруулах халдлага. |
| 16 | Filter | Шүүлтүүр | Servlet түвшинд хүсэлт/хариуг шүүх (logging, encoding). |
| 17 | Interceptor | Тасалдуулагч | Spring MVC түвшинд хүсэлтийг шалгах (auth, role). |
| 18 | @Controller | Контроллер | Spring MVC — HTML view буцаах controller annotation. |
| 19 | @RestController | REST контроллер | JSON/XML буцаах controller — @Controller + @ResponseBody. |
| 20 | @RequestMapping | Хүсэлт зураглалт | URL path-г controller method-д холбох. |
| 21 | @GetMapping | GET зураглалт | HTTP GET хүсэлт хүлээн авах method. |
| 22 | @PostMapping | POST зураглалт | HTTP POST хүсэлт хүлээн авах method. |
| 23 | @PathVariable | Замын хувьсагч | URL-аас утга авах — /students/{id}. |
| 24 | @RequestParam | Хүсэлтийн параметр | Query parameter авах — ?name=Бат. |
| 25 | @ModelAttribute | Загварын атрибут | Form өгөгдлийг Java object-д автомат bind хийх. |
| 26 | @Valid | Баталгаажуулалт | Form object-ийн validation annotation-уудыг шалгах. |
| 27 | BindingResult | Холболтын үр дүн | Validation алдааг хадгалах object. |
| 28 | RedirectAttributes | Чиглүүлэлтийн атрибут | Redirect хийхэд flash message дамжуулах. |
| 29 | th:each | Давталт | Thymeleaf — Collection давталт (for-each). |
| 30 | th:text | Текст | Thymeleaf — Элементийн текстийг тохируулах + XSS escape. |
| 31 | th:field | Талбар | Thymeleaf — Form field-г object property-д bind хийх. |
| 32 | th:fragment | Хэсэг | Thymeleaf — Дахин ашиглах HTML хэсэг (header, footer). |
| 33 | Expression Language | Илэрхийллийн хэл | ${...} — JSP/Thymeleaf-д утга харуулах. |
| 34 | JSTL | JSP стандарт тагийн сан | JSP-д <c:forEach>, <c:if> зэрэг tag-ууд. |
| 35 | Tomcat | Томкат | Apache-ийн Servlet container / Web server. |
| 36 | Web Container | Вэб контейнер | Servlet ажиллуулах орчин (Tomcat, Jetty). |
| 37 | Embedded Server | Суулгасан сервер | Spring Boot-д Tomcat JAR дотор ажиллана. |
| 38 | Static Resource | Статик нөөц | CSS, JS, Image — Server боловсруулахгүй, шууд буцаах. |
| 39 | @ControllerAdvice | Контроллер зөвлөмж | Бүх controller-д хамаарах global exception handler. |
| 40 | Pagination | Хуудаслалт | Их өгөгдлийг хуудас хуудсаар харуулах (Page, Pageable). |
ХЭСЭГ 3: ЛАБОРАТОРИ БА ПРАКТИК ЗААВАР (Labs & Step-by-Step Guide)
3.1 Лабораторийн зорилго
Энэ лабораторид та:
- Spring Boot Web + Thymeleaf төсөл үүсгэх
- Full CRUD (Оюутан) бүтээх
- Form Validation нэмэх
- Session-аар Login/Logout хийх
- Filter/Interceptor-аар Auth шалгах
Хэл: Java 17+ / Spring Boot 3.x | Template: Thymeleaf | DB: H2 (in-memory)
3.2 Лаб 1: Төслийн бүтэц үүсгэх
Алхам 1: Dependencies (pom.xml)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
Алхам 2: application.yml
server:
port: 8080
servlet:
session:
timeout: 30m
spring:
datasource:
url: jdbc:h2:mem:studentdb
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true # http://localhost:8080/h2-console
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
thymeleaf:
cache: false # Хөгжүүлэлтэд cache унтраах
Алхам 3: Entity
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String name;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private Double gpa;
// Constructors
public Student() {}
public Student(String name, String email, Double gpa) {
this.name = name;
this.email = email;
this.gpa = gpa;
}
// Getters & Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Double getGpa() { return gpa; }
public void setGpa(Double gpa) { this.gpa = gpa; }
}
Алхам 4: Repository
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
boolean existsByEmail(String email);
List<Student> findByNameContainingIgnoreCase(String name);
}
3.3 Лаб 2: Service + Form + Controller (Full CRUD)
Алхам 1: StudentForm (Validation)
public class StudentForm {
private Long id;
@NotBlank(message = "Нэр хоосон байж болохгүй")
@Size(min = 2, max = 50, message = "Нэр 2-50 тэмдэгт байх ёстой")
private String name;
@NotBlank(message = "Email хоосон байж болохгүй")
@Email(message = "Email формат буруу байна")
private String email;
@NotNull(message = "GPA оруулна уу")
@DecimalMin(value = "0.0", message = "GPA 0.0-аас багагүй")
@DecimalMax(value = "4.0", message = "GPA 4.0-аас ихгүй")
private Double gpa;
// Getters & Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Double getGpa() { return gpa; }
public void setGpa(Double gpa) { this.gpa = gpa; }
}
Алхам 2: StudentService
@Service
public class StudentService {
private final StudentRepository repository;
public StudentService(StudentRepository repository) {
this.repository = repository;
}
public List<Student> findAll() {
return repository.findAll();
}
public Student findById(Long id) {
return repository.findById(id)
.orElseThrow(() -> new StudentNotFoundException("Оюутан олдсонгүй: ID=" + id));
}
public Student create(StudentForm form) {
Student student = new Student(form.getName(), form.getEmail(), form.getGpa());
return repository.save(student);
}
public Student update(Long id, StudentForm form) {
Student student = findById(id);
student.setName(form.getName());
student.setEmail(form.getEmail());
student.setGpa(form.getGpa());
return repository.save(student);
}
public void delete(Long id) {
Student student = findById(id);
repository.delete(student);
}
public StudentForm toForm(Student student) {
StudentForm form = new StudentForm();
form.setId(student.getId());
form.setName(student.getName());
form.setEmail(student.getEmail());
form.setGpa(student.getGpa());
return form;
}
public List<Student> search(String keyword) {
return repository.findByNameContainingIgnoreCase(keyword);
}
}
Алхам 3: StudentController (Full CRUD)
@Controller
@RequestMapping("/students")
public class StudentController {
private final StudentService studentService;
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
// LIST — Жагсаалт
@GetMapping
public String list(@RequestParam(required = false) String search, Model model) {
List<Student> students;
if (search != null && !search.isBlank()) {
students = studentService.search(search);
model.addAttribute("search", search);
} else {
students = studentService.findAll();
}
model.addAttribute("students", students);
return "students/list";
}
// NEW — Form харуулах
@GetMapping("/new")
public String showCreateForm(Model model) {
model.addAttribute("student", new StudentForm());
model.addAttribute("editMode", false);
return "students/form";
}
// CREATE — Хадгалах
@PostMapping
public String create(@Valid @ModelAttribute("student") StudentForm form,
BindingResult result, Model model,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
model.addAttribute("editMode", false);
return "students/form";
}
studentService.create(form);
redirectAttributes.addFlashAttribute("message", "Оюутан амжилттай нэмэгдлээ!");
return "redirect:/students";
}
// EDIT — Form + одоогийн утга
@GetMapping("/edit/{id}")
public String showEditForm(@PathVariable Long id, Model model) {
Student student = studentService.findById(id);
model.addAttribute("student", studentService.toForm(student));
model.addAttribute("editMode", true);
return "students/form";
}
// UPDATE — Шинэчлэх
@PostMapping("/edit/{id}")
public String update(@PathVariable Long id,
@Valid @ModelAttribute("student") StudentForm form,
BindingResult result, Model model,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
model.addAttribute("editMode", true);
return "students/form";
}
studentService.update(id, form);
redirectAttributes.addFlashAttribute("message", "Оюутан амжилттай шинэчлэгдлээ!");
return "redirect:/students";
}
// DELETE — Устгах
@GetMapping("/delete/{id}")
public String delete(@PathVariable Long id, RedirectAttributes redirectAttributes) {
studentService.delete(id);
redirectAttributes.addFlashAttribute("message", "Оюутан амжилттай устгагдлаа!");
return "redirect:/students";
}
}
3.4 Лаб 3: Thymeleaf Templates
templates/students/list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Оюутнууд</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:hover { background-color: #f5f5f5; }
.alert { padding: 10px; margin: 10px 0; border-radius: 4px; }
.alert-success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.btn { padding: 6px 12px; text-decoration: none; border-radius: 4px; }
.btn-primary { background-color: #007bff; color: white; }
.btn-warning { background-color: #ffc107; color: black; }
.btn-danger { background-color: #dc3545; color: white; }
.search-box { margin: 10px 0; }
</style>
</head>
<body>
<h1>Оюутнуудын жагсаалт</h1>
<!-- Амжилтын мэдэгдэл -->
<div th:if="${message}" class="alert alert-success" th:text="${message}"></div>
<!-- Хайлт -->
<div class="search-box">
<form th:action="@{/students}" method="GET">
<input type="text" name="search" placeholder="Нэрээр хайх..."
th:value="${search}">
<button type="submit" class="btn btn-primary">Хайх</button>
<a th:href="@{/students}" class="btn">Цэвэрлэх</a>
</form>
</div>
<a th:href="@{/students/new}" class="btn btn-primary">+ Шинэ оюутан</a>
<table th:if="${!#lists.isEmpty(students)}" style="margin-top: 10px;">
<thead>
<tr>
<th>ID</th>
<th>Нэр</th>
<th>Email</th>
<th>GPA</th>
<th>Үйлдэл</th>
</tr>
</thead>
<tbody>
<tr th:each="student : ${students}">
<td th:text="${student.id}"></td>
<td th:text="${student.name}"></td>
<td th:text="${student.email}"></td>
<td th:text="${student.gpa}"></td>
<td>
<a th:href="@{/students/edit/{id}(id=${student.id})}" class="btn btn-warning">Засах</a>
<a th:href="@{/students/delete/{id}(id=${student.id})}" class="btn btn-danger"
onclick="return confirm('Устгахдаа итгэлтэй байна уу?')">Устгах</a>
</td>
</tr>
</tbody>
</table>
<p th:if="${#lists.isEmpty(students)}">Оюутан бүртгэгдээгүй байна.</p>
<p style="margin-top: 20px;">Нийт: <strong th:text="${students.size()}">0</strong> оюутан</p>
</body>
</html>
templates/students/form.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${editMode} ? 'Оюутан засах' : 'Шинэ оюутан'">Form</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.form-group { margin: 10px 0; }
label { display: block; font-weight: bold; margin-bottom: 4px; }
input { padding: 8px; width: 300px; border: 1px solid #ccc; border-radius: 4px; }
input.is-invalid { border-color: #dc3545; }
.error { color: #dc3545; font-size: 0.85em; }
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
.btn-primary { background-color: #007bff; color: white; }
.btn-secondary { background-color: #6c757d; color: white; text-decoration: none; padding: 8px 16px; border-radius: 4px; }
</style>
</head>
<body>
<h1 th:text="${editMode} ? 'Оюутан засах' : 'Шинэ оюутан нэмэх'">Form</h1>
<form th:action="${editMode} ? @{/students/edit/{id}(id=${student.id})} : @{/students}"
th:object="${student}" method="POST">
<div class="form-group">
<label for="name">Нэр:</label>
<input type="text" id="name" th:field="*{name}"
th:classappend="${#fields.hasErrors('name')} ? 'is-invalid'">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="error"></div>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" th:field="*{email}"
th:classappend="${#fields.hasErrors('email')} ? 'is-invalid'">
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="error"></div>
</div>
<div class="form-group">
<label for="gpa">GPA:</label>
<input type="number" step="0.1" id="gpa" th:field="*{gpa}"
th:classappend="${#fields.hasErrors('gpa')} ? 'is-invalid'">
<div th:if="${#fields.hasErrors('gpa')}" th:errors="*{gpa}" class="error"></div>
</div>
<div class="form-group" style="margin-top: 15px;">
<button type="submit" class="btn btn-primary"
th:text="${editMode} ? 'Шинэчлэх' : 'Нэмэх'">Submit</button>
<a th:href="@{/students}" class="btn-secondary">Буцах</a>
</div>
</form>
</body>
</html>
3.5 Лаб 4: Session Login/Logout
User Entity + Repository
@Entity
@Table(name = "users")
public class AppUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String role; // ADMIN, USER
// Constructors, Getters, Setters
public AppUser() {}
public AppUser(String email, String password, String name, String role) {
this.email = email;
this.password = password;
this.name = name;
this.role = role;
}
public Long getId() { return id; }
public String getEmail() { return email; }
public String getPassword() { return password; }
public String getName() { return name; }
public String getRole() { return role; }
}
@Repository
public interface UserRepository extends JpaRepository<AppUser, Long> {
Optional<AppUser> findByEmail(String email);
}
AuthController
@Controller
public class AuthController {
private final UserRepository userRepository;
public AuthController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/login")
public String loginPage() {
return "auth/login";
}
@PostMapping("/login")
public String login(@RequestParam String email,
@RequestParam String password,
HttpSession session,
RedirectAttributes redirectAttributes) {
Optional<AppUser> userOpt = userRepository.findByEmail(email);
if (userOpt.isPresent() && userOpt.get().getPassword().equals(password)) {
AppUser user = userOpt.get();
session.setAttribute("currentUser", user);
return "redirect:/students";
}
redirectAttributes.addFlashAttribute("error", "Email эсвэл нууц үг буруу!");
return "redirect:/login";
}
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/login";
}
}
templates/auth/login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Нэвтрэх</title>
<style>
body { font-family: Arial; display: flex; justify-content: center; margin-top: 100px; }
.login-box { width: 350px; padding: 30px; border: 1px solid #ddd; border-radius: 8px; }
input { width: 100%; padding: 10px; margin: 8px 0; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
button { width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.error { color: #dc3545; margin-bottom: 10px; }
</style>
</head>
<body>
<div class="login-box">
<h2>Нэвтрэх</h2>
<div th:if="${error}" class="error" th:text="${error}"></div>
<form th:action="@{/login}" method="POST">
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Нууц үг" required>
<button type="submit">Нэвтрэх</button>
</form>
</div>
</body>
</html>
3.6 Лаб 5: Auth Interceptor + Exception Handler
AuthInterceptor
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("currentUser") == null) {
response.sendRedirect("/login");
return false;
}
return true;
}
}
WebConfig — Interceptor бүртгэх
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
public WebConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/logout", "/css/**", "/js/**", "/h2-console/**");
}
}
StudentNotFoundException + Handler
public class StudentNotFoundException extends RuntimeException {
public StudentNotFoundException(String message) {
super(message);
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(StudentNotFoundException.class)
public String handleNotFound(StudentNotFoundException ex, Model model) {
model.addAttribute("error", ex.getMessage());
return "error/404";
}
@ExceptionHandler(Exception.class)
public String handleGeneral(Exception ex, Model model) {
model.addAttribute("error", "Серверийн алдаа: " + ex.getMessage());
return "error/500";
}
}
DataInitializer — Анхны өгөгдөл
@Component
public class DataInitializer implements CommandLineRunner {
private final UserRepository userRepository;
private final StudentRepository studentRepository;
public DataInitializer(UserRepository userRepository, StudentRepository studentRepository) {
this.userRepository = userRepository;
this.studentRepository = studentRepository;
}
@Override
public void run(String... args) {
// Хэрэглэгч
userRepository.save(new AppUser("admin@test.com", "admin123", "Админ", "ADMIN"));
userRepository.save(new AppUser("user@test.com", "user123", "Хэрэглэгч", "USER"));
// Оюутнууд
studentRepository.save(new Student("Бат", "bat@test.com", 3.5));
studentRepository.save(new Student("Сараа", "saraa@test.com", 3.8));
studentRepository.save(new Student("Дорж", "dorj@test.com", 2.9));
}
}
Тест хийх:
# Апп ажиллуулах
./mvnw spring-boot:run
# Browser-д нээх
# http://localhost:8080/login
# Email: admin@test.com, Password: admin123
# Оюутнуудын жагсаалт
# http://localhost:8080/students
# CRUD тест:
# + Шинэ оюутан → Нэмэх → Жагсаалтад харагдана
# Засах → Утга өөрчлөх → Шинэчлэгдэнэ
# Устгах → Confirm → Устгагдана
# Хайх → Нэрээр хайна
# H2 Console
# http://localhost:8080/h2-console
# JDBC URL: jdbc:h2:mem:studentdb
ХЭСЭГ 4: ШАЛГАЛТЫН АСУУЛТ (Knowledge Check — 100 тест)
Тест 1
HTTP протоколын гол шинж юу вэ?
- A) Stateful
- B) STATELESS — Хүсэлт бүр бие даасан, server өмнөх хүсэлтийг санахгүй
- C) Persistent
- D) Encrypted
Зөв хариулт: B
Тайлбар: HTTP = Stateless. Server хүсэлт бүрийг ШИНЭ гэж үзнэ. Өмнө хэн нэвтэрснийг МЭДЭХГҮЙ. Session/Cookie = State нэмэх механизм.
Тест 2
HTTP GET method юу хийдэг вэ?
- A) Өгөгдөл үүсгэх
- B) Өгөгдөл АВАХ (унших) — URL-д parameter, body БАЙХГҮЙ
- C) Өгөгдөл устгах
- D) Өгөгдөл шинэчлэх
Зөв хариулт: B
Тайлбар: GET = Read.
GET /students= Бүх оюутан.GET /students/1= ID=1 оюутан. Idempotent = Хэдэн ч удаа дуудсан ижил хариу. Body БАЙХГҮЙ.
Тест 3
HTTP POST method юу хийдэг вэ?
- A) Өгөгдөл авах
- B) Өгөгдөл ҮҮСГЭХ — Request body-д өгөгдөл, idempotent БИШ
- C) Өгөгдөл устгах
- D) Redirect
Зөв хариулт: B
Тайлбар: POST = Create. Form submit → POST /students → Шинэ оюутан нэмэх. Body-д нэр, email, GPA. NOT idempotent = 2 удаа дарвал 2 оюутан үүснэ (PRG шийднэ).
Тест 4
HTTP 404 status code юу вэ?
- A) Амжилттай
- B) NOT FOUND — Хүсэлтийн URL/нөөц олдсонгүй
- C) Server алдаа
- D) Redirect
Зөв хариулт: B
Тайлбар:
/students/999→ Student ID=999 байхгүй → 404 Not Found. URL буруу бичсэн ч 404. Custom 404 хуудас тохируулах = templates/error/404.html.
Тест 5
Servlet гэж юу вэ?
- A) HTML файл
- B) HTTP хүсэлт хүлээн авч, боловсруулж, ХАРИУ БУЦААХ Java class
- C) CSS файл
- D) Database
Зөв хариулт: B
Тайлбар: Servlet = Java web-ийн суурь.
doGet()= GET хүсэлт.doPost()= POST хүсэлт. Web container (Tomcat) → Servlet ажиллуулна. Spring Controller = Servlet-ийн дээд түвшний хялбарчлал.
Тест 6
Servlet Lifecycle-ийн зөв дараалал аль вэ?
- A) service → init → destroy
- B) INIT → SERVICE → DESTROY (Эхлүүлэх → Хүсэлт боловсруулах → Устгах)
- C) destroy → init → service
- D) service → destroy → init
Зөв хариулт: B
Тайлбар: init() = НЭГ удаа (Servlet ачаалахад). service() = Хүсэлт бүрт (doGet/doPost). destroy() = НЭГ удаа (Servlet устгахад). init → олон service → destroy.
Тест 7
JSP гэж юу вэ?
- A) JavaScript
- B) HTML дотор JAVA КОД бичих template технологи — Server дээр HTML болж хөрвөнө
- C) JSON
- D) CSS
Зөв хариулт: B
Тайлбар: JSP = JavaServer Pages.
${student.name}= EL (Expression Language).<c:forEach>= JSTL. Server дээр Java → HTML болж хөрвөнө → Browser-д HTML ирнэ.
Тест 8
Thymeleaf JSP-ээс юугаараа давуу вэ?
- A) Хурд бага
- B) NATURAL TEMPLATE — HTML файлыг browser-д шууд нээж болно, Spring Boot-д автомат
- C) Хуучин
- D) XML syntax
Зөв хариулт: B
Тайлбар: JSP = .jsp файл → Browser-д шууд нээхгүй. Thymeleaf = .html файл → Browser-д шууд нээж UI харна (th: attribute-ууд ignore). Spring Boot = Автомат тохиргоо.
Тест 9
MVC Pattern-ийн 3 хэсэг юу вэ?
- A) HTML, CSS, JS
- B) MODEL (өгөгдөл), VIEW (харагдац), CONTROLLER (удирдлага)
- C) DB, API, UI
- D) Client, Server, Network
Зөв хариулт: B
Тайлбар: Model = Student entity + Service (бизнес логик). View = Thymeleaf template (HTML). Controller = @Controller (хүсэлт хүлээж, service дуудаж, view буцаах).
Тест 10
@Controller vs @RestController-ийн ялгаа юу вэ?
- A) Ялгаагүй
- B) @Controller = VIEW НЭР буцаана (HTML), @RestController = JSON/XML буцаана
- C) @Controller = JSON
- D) @RestController = HTML
Зөв хариулт: B
Тайлбар:
@Controller→return "students/list"→ Thymeleaf template.@RestController→return studentList→ JSON. @RestController = @Controller + @ResponseBody.
Тест 11
th:each юу хийдэг вэ?
- A) Нөхцөл
- B) Collection (list)-г ДАВТАЖ, элемент бүрийг HTML-д харуулах
- C) Link үүсгэх
- D) Form bind
Зөв хариулт: B
Тайлбар:
th:each="student : ${students}"= students list-ийн элемент бүрийг "student" нэрээр давтана.<tr>бүрт нэг оюутан. Java-ийн for-each-тай ижил.
Тест 12
th:text="${student.name}" юу хийдэг вэ?
- A) Link
- B) Элементийн текстийг student.name утгаар ТОХИРУУЛЖ, XSS-аас ХАМГААЛАХ (escape)
- C) Form bind
- D) CSS class
Зөв хариулт: B
Тайлбар:
<td th:text="${student.name}">Бат</td>→ "Бат" гэсэн текст харагдана. Хэрэв name =<script>alert('xss')</script>→ Escape хийж, текст болгоно. XSS хамгаалалт!
Тест 13
th:field="*{name}" юу хийдэг вэ?
- A) Текст харуулах
- B) Form INPUT-г Java object-ийн PROPERTY-д автомат BIND хийх
- C) Link
- D) CSS
Зөв хариулт: B
Тайлбар:
th:object="${student}"+th:field="*{name}"= student.name ↔ input. Form submit → StudentForm.name-д утга автомат орно. Edit үед одоогийн утга input-д харагдана.
Тест 14
PRG Pattern гэж юу вэ?
- A) Programming pattern
- B) POST → REDIRECT → GET — Form submit дараа REDIRECT хийж, F5 давхар submit хамгаалах
- C) REST pattern
- D) DB pattern
Зөв хариулт: B
Тайлбар: POST /students → Student save →
return "redirect:/students"→ 302 → GET /students. F5 дарвал GET давтагдана (POST биш!). Давхар бичлэг ҮҮСЭХГҮЙ.
Тест 15
@Valid annotation юу хийдэг вэ?
- A) Шаардлагагүй
- B) @ModelAttribute object-ийн VALIDATION ANNOTATION-уудыг (@NotBlank, @Email) ШАЛГАХ
- C) DB save
- D) Redirect
Зөв хариулт: B
Тайлбар:
@Valid @ModelAttribute("student") StudentForm form→ form-ийн @NotBlank, @Email, @Size бүгдийг шалгана. Алдаа → BindingResult.hasErrors() = true.
Тест 16
BindingResult юу хийдэг вэ?
- A) DB bind
- B) @Valid VALIDATION-ийн АЛДААГ хадгалах object — hasErrors() = true бол form руу буцах
- C) Session bind
- D) Cookie bind
Зөв хариулт: B
Тайлбар:
if (result.hasErrors()) { return "students/form"; }= Алдаатай бол form руу буцааж, алдааг харуулна.th:errors="*{name}"= name field-ийн алдааны мэдэгдэл.
Тест 17
Session гэж юу вэ?
- A) Client дээр хадгалах
- B) SERVER дээр хэрэглэгчийн STATE хадгалах механизм — Login, cart
- C) Database
- D) File
Зөв хариулт: B
Тайлбар:
session.setAttribute("currentUser", user)→ Server-д хадгалагдана. JSESSIONID cookie → Client-д. Дараагийн хүсэлтэд JSESSIONID → Server → Session олно.
Тест 18
Cookie гэж юу вэ?
- A) Server дээр
- B) CLIENT (browser) дээр жижиг өгөгдөл хадгалах — 4KB хязгаартай
- C) Database
- D) File
Зөв хариулт: B
Тайлбар:
Set-Cookie: theme=dark; Max-Age=2592000; HttpOnly; Secure. Browser хадгалж, дараагийн хүсэлт бүрт серверт илгээнэ. Remember me, preferences, tracking.
Тест 19
Session vs Cookie-ийн ялгаа юу вэ?
- A) Ялгаагүй
- B) Session = SERVER дээр (аюулгүй), Cookie = CLIENT дээр (4KB, XSS-д эмзэг)
- C) Session = Client
- D) Cookie = Server
Зөв хариулт: B
Тайлбар: Login мэдээлэл → Session (server, аюулгүй). Theme сонголт → Cookie (client, хөнгөн). Session ID = Cookie-р дамжина (JSESSIONID).
Тест 20
session.invalidate() юу хийдэг вэ?
- A) Session үүсгэх
- B) Session-г БҮРЭН УСТГАХ — Бүх attribute арилна (Logout)
- C) Session шинэчлэх
- D) Cookie устгах
Зөв хариулт: B
Тайлбар: Logout →
session.invalidate()→ currentUser, role бүгд УСТНА. Дараагийн хүсэлтэд session байхгүй → Interceptor → Login руу redirect.
Тест 21
Filter гэж юу вэ?
- A) Controller
- B) SERVLET түвшинд хүсэлт/хариуг ШҮҮХ — Logging, encoding, CORS
- C) View
- D) Service
Зөв хариулт: B
Тайлбар: Filter = Servlet-ийн өмнө/дараа ажиллана.
chain.doFilter()= Дараагийн filter/servlet руу дамжуулах. Бүх хүсэлтэд (static файл ч) ажиллана.
Тест 22
Interceptor гэж юу вэ?
- A) Filter
- B) SPRING MVC түвшинд Controller-ийн өмнө/дараа хүсэлтийг ШАЛГАХ — Auth, role
- C) Servlet
- D) JSP
Зөв хариулт: B
Тайлбар:
preHandle()= Controller-ийн ӨМНӨ (auth шалгах).postHandle()= Controller-ийн ДАРАА.afterCompletion()= View render-ийн дараа. Controller хүсэлтэд Л ажиллана.
Тест 23
Filter vs Interceptor-ийн ялгаа юу вэ?
- A) Ялгаагүй
- B) Filter = SERVLET түвшин (бүх хүсэлт), Interceptor = SPRING MVC (Controller хүсэлт)
- C) Filter = Spring
- D) Interceptor = Servlet
Зөв хариулт: B
Тайлбар: Filter: /css/style.css хүсэлтэд ч ажиллана. Interceptor: excludePathPatterns("/css/**") = CSS хүсэлтэд ажиллахгүй. Auth → Interceptor (controller-д хэрэгтэй).
Тест 24
@ControllerAdvice юу хийдэг вэ?
- A) Controller нэмэх
- B) БҮХ controller-д хамаарах GLOBAL exception handler, model attribute
- C) View нэмэх
- D) Service нэмэх
Зөв хариулт: B
Тайлбар:
@ControllerAdvice+@ExceptionHandler(StudentNotFoundException.class)= Аль ч controller-д StudentNotFoundException гарвал → error/404 view. Global = Бүх controller.
Тест 25
RedirectAttributes.addFlashAttribute() юу хийдэг вэ?
- A) URL parameter нэмэх
- B) Redirect хийхэд НЭГ УДАА харуулах МЭДЭГДЭЛ дамжуулах (flash message)
- C) Session нэмэх
- D) Cookie нэмэх
Зөв хариулт: B
Тайлбар:
redirectAttributes.addFlashAttribute("message", "Амжилттай!")→ POST → Redirect → GET хуудас дээр "Амжилттай!" мэдэгдэл → F5 дарвал АРИЛНА (нэг удаа).
Тест 26
@PathVariable юу хийдэг вэ?
- A) Query parameter
- B) URL PATH-аас утга авах —
/students/{id}→ id = 5 - C) Form data
- D) Header
Зөв хариулт: B
Тайлбар:
@GetMapping("/students/{id}")+@PathVariable Long id=/students/5→ id = 5. URL-д тодорхой нөөцийг зааж өгнө. RESTful URL-ийн суурь.
Тест 27
@RequestParam юу хийдэг вэ?
- A) Path variable
- B) URL QUERY PARAMETER-аас утга авах —
?search=Бат→ search = "Бат" - C) Form data
- D) Header
Зөв хариулт: B
Тайлбар:
@RequestParam(required=false) String search=/students?search=Бат→ search = "Бат".required=false= Parameter байхгүй ч OK (null). Хайлт, шүүлтүүрт.
Тест 28
@ModelAttribute юу хийдэг вэ?
- A) JSON parse
- B) HTTP FORM өгөгдлийг JAVA OBJECT-д автомат BIND хийх
- C) File upload
- D) Session
Зөв хариулт: B
Тайлбар: Form: name=Бат, email=bat@test.com →
@ModelAttribute("student") StudentForm form→ form.getName() = "Бат", form.getEmail() = "bat@test.com". Автомат binding.
Тест 29
Thymeleaf @{...} syntax юу вэ?
- A) Текст
- B) URL EXPRESSION — Link/path үүсгэх, context path автомат нэмэх
- C) Нөхцөл
- D) Давталт
Зөв хариулт: B
Тайлбар:
th:href="@{/students}"=/students.@{/students/{id}(id=${s.id})}=/students/5. Context path автомат нэмэгдэнэ. Static:@{/css/style.css}.
Тест 30
th:if / th:unless юу хийдэг вэ?
- A) Давталт
- B) НӨХЦЛӨӨР элемент ХАРУУЛАХ/НУУХ — th:if = true бол, th:unless = false бол
- C) Link
- D) Form
Зөв хариулт: B
Тайлбар:
th:if="${message}"= message null биш бол харуулна.th:unless="${session.currentUser}"= Нэвтрээгүй бол харуулна. HTML элементийг нөхцлөөр удирдах.
Тест 31
CSRF халдлага гэж юу вэ?
- A) SQL Injection
- B) Хэрэглэгчийн НЭВТЭРСЭН SESSION-г ашиглаж ХУУРАМЧ ХҮСЭЛТ илгээх
- C) XSS
- D) DDoS
Зөв хариулт: B
Тайлбар: Хэрэглэгч bank.com-д нэвтэрсэн → Хортой сайт →
<form action="bank.com/transfer" method="POST">→ Автомат submit. CSRF token = Server-ийн нууц token form-д нэмж шалгах.
Тест 32
XSS халдлага гэж юу вэ?
- A) CSRF
- B) Хортой JAVASCRIPT кодыг вэб хуудсанд ОРУУЛАХ — Cookie хулгайлах, redirect
- C) SQL Injection
- D) Brute force
Зөв хариулт: B
Тайлбар: Input:
<script>document.cookie</script>→ Хадгалагдаж → Бусад хэрэглэгчид харагдвал → Cookie хулгайлна.th:text= Автомат escape.th:utext= Escape ХИЙХГҮЙ (аюултай).
Тест 33
cookie.setHttpOnly(true) юу хийдэг вэ?
- A) Cookie устгах
- B) Cookie-г JAVASCRIPT-аар УНШИХЫГ хориглох — XSS-д эсрэг
- C) Cookie encrypt
- D) Cookie expire
Зөв хариулт: B
Тайлбар: HttpOnly =
document.cookieJavaScript-аар cookie харагдахгүй. XSS халдлагаар cookie хулгайлахаас хамгаална. JSESSIONID = HttpOnly байх ЁСТОЙ.
Тест 34
th:fragment юу хийдэг вэ?
- A) Устгах
- B) HTML хэсгийг НЭР ӨГӨЖ, бусад template-д ДАХИН АШИГЛАХ — Header, footer
- C) Form
- D) Link
Зөв хариулт: B
Тайлбар:
<header th:fragment="header">= "header" нэртэй fragment.<div th:replace="~{fragments/header :: header}">= Энэ div-г header fragment-аар СОЛИХ. DRY зарчим.
Тест 35
Tomcat гэж юу вэ?
- A) Database
- B) Apache-ийн SERVLET CONTAINER / WEB SERVER — Servlet, JSP ажиллуулах
- C) Framework
- D) IDE
Зөв хариулт: B
Тайлбар: Tomcat = Servlet container. HTTP хүсэлт хүлээж → Servlet дуудаж → Хариу буцаах. Spring Boot = Embedded Tomcat (JAR дотор). Тусдаа суулгах шаардлагагүй.
Тест 36
@WebServlet("/students") юу хийдэг вэ?
- A) Controller
- B) Servlet class-г
/studentsURL-д ЗУРАГЛАХ (mapping) - C) View
- D) Service
Зөв хариулт: B
Тайлбар: Browser →
GET /students→ Tomcat →@WebServlet("/students")→ StudentServlet.doGet() ажиллана. Annotation-аар URL mapping (web.xml-ийн орлуулагч).
Тест 37
request.getParameter("name") юу хийдэг вэ?
- A) Header авах
- B) HTTP хүсэлтийн FORM/QUERY PARAMETER-ийн утгыг авах
- C) Cookie авах
- D) Session авах
Зөв хариулт: B
Тайлбар: Form:
<input name="name">→ Submit →request.getParameter("name")= "Бат". URL:?name=Бат→ "Бат". Servlet-д form өгөгдөл авах арга. Spring = @RequestParam.
Тест 38
request.getRequestDispatcher().forward() юу хийдэг вэ?
- A) Redirect
- B) Хүсэлтийг СЕРВЕРИЙН ДОТОР JSP/Servlet руу ДАМЖУУЛАХ — URL өөрчлөгдөхгүй
- C) Response буцаах
- D) Cookie тохируулах
Зөв хариулт: B
Тайлбар:
forward("/WEB-INF/views/students.jsp")= Server дотор JSP руу шилжих. URL хэвээр (/students). Client мэдэхгүй.redirect= Шинэ хүсэлт → URL ӨӨРЧЛӨГДӨНӨ.
Тест 39
response.sendRedirect("/students") vs forward() ялгаа юу вэ?
- A) Ялгаагүй
- B) Redirect = CLIENT-д 302 + шинэ URL → URL ӨӨРЧЛӨГДӨНӨ, Forward = SERVER дотор → URL ХЭВЭЭР
- C) Forward = URL өөрчлөгдөнө
- D) Redirect = Server дотор
Зөв хариулт: B
Тайлбар: POST → forward → JSP = URL /students (POST). F5 = POST давтагдана! POST → redirect → GET = URL /students (GET). F5 = GET давтагдана (OK). PRG = Redirect ашиглах.
Тест 40
JSTL <c:forEach> юу хийдэг вэ?
- A) Нөхцөл
- B) JSP-д collection-г ДАВТАЖ, элемент бүрийг харуулах
- C) Link
- D) Form
Зөв хариулт: B
Тайлбар:
<c:forEach var="s" items="${students}">= students list давтах.${s.name}= Нэг элементийн name. Thymeleaf-дth:each-тай ижил үүрэг.
Тест 41
Expression Language (EL) ${...} юу хийдэг вэ?
- A) Java код
- B) JSP/Thymeleaf-д УТГА ХАРУУЛАХ хялбар syntax —
${student.name}= getName() - C) SQL
- D) CSS
Зөв хариулт: B
Тайлбар:
${student.name}= student.getName() дуудах.${students.size()}= List хэмжээ. JSP-д<%= student.getName() %>= Хуучин арга. EL = Хялбар.
Тест 42
Spring Boot-д Thymeleaf template хаана байрлах вэ?
- A) /static/
- B) /src/main/resources/TEMPLATES/ — students/list.html, students/form.html
- C) /WEB-INF/
- D) /public/
Зөв хариулт: B
Тайлбар:
return "students/list"→templates/students/list.html. Автомат prefix:templates/, suffix:.html. Static файл (CSS, JS) →/static/.
Тест 43
Static файлууд (CSS, JS, Image) хаана байрлах вэ?
- A) /templates/
- B) /src/main/resources/STATIC/ — css/, js/, images/
- C) /WEB-INF/
- D) /java/
Зөв хариулт: B
Тайлбар:
static/css/style.css→th:href="@{/css/style.css}".static/images/logo.png→th:src="@{/images/logo.png}". Static = Server боловсруулахгүй, шууд буцаана.
Тест 44
Model object юу хийдэг вэ?
- A) Database
- B) Controller-аас VIEW руу ӨГӨГДӨЛ ДАМЖУУЛАХ —
model.addAttribute("students", list) - C) Service
- D) Repository
Зөв хариулт: B
Тайлбар: Controller:
model.addAttribute("students", studentList)→ Thymeleaf:${students}= studentList. Model = Controller ↔ View хоорондын өгөгдлийн "гүүр".
Тест 45
return "redirect:/students" юу хийдэг вэ?
- A) Forward
- B) Client-д HTTP 302 REDIRECT хариу → Browser шинэ GET /students хүсэлт илгээх
- C) View render
- D) JSON буцаах
Зөв хариулт: B
Тайлбар: POST /students → Save →
redirect:/students→ 302 Found → Browser → GET /students → List. PRG pattern.return "students/list"= Forward (redirect биш).
Тест 46
@GetMapping("/students/edit/{id}") юу хийдэг вэ?
- A) POST mapping
- B) GET /students/edit/5 URL-г ЭНЭ METHOD-д холбох — Edit form харуулах
- C) DELETE mapping
- D) PUT mapping
Зөв хариулт: B
Тайлбар:
GET /students/edit/5→@GetMapping("/students/edit/{id}")→@PathVariable Long id= 5 → Student олж → Form-д харуулах. Edit form = GET, Save = POST.
Тест 47
th:action юу хийдэг вэ?
- A) Link
- B) HTML form-ийн ACTION URL-г Thymeleaf-аар тохируулах
- C) Text
- D) CSS
Зөв хариулт: B
Тайлбар:
th:action="@{/students}"= Form submit → POST /students.th:action="@{/students/edit/{id}(id=${student.id})}"= POST /students/edit/5. Context path автомат.
Тест 48
th:object="${student}" юу хийдэг вэ?
- A) Текст
- B) Form-г Java OBJECT-д BIND хийх —
*{name}= student.name-д - C) Link
- D) Давталт
Зөв хариулт: B
Тайлбар:
th:object="${student}"= Form = StudentForm.th:field="*{name}"= student.getName()/setName().*{...}= th:object-ийн property-д хандах shortcut.
Тест 49
th:classappend юу хийдэг вэ?
- A) Class устгах
- B) Нөхцлөөр CSS CLASS НЭМЭХ — Алдаатай field-д "is-invalid" class нэмэх
- C) Style нэмэх
- D) ID нэмэх
Зөв хариулт: B
Тайлбар:
th:classappend="${#fields.hasErrors('name')} ? 'is-invalid'"= name field алдаатай бол →class="is-invalid"нэмэгдэнэ → CSS-аар улаан border. Нөхцөлт стиль.
Тест 50
th:errors="*{name}" юу хийдэг вэ?
- A) Текст
- B) name FIELD-ийн VALIDATION АЛДААНЫ мэдэгдэлийг харуулах
- C) Link
- D) Form
Зөв хариулт: B
Тайлбар:
@NotBlank(message="Нэр хоосон байж болохгүй")→ Form submit → name хоосон →th:errors="*{name}"= "Нэр хоосон байж болохгүй" текст харагдана.
Тест 51
@NotBlank vs @NotNull ялгаа юу вэ?
- A) Ялгаагүй
- B) @NotBlank = null + ХООСОН STRING ч болохгүй, @NotNull = зөвхөн null болохгүй
- C) @NotBlank = Зөвхөн null
- D) @NotNull = Хоосон string
Зөв хариулт: B
Тайлбар:
@NotNull→ null ❌, "" ✅.@NotBlank→ null ❌, "" ❌, " " ❌. String field → @NotBlank ашиглах. Number field (Double) → @NotNull ашиглах.
Тест 52
@Email annotation юу хийдэг вэ?
- A) Email илгээх
- B) String-г EMAIL FORMAT-д НИЙЦЭЖ буйг шалгах — user@example.com
- C) Email хадгалах
- D) Email устгах
Зөв хариулт: B
Тайлбар:
@Email(message="Email формат буруу")→ "bat" ❌, "bat@" ❌, "bat@test.com" ✅. RFC 5322 email формат. @NotBlank-тай хамт ашиглах (хоосон ч шалгах).
Тест 53
@Size(min=2, max=50) юу хийдэг вэ?
- A) Number шалгах
- B) String/Collection-ийн ХЭМЖЭЭГ min-max хооронд байхыг шалгах
- C) File шалгах
- D) Date шалгах
Зөв хариулт: B
Тайлбар:
@Size(min=2, max=50, message="Нэр 2-50 тэмдэгт")→ "Б" (1 тэмдэгт) ❌, "Бат" (3) ✅. String length шалгана. Collection-д size шалгана.
Тест 54
HTTP 500 status code юу вэ?
- A) Not Found
- B) INTERNAL SERVER ERROR — Серверийн дотоод алдаа (NullPointer, DB алдаа)
- C) Redirect
- D) OK
Зөв хариулт: B
Тайлбар: Server дээр Exception catch хийгдээгүй → 500. NullPointerException, SQL алдаа. Custom error page: templates/error/500.html. @ControllerAdvice = Global handler.
Тест 55
HTTP 302 status code юу вэ?
- A) OK
- B) FOUND (REDIRECT) — Өөр URL руу ЧИГЛҮҮЛЭХ
- C) Not Found
- D) Server Error
Зөв хариулт: B
Тайлбар:
return "redirect:/students"→ Response: 302 Found + Location: /students → Browser → GET /students. PRG pattern-ийн суурь. 301 = Permanent, 302 = Temporary.
Тест 56
HttpSession session = request.getSession() юу хийдэг вэ?
- A) Session устгах
- B) Одоогийн SESSION-г авах, байхгүй бол ШИНЭ үүсгэх
- C) Cookie авах
- D) Request авах
Зөв хариулт: B
Тайлбар:
getSession()= Session байвал авна, байхгүй бол шинээр үүсгэнэ.getSession(false)= Session байхгүй бол NULL буцаана (шинээр үүсгэхгүй). Interceptor-дfalseашиглах.
Тест 57
excludePathPatterns("/login", "/css/**") юу хийдэг вэ?
- A) Path нэмэх
- B) Interceptor-ийг ЭНЭ URL-уудад АЖИЛЛУУЛАХГҮЙ — Login хуудас, static файлууд
- C) Path устгах
- D) Path шинэчлэх
Зөв хариулт: B
Тайлбар: Auth Interceptor → /login = Exclude (нэвтрэхгүйгээр харах). /css/** = Exclude (CSS файл авах). /students/** = Auth шалгана. Exclude = Interceptor ажиллахгүй.
Тест 58
CommandLineRunner юу хийдэг вэ?
- A) CLI tool
- B) Spring Boot ЭХЛЭХЭД автомат код ажиллуулах — Анхны өгөгдөл нэмэх (seed data)
- C) Test runner
- D) Build tool
Зөв хариулт: B
Тайлбар:
implements CommandLineRunner→run()= Апп эхлэхэд ажиллана. Test хэрэглэгч, жишээ оюутнуудыг DB-д нэмэх. H2 in-memory DB = Апп зогсоход устна → CommandLineRunner дахин нэмнэ.
Тест 59
H2 Database юу вэ?
- A) Production DB
- B) JAVA IN-MEMORY database — Хөгжүүлэлт, тестэд ашиглах, апп зогсоход УСТНА
- C) Cloud DB
- D) File DB
Зөв хариулт: B
Тайлбар:
jdbc:h2:mem:studentdb= Memory-д. Апп эхлэх → DB үүснэ → Апп зогсох → DB устна. H2 Console:localhost:8080/h2-console. Хөгжүүлэлтэд хурдан. Production → PostgreSQL.
Тест 60
spring.thymeleaf.cache=false юу хийдэг вэ?
- A) Cache идэвхжүүлэх
- B) Template CACHE унтраах — HTML өөрчлөхөд ШУУД хуудас дээр харагдах (development)
- C) DB cache
- D) Static cache
Зөв хариулт: B
Тайлбар: Development: cache=false → HTML засвар → Browser refresh → Шууд харна. Production: cache=true (default) → Хурд нэмэгдэнэ. DevTools + LiveReload бас ашиглаж болно.
Тест 61
server.servlet.session.timeout=30m юу хийдэг вэ?
- A) Server timeout
- B) Session 30 МИНУТЫН дараа АВТОМАТ ХҮЧИНГҮЙ болох (expire)
- C) Request timeout
- D) DB timeout
Зөв хариулт: B
Тайлбар: 30 минут → Хэрэглэгч ямар ч хүсэлт илгээхгүй → Session хүчингүй → Дараагийн хүсэлтэд → Interceptor → Login руу. Аюулгүй байдлын тохиргоо.
Тест 62
th:replace vs th:insert ялгаа юу вэ?
- A) Ялгаагүй
- B) th:replace = Host tag-г БҮРЭН СОЛИХ, th:insert = Host tag-ийн ДОТОР нэмэх
- C) th:replace = Дотор
- D) th:insert = Солих
Зөв хариулт: B
Тайлбар:
<div th:replace="~{header :: nav}">= div → nav-аар СОЛИГДОНО (div байхгүй).<div th:insert="~{header :: nav}">= div ДОТОР nav орно (div хэвээр). Replace = Илүү цэвэр.
Тест 63
@CookieValue annotation юу хийдэг вэ?
- A) Cookie тохируулах
- B) HTTP COOKIE-ийн утгыг METHOD PARAMETER-д авах
- C) Cookie устгах
- D) Session авах
Зөв хариулт: B
Тайлбар:
@CookieValue(value="theme", defaultValue="light") String theme= theme cookie-ийн утга. Cookie байхгүй бол "light". Spring-ийн хялбар cookie унших арга.
Тест 64
cookie.setSecure(true) юу хийдэг вэ?
- A) Cookie encrypt
- B) Cookie-г зөвхөн HTTPS холболтоор ИЛГЭЭХ — HTTP-р илгээхгүй
- C) Cookie delete
- D) Cookie size
Зөв хариулт: B
Тайлбар: Secure=true → HTTPS-ээр Л. HTTP (шифрлэгдээгүй) → Cookie илгээхгүй → Сүлжээнд cookie хулгайлахаас хамгаалах. Production-д ЗААВАЛ HTTPS + Secure.
Тест 65
Pagination (хуудаслалт) яагаад хэрэгтэй вэ?
- A) Шаардлагагүй
- B) 10,000 оюутныг НЭГ ХУУДСАНД харуулахгүй — Хуудас хуудсаар (20 оюутан/хуудас)
- C) Зөвхөн тест
- D) Зөвхөн API
Зөв хариулт: B
Тайлбар:
Pageable pageable = PageRequest.of(0, 20)= 1-р хуудас, 20 оюутан.repository.findAll(pageable)→ Page<Student>. URL:/students?page=0&size=20. Хурд + UX.
Тест 66
th:href="@{/students/edit/{id}(id=${student.id})}" юу үүсгэх вэ?
- A) /students/edit/{id}
- B) /students/edit/5 (student.id=5 бол) — Dynamic URL parameter
- C) /students/edit/
- D) /students/
Зөв хариулт: B
Тайлбар:
{id}= placeholder.(id=${student.id})= Утга оноох. student.id=5 →/students/edit/5. Олон parameter:@{/students(page=${page}, size=${size})}→/students?page=0&size=20.
Тест 67
onclick="return confirm('Устгах уу?')" юу хийдэг вэ?
- A) Шууд устгах
- B) БАТАЛГААЖУУЛАХ dialog харуулж, OK = устгах, Cancel = ЦУЦЛАХ
- C) Alert
- D) Redirect
Зөв хариулт: B
Тайлбар: Устгах link дээр дарахад → "Устгах уу?" dialog → OK → /students/delete/5 руу → Устгах. Cancel → Юу ч хийхгүй. Client-side баталгаажуулалт. Санамсаргүй устгалтаас хамгаалах.
Тест 68
Web application-д давхаргалалт (layering) яагаад чухал вэ?
- A) Шаардлагагүй
- B) Controller → Service → Repository ТУСГААРЛАЖ, код цэвэр, тест хялбар, засвар хөнгөн
- C) Зөвхөн том төсөлд
- D) Зөвхөн API-д
Зөв хариулт: B
Тайлбар: Controller = Хүсэлт хүлээх. Service = Бизнес логик. Repository = DB. Controller-д SQL бичихгүй! Service-д HTTP мэдэхгүй! Separation of Concerns = Цэвэр код.
Тест 69
Embedded Tomcat гэж юу вэ?
- A) Тусдаа сервер
- B) Spring Boot JAR ДОТОР Tomcat суулгагдсан — Тусдаа суулгахгүй,
java -jar app.jar - C) Docker container
- D) Cloud сервер
Зөв хариулт: B
Тайлбар: Хуучин: WAR файл → Тусдаа Tomcat-д deploy. Spring Boot: JAR = Tomcat дотор.
./mvnw spring-boot:runэсвэлjava -jar app.jar. Хялбар deploy.
Тест 70
@RequestMapping("/students") юу хийдэг вэ?
- A) Зөвхөн GET
- B) Class/method түвшинд URL PREFIX тохируулах — Бүх method /students-ээр эхэлнэ
- C) Зөвхөн POST
- D) Зөвхөн DELETE
Зөв хариулт: B
Тайлбар: Class-д:
@RequestMapping("/students")→@GetMapping= GET /students.@GetMapping("/new")= GET /students/new.@PostMapping("/edit/{id}")= POST /students/edit/5. Prefix.
Тест 71
Idempotent HTTP method гэж юу вэ?
- A) Бүх method
- B) Хэдэн ч удаа дуудсан ҮР ДҮН ИЖИЛ — GET, PUT, DELETE
- C) Зөвхөн POST
- D) Зөвхөн PATCH
Зөв хариулт: B
Тайлбар: GET /students/1 → 10 удаа = Ижил student. DELETE /students/1 → 2 удаа = 1-р удаа устгана, 2-р удаа 404 (устгасан). POST = Idempotent БИШ (2 удаа = 2 бичлэг).
Тест 72
Content-Type: text/html header юу вэ?
- A) JSON
- B) Response body-ийн ФОРМАТ = HTML гэж browser-д мэдэгдэх
- C) XML
- D) Plain text
Зөв хариулт: B
Тайлбар:
text/html= HTML хуудас.application/json= JSON.text/plain= Энгийн текст. Browser Content-Type-аар body-г яаж харуулахаа мэднэ.
Тест 73
@ResponseBody annotation юу хийдэг вэ?
- A) View буцаах
- B) Method-ийн буцаах утгыг HTTP RESPONSE BODY-д шууд бичих (JSON/String)
- C) Redirect
- D) Forward
Зөв хариулт: B
Тайлбар:
@Controller+@ResponseBody=@RestController.return student→ JSON. View resolver ашиглахгүй. Template render хийхгүй. REST API-д.
Тест 74
web.xml файл юу вэ?
- A) HTML файл
- B) Servlet-ийн ТОХИРГОО файл — Servlet mapping, filter, listener (хуучин арга)
- C) CSS файл
- D) JS файл
Зөв хариулт: B
Тайлбар: Хуучин: web.xml →
<servlet-mapping>тохируулах. Шинэ:@WebServletannotation. Spring Boot: web.xml ШААРДЛАГАГҮЙ (автомат тохиргоо). Backward compatibility.
Тест 75
request.setAttribute("key", value) юу хийдэг вэ?
- A) Session нэмэх
- B) REQUEST SCOPE-д өгөгдөл нэмж, FORWARD-аар JSP руу дамжуулах
- C) Cookie нэмэх
- D) Response нэмэх
Зөв хариулт: B
Тайлбар: Servlet:
request.setAttribute("students", list)→forward("students.jsp")→ JSP:${students}. Request scope = Зөвхөн ЭНЭ хүсэлтэд. Spring-д Model object ижил.
Тест 76
th:src="@{/images/logo.png}" юу хийдэг вэ?
- A) Текст
- B) HTML img tag-ийн SRC-г Thymeleaf-аар тохируулах — Static image
- C) Link
- D) Form
Зөв хариулт: B
Тайлбар:
static/images/logo.png→th:src="@{/images/logo.png}"→<img src="/images/logo.png">. Context path автомат. Browser → Server → Static файл буцаана.
Тест 77
@DecimalMin / @DecimalMax юу хийдэг вэ?
- A) String шалгах
- B) Тоон утгын ХАМГИЙН БАГА/ИХ хязгаарыг шалгах — GPA 0.0-4.0
- C) Date шалгах
- D) Boolean шалгах
Зөв хариулт: B
Тайлбар:
@DecimalMin("0.0")→ -0.1 ❌, 0.0 ✅.@DecimalMax("4.0")→ 4.1 ❌, 4.0 ✅. Double, BigDecimal field-д. Integer → @Min/@Max ашиглаж болно.
Тест 78
#fields.hasErrors('name') (Thymeleaf) юу хийдэг вэ?
- A) Field утга
- B) name FIELD-д VALIDATION АЛДАА байгаа эсэхийг шалгах — true/false
- C) Field устгах
- D) Field нэмэх
Зөв хариулт: B
Тайлбар:
th:if="${#fields.hasErrors('name')}"= name алдаатай бол → Алдааны div харуулах.#fields.hasAnyErrors()= Аль нэг field алдаатай бол. Нөхцөлт алдааны харуулалт.
Тест 79
#lists.isEmpty(students) (Thymeleaf) юу хийдэг вэ?
- A) List үүсгэх
- B) students LIST ХООСОН ЭСЭХИЙГ шалгах — Хоосон бол "Оюутан байхгүй" мэдэгдэл
- C) List устгах
- D) List нэмэх
Зөв хариулт: B
Тайлбар:
th:if="${#lists.isEmpty(students)}"= Хоосон → "Оюутан бүртгэгдээгүй байна."th:if="${!#lists.isEmpty(students)}"= Хоосон биш → Хүснэгт харуулах. Utility object.
Тест 80
addPathPatterns("/**") (Interceptor) юу вэ?
- A) Нэг path
- B) БҮХ URL PATTERN-д interceptor ажиллуулах —
/**= Бүх path - C) Зөвхөн root
- D) Static файл
Зөв хариулт: B
Тайлбар:
/**= /students, /students/new, /dashboard бүгд.excludePathPatterns("/login", "/css/**")= Эдгээрийг хасна. addPath + excludePath = Хамрах хүрээ тохируулах.
Тест 81
request.getRequestDispatcher("/WEB-INF/views/students.jsp") яагаад /WEB-INF/ дотор вэ?
- A) Дурын байршил
- B) /WEB-INF/ дотор = Browser-аас ШУУД ХАНДАЖ чадахгүй, зөвхөн FORWARD-аар
- C) /static/ дотор
- D) /templates/ дотор
Зөв хариулт: B
Тайлбар: /WEB-INF/ = Protected. Browser:
localhost:8080/WEB-INF/views/students.jsp→ 404! Зөвхөн server-side forward-аар хандана. Аюулгүй байдал = JSP шууд нээгдэхгүй.
Тест 82
Spring Boot DevTools юу хийдэг вэ?
- A) Production tool
- B) Код өөрчлөхөд АВТОМАТ RESTART + Browser LiveReload — Хөгжүүлэлтийг хурдасгах
- C) Build tool
- D) Test tool
Зөв хариулт: B
Тайлбар:
spring-boot-devtoolsdependency → Java код өөрчлөх → Автомат restart (хурдан). HTML/CSS өөрчлөх → LiveReload → Browser refresh. Production-д идэвхгүй.
Тест 83
multipart/form-data юу вэ?
- A) JSON
- B) ФАЙЛ UPLOAD хийх form-ийн ENCODING TYPE —
<form enctype="multipart/form-data"> - C) Plain text
- D) XML
Зөв хариулт: B
Тайлбар: Файл upload:
<input type="file">→enctype="multipart/form-data". Controller:@RequestParam("file") MultipartFile file.spring.servlet.multipart.max-file-size=10MB.
Тест 84
th:switch / th:case юу хийдэг вэ?
- A) Давталт
- B) Утгаар САЛААЛЖ, тохирох CASE-д харуулах — Java switch-тай ижил
- C) Link
- D) Form
Зөв хариулт: B
Тайлбар:
th:switch="${user.role}"→th:case="'ADMIN'"= Админ UI.th:case="'USER'"= Хэрэглэгч UI.th:case="*"= Default. Role-аар UI ялгах.
Тест 85
application/x-www-form-urlencoded юу вэ?
- A) JSON
- B) HTML FORM-ийн DEFAULT ENCODING — key=value&key2=value2 формат
- C) File upload
- D) XML
Зөв хариулт: B
Тайлбар:
<form method="POST">= Default:application/x-www-form-urlencoded. Body:name=Бат&email=bat@test.com. @RequestParam, @ModelAttribute = Энэ формат уншина. Файл → multipart.
Тест 86
session.getAttribute("currentUser") юу хийдэг вэ?
- A) Session устгах
- B) Session-аас "currentUser" KEY-р хадгалсан УТГА АВАХ
- C) Session нэмэх
- D) Cookie авах
Зөв хариулт: B
Тайлбар: Login:
session.setAttribute("currentUser", user). Dashboard:User user = (User) session.getAttribute("currentUser"). null = Нэвтрээгүй → Login руу redirect.
Тест 87
Max-Age (Cookie) юу вэ?
- A) Session timeout
- B) Cookie хэдэн СЕКУНДЫН дараа ХУГАЦАА ДУУСАХ — 0 = Шууд устгах
- C) Server timeout
- D) Request timeout
Зөв хариулт: B
Тайлбар:
cookie.setMaxAge(30*24*60*60)= 30 хоног.setMaxAge(0)= Cookie ШУУД устгах (logout).setMaxAge(-1)= Browser хаахад устна (session cookie). Хугацаа удирдах.
Тест 88
@ExceptionHandler annotation юу хийдэг вэ?
- A) Exception үүсгэх
- B) Тодорхой EXCEPTION ГАРАХАД дуудагдах METHOD тодорхойлох
- C) Exception устгах
- D) Exception лог
Зөв хариулт: B
Тайлбар:
@ExceptionHandler(StudentNotFoundException.class)→ StudentNotFoundException гарвал → Энэ method ажиллана → error/404 view. try-catch биш, declaration-аар exception удирдах.
Тест 89
~{fragments/header :: header} (Thymeleaf) юу вэ?
- A) URL
- B) fragments/header.html файлын "header" FRAGMENT-г оруулах expression
- C) Java expression
- D) CSS selector
Зөв хариулт: B
Тайлбар:
~{файл :: fragment_нэр}= Fragment expression.fragments/header.htmlфайлынth:fragment="header"тэмдэглэсэн хэсгийг оруулна. Layout, дахин ашиглалт.
Тест 90
th:value="${student.name}" vs th:field="*{name}" ялгаа юу вэ?
- A) Ялгаагүй
- B) th:value = Зөвхөн VALUE тохируулах, th:field = VALUE + NAME + ID автомат BIND
- C) th:value = Bind
- D) th:field = Зөвхөн value
Зөв хариулт: B
Тайлбар:
th:field="*{name}"=id="name" name="name" value="Бат"автомат.th:value= Зөвхөн value. Form binding →th:fieldашиглах (validation-тай ажиллана).
Тест 91
Вэб аппын аюулгүй байдлын 3 гол зарчим юу вэ?
- A) HTML, CSS, JS
- B) INPUT VALIDATION + OUTPUT ENCODING + SESSION MANAGEMENT
- C) GET, POST, DELETE
- D) MVC layers
Зөв хариулт: B
Тайлбар: Input: @Valid, @NotBlank (Server-side). Output: th:text escape (XSS). Session: HttpOnly, Secure, Timeout. 3 зарчим = Ихэнх вэб халдлагаас хамгаалах.
Тест 92
200 OK status code юу вэ?
- A) Redirect
- B) Хүсэлт АМЖИЛТТАЙ боловсрогдсон — GET хариу, HTML хуудас
- C) Not Found
- D) Server Error
Зөв хариулт: B
Тайлбар: GET /students → 200 OK + HTML body. POST → redirect → GET → 200 OK. Хамгийн түгээмэл status code. Browser → HTML render хийнэ.
Тест 93
201 Created status code юу вэ?
- A) OK
- B) Шинэ нөөц АМЖИЛТТАЙ ҮҮССЭН — POST хариу (REST API-д)
- C) Redirect
- D) Not Found
Зөв хариулт: B
Тайлбар: REST: POST /api/students → 201 Created + Location header. Web form: POST → 302 Redirect (PRG). 201 = REST API-д. 302 = Web form-д.
Тест 94
400 Bad Request status code юу вэ?
- A) Server алдаа
- B) CLIENT-ийн хүсэлт БУРУУ — Validation алдаа, формат буруу
- C) Not Found
- D) Redirect
Зөв хариулт: B
Тайлбар: Буруу JSON, required parameter дутуу, validation fail. REST API → 400 + алдааны мэдэгдэл. Web form → Form руу буцааж алдаа харуулах (200 + form).
Тест 95
401 Unauthorized vs 403 Forbidden ялгаа юу вэ?
- A) Ялгаагүй
- B) 401 = НЭВТРЭЛТ хэрэгтэй (login), 403 = Нэвтэрсэн ч ЭРХ ХҮРЭХГҮЙ (role)
- C) 401 = Эрхгүй
- D) 403 = Login хэрэгтэй
Зөв хариулт: B
Тайлбар: 401: Login хийгээгүй → "Нэвтэрнэ үү." 403: Login хийсэн (USER) → Admin хуудас → "Таны эрх хүрэхгүй." 401 = Authentication. 403 = Authorization.
Тест 96
spring-boot-starter-web юу агуулдаг вэ?
- A) Зөвхөн Tomcat
- B) EMBEDDED TOMCAT + Spring MVC + Jackson (JSON) — Вэб апп бүтээхэд хэрэгтэй бүгд
- C) Зөвхөн Spring MVC
- D) Зөвхөн Jackson
Зөв хариулт: B
Тайлбар:
spring-boot-starter-web= Tomcat (server) + Spring MVC (controller, mapping) + Jackson (JSON). Нэг dependency → Вэб апп бэлэн. Thymeleaf → Тусдаа starter нэмэх.
Тест 97
spring-boot-starter-validation юу агуулдаг вэ?
- A) Зөвхөн @Valid
- B) Hibernate Validator + Jakarta Validation API — @NotBlank, @Email, @Size бүгд
- C) Зөвхөн @NotNull
- D) Зөвхөн @Email
Зөв хариулт: B
Тайлбар: Jakarta Validation = Interface (@NotBlank, @Email). Hibernate Validator = Implementation. Spring Boot 3.x = jakarta.validation.constraints package. Dependency нэмэхгүй бол @Valid ажиллахгүй!
Тест 98
Server-side rendering vs Client-side rendering ялгаа юу вэ?
- A) Ялгаагүй
- B) SSR = SERVER дээр HTML бэлдэж буцаах (Thymeleaf), CSR = CLIENT-д JS-аар render (React)
- C) SSR = Client
- D) CSR = Server
Зөв хариулт: B
Тайлбар: Thymeleaf/JSP = SSR → Server HTML → Browser шууд харуулна. React/Vue = CSR → Server JSON → Browser JS-аар HTML render. SSR = SEO сайн, анхны ачаалал хурдан. CSR = Интерактив.
Тест 99
Вэб аппын тест хийхэд юу ашиглах вэ?
- A) Зөвхөн browser
- B) JUnit + MockMvc (Controller тест) + Selenium (Browser тест) + Postman (API тест)
- C) Зөвхөн Postman
- D) Зөвхөн JUnit
Зөв хариулт: B
Тайлбар: MockMvc = Controller endpoint тест (server эхлүүлэхгүй). Selenium = Browser автомат тест (UI). Postman = API хүсэлт тест. @SpringBootTest = Integration тест.
Тест 100
Java Web Programming-ийн гол ойлголтуудыг нэгтгэвэл?
- A) Зөвхөн HTML
- B) HTTP + Servlet + MVC + Template Engine + CRUD + Session + Validation + Security = Бүтэн вэб апп
- C) Зөвхөн DB
- D) Зөвхөн API
Зөв хариулт: B
Тайлбар: HTTP (протокол) → Servlet/Controller (хүсэлт) → Service (логик) → Repository (DB) → Thymeleaf (view) → Session (state) → Validation (шалгалт) → Security (хамгаалалт). Бүх давхаргыг ХАМТАД нь ашиглан бүтэн вэб аппликейшн бүтээнэ.
📚 Ашигласан эх сурвалжууд:
- Tim Downey — Guide to Web Development with Java: Understanding Website Creation (Springer, 2012)
- Spring Boot Reference Documentation (spring.io/projects/spring-boot)
- Thymeleaf Documentation (thymeleaf.org/documentation.html)
- Jakarta Servlet Specification (jakarta.ee/specifications/servlet)
- Oracle Java EE Tutorial — Servlets (docs.oracle.com/javaee/tutorial)
- Baeldung — Spring MVC & Thymeleaf Tutorials (baeldung.com)
- OWASP — Web Security Best Practices (owasp.org)
- Lectures Web I-III, Lecture 07-11 (Course Materials)