ЛЕКЦ 12

Вэб Програмчлал

ЛЕКЦ 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ЗорилгоIdempotentBody
GETӨгөгдөл АВАХ (унших)✅ Тийм❌ Байхгүй
POSTӨгөгдөл ҮҮСГЭХ (бичих)❌ Үгүй✅ Байна
PUTӨгөгдөл бүрэн ШИНЭЧЛЭХ✅ Тийм✅ Байна
DELETEӨгөгдөл УСТГАХ✅ Тийм❌ Байхгүй
PATCHӨгөгдөл хэсэгчлэн шинэчлэх❌ Үгүй✅ Байна

HTTP Status Code:

КодУтгаЖишээ
200OK — АмжилттайGET хариу
201Created — ҮүсгэсэнPOST хариу
301Moved Permanently — ШилжсэнRedirect
302Found — Түр шилжүүлэхLogin дараа redirect
400Bad Request — Буруу хүсэлтValidation алдаа
401Unauthorized — НэвтрэлтгүйLogin хэрэгтэй
403Forbidden — ЭрхгүйЗөвшөөрөлгүй
404Not Found — ОлдсонгүйURL буруу
500Internal 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.xmlXML файлаар тохируулах (хуучин)
<!-- 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/JSPSpring Boot Web
web.xml тохиргооАвтомат тохиргоо
Tomcat тусад deployEmbedded Tomcat (JAR дотор)
Manual dependencySpring 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Буцаах зүйлХэрэглээ
@ControllerVIEW нэр (HTML template)Вэб хуудас (Thymeleaf)
@RestControllerJSON/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:

ШинжJSPThymeleaf
Файл.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:hrefLink үүсгэхth:href="@{/students/{id}(id=${s.id})}"
th:actionForm actionth:action="@{/students}"
th:objectForm object bindth:object="${student}"
th:fieldForm field bindth:field="*{name}"
th:valueУтга оноохth:value="${student.name}"
th:srcImage srcth:src="@{/images/logo.png}"
th:classCSS classth: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>

HTTP = Stateless → Session/Cookie-аар state удирдах

┌────────┐                          ┌────────┐
│ Client │ ── Request + Cookie ───► │ Server │
│        │ ◄── Response + Set-Cookie│        │
│        │                          │        │
│ Cookie:│                          │Session:│
│ JSESSIONID=abc123                 │ abc123:│
│                                   │  user: "Бат"│
│                                   │  role: "ADMIN"│
└────────┘                          └────────┘
ШинжSessionCookie
ХадгалахSERVER дээрCLIENT (browser) дээр
ХэмжээХязгааргүй4KB
АюулгүйИлүү аюулгүйXSS-д эмзэг
ХугацааTimeout (30 мин)Тохируулах боломжтой
ХэрэглээLogin, cartRemember 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 тохируулах
@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:

ШинжFilterInterceptor
ТүвшинServlet (доод)Spring MVC (дээд)
ХамрахБүх хүсэлт (static ч)Controller хүсэлт
Тохиргоо@ComponentWebMvcConfigurer
ХэрэглээLogging, CORS, encodingAuth, 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>&copy; 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)

#ЗарчимТайлбар
1PRG PatternPOST → Redirect → GET (давхар submit хамгаалалт)
2Input ValidationServer-side @Valid + Client-side HTML5
3CSRF ProtectionSpring Security CSRF token (form-д)
4XSS PreventionThymeleaf автомат escape (th:text)
5Session Timeoutserver.servlet.session.timeout=30m
6HttpOnly Cookiecookie.setHttpOnly(true)
7Error PageCustom 404, 500 хуудас
8Fragment/LayoutHeader, footer дахин ашиглах
9Flash MessageRedirectAttributes амжилт/алдаа мэдэгдэл
10SeparationController → Service → Repository давхаргалах
11Static ResourcesCSS, JS, Image → /static/
12PaginationИх өгөгдлийг хуудаслах (Pageable)
13ResponsiveMobile-friendly дизайн (Bootstrap)
14LoggingХүсэлт, алдаа бүрийг лог бичих
15HTTPSProduction-д заавал HTTPS


ХЭСЭГ 2: ТҮЛХҮҮР ҮГ БА МЭРГЭЖЛИЙН НЭР ТОМЬЁО (Keywords & Glossary)

#Англи нэр томьёоМонгол утгаДэлгэрэнгүй тайлбар
1HTTPГипертекст дамжуулах протоколClient-Server хоорондын хүсэлт/хариу дамжуулах протокол.
2ServletСеровлетHTTP хүсэлт хүлээн авч, боловсруулж, хариу буцаах Java class.
3JSPJava серверийн хуудасHTML дотор Java код бичих template технологи.
4ThymeleafТаймлифОрчин үеийн server-side HTML template engine — Natural template.
5MVCЗагвар-Харагдац-УдирдлагаModel-View-Controller — Вэб аппын бүтэц хуваах загвар.
6ControllerУдирдагчHTTP хүсэлт хүлээн авч, Service дуудаж, View буцаах.
7ModelЗагварӨгөгдөл + бизнес логик (Entity, Service).
8ViewХарагдацХэрэглэгчид харуулах UI (HTML template).
9CRUDҮүсгэх-Унших-Шинэчлэх-УстгахCreate, Read, Update, Delete — Өгөгдлийн 4 үндсэн үйлдэл.
10SessionСессияServer дээр хэрэглэгчийн state хадгалах механизм.
11CookieКүүкиClient (browser) дээр жижиг өгөгдөл хадгалах механизм.
12JSESSIONIDСессийн IDTomcat-ийн session cookie — Хэрэглэгчийг таних.
13PRG PatternPost-Redirect-GetPOST → 302 Redirect → GET — Давхар submit хамгаалалт.
14CSRFХуурамч хүсэлтCross-Site Request Forgery — Хэрэглэгчийн нэрээр хуурамч хүсэлт.
15XSSХөндлөн скриптCross-Site Scripting — Хортой JavaScript оруулах халдлага.
16FilterШүүлтүүрServlet түвшинд хүсэлт/хариуг шүүх (logging, encoding).
17InterceptorТасалдуулагчSpring MVC түвшинд хүсэлтийг шалгах (auth, role).
18@ControllerКонтроллерSpring MVC — HTML view буцаах controller annotation.
19@RestControllerREST контроллерJSON/XML буцаах controller — @Controller + @ResponseBody.
20@RequestMappingХүсэлт зураглалтURL path-г controller method-д холбох.
21@GetMappingGET зураглалтHTTP GET хүсэлт хүлээн авах method.
22@PostMappingPOST зураглалт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-уудыг шалгах.
27BindingResultХолболтын үр дүнValidation алдааг хадгалах object.
28RedirectAttributesЧиглүүлэлтийн атрибутRedirect хийхэд flash message дамжуулах.
29th:eachДавталтThymeleaf — Collection давталт (for-each).
30th:textТекстThymeleaf — Элементийн текстийг тохируулах + XSS escape.
31th:fieldТалбарThymeleaf — Form field-г object property-д bind хийх.
32th:fragmentХэсэгThymeleaf — Дахин ашиглах HTML хэсэг (header, footer).
33Expression LanguageИлэрхийллийн хэл${...} — JSP/Thymeleaf-д утга харуулах.
34JSTLJSP стандарт тагийн санJSP-д <c:forEach>, <c:if> зэрэг tag-ууд.
35TomcatТомкатApache-ийн Servlet container / Web server.
36Web ContainerВэб контейнерServlet ажиллуулах орчин (Tomcat, Jetty).
37Embedded ServerСуулгасан серверSpring Boot-д Tomcat JAR дотор ажиллана.
38Static ResourceСтатик нөөцCSS, JS, Image — Server боловсруулахгүй, шууд буцаах.
39@ControllerAdviceКонтроллер зөвлөмжБүх controller-д хамаарах global exception handler.
40PaginationХуудаслалтИх өгөгдлийг хуудас хуудсаар харуулах (Page, Pageable).


ХЭСЭГ 3: ЛАБОРАТОРИ БА ПРАКТИК ЗААВАР (Labs & Step-by-Step Guide)

3.1 Лабораторийн зорилго

Энэ лабораторид та:

  1. Spring Boot Web + Thymeleaf төсөл үүсгэх
  2. Full CRUD (Оюутан) бүтээх
  3. Form Validation нэмэх
  4. Session-аар Login/Logout хийх
  5. 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

Тайлбар: @Controllerreturn "students/list" → Thymeleaf template. @RestControllerreturn 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.cookie JavaScript-аар 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-г /students URL-д ЗУРАГЛАХ (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.cssth:href="@{/css/style.css}". static/images/logo.pngth: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 CommandLineRunnerrun() = Апп эхлэхэд ажиллана. 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> тохируулах. Шинэ: @WebServlet annotation. 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.pngth: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-devtools dependency → 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)