3.1 Лабораторийн зорилго
Энэ лабораторид та Spring Boot ашиглан RESTful API бүтээж, бодит дадлага хийнэ:
- Spring Boot төсөл үүсгэх
- CRUD REST API хөгжүүлэх (Entity, DTO, Repository, Service, Controller)
- Validation ба Exception Handling
- Swagger/OpenAPI баримт бичиг
Хэл: Java 17+ | Framework: Spring Boot 3.x | IDE: Eclipse IDE
3.2 Лаб 1: Spring Boot төсөл үүсгэх
Алхам 1: Spring Initializr ашиглах
https://start.spring.io руу орж:
| Тохиргоо | Утга |
|---|---|
| Project | Maven |
| Language | Java |
| Spring Boot | 3.2.x |
| Group | com.example |
| Artifact | student-api |
| Packaging | Jar |
| Java | 17 |
Алхам 2: Dependency нэмэх
<!-- pom.xml -->
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA + Database -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database (хөгжүүлэлтийн орчинд) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Swagger/OpenAPI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Алхам 3: application.yml тохиргоо
# src/main/resources/application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:studentdb
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
path: /h2-console
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
springdoc:
swagger-ui:
path: /swagger-ui.html
Алхам 4: Ажиллуулах
./mvnw spring-boot:run
Шалгах:
http://localhost:8080/h2-console→ H2 Database consolehttp://localhost:8080/swagger-ui.html→ Swagger UI
3.3 Лаб 2: CRUD REST API хөгжүүлэх
Алхам 1: Entity үүсгэх
// src/main/java/com/example/studentapi/entity/Student.java
package com.example.studentapi.entity;
import jakarta.persistence.*;
import java.time.LocalDate;
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private Double gpa;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private StudentStatus status = StudentStatus.ACTIVE;
@Column(name = "enrolled_at")
private LocalDate enrolledAt = LocalDate.now();
public Student() {}
public Student(String name, String email, Double gpa) {
this.name = name;
this.email = email;
this.gpa = gpa;
}
// Getter, Setter методууд
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; }
public StudentStatus getStatus() { return status; }
public void setStatus(StudentStatus status) { this.status = status; }
public LocalDate getEnrolledAt() { return enrolledAt; }
public void setEnrolledAt(LocalDate enrolledAt) { this.enrolledAt = enrolledAt; }
}
// src/main/java/com/example/studentapi/entity/StudentStatus.java
package com.example.studentapi.entity;
public enum StudentStatus {
ACTIVE, INACTIVE, GRADUATED, SUSPENDED
}
Алхам 2: DTO үүсгэх
// src/main/java/com/example/studentapi/dto/StudentCreateRequest.java
package com.example.studentapi.dto;
import jakarta.validation.constraints.*;
public record StudentCreateRequest(
@NotBlank(message = "Нэр хоосон байж болохгүй")
@Size(min = 2, max = 100, message = "Нэр 2-100 тэмдэгт байх ёстой")
String name,
@NotBlank(message = "Имэйл хоосон байж болохгүй")
@Email(message = "Имэйл формат буруу")
String email,
@NotNull(message = "GPA заавал оруулна")
@Min(value = 0, message = "GPA 0-аас бага байж болохгүй")
@Max(value = 4, message = "GPA 4-аас их байж болохгүй")
Double gpa
) {}
// src/main/java/com/example/studentapi/dto/StudentResponse.java
package com.example.studentapi.dto;
import com.example.studentapi.entity.Student;
import java.time.LocalDate;
public record StudentResponse(
Long id,
String name,
String email,
Double gpa,
String status,
LocalDate enrolledAt
) {
public static StudentResponse from(Student student) {
return new StudentResponse(
student.getId(),
student.getName(),
student.getEmail(),
student.getGpa(),
student.getStatus().name(),
student.getEnrolledAt()
);
}
}
Алхам 3: Repository үүсгэх
// src/main/java/com/example/studentapi/repository/StudentRepository.java
package com.example.studentapi.repository;
import com.example.studentapi.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface StudentRepository extends JpaRepository<Student, Long> {
Optional<Student> findByEmail(String email);
boolean existsByEmail(String email);
}
Алхам 4: Custom Exception үүсгэх
// src/main/java/com/example/studentapi/exception/StudentNotFoundException.java
package com.example.studentapi.exception;
public class StudentNotFoundException extends RuntimeException {
public StudentNotFoundException(String message) {
super(message);
}
}
// src/main/java/com/example/studentapi/exception/DuplicateEmailException.java
package com.example.studentapi.exception;
public class DuplicateEmailException extends RuntimeException {
public DuplicateEmailException(String message) {
super(message);
}
}
Алхам 5: Service үүсгэх
// src/main/java/com/example/studentapi/service/StudentService.java
package com.example.studentapi.service;
import com.example.studentapi.dto.*;
import com.example.studentapi.entity.Student;
import com.example.studentapi.exception.*;
import com.example.studentapi.repository.StudentRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class StudentService {
private final StudentRepository repository;
public StudentService(StudentRepository repository) {
this.repository = repository;
}
public List<StudentResponse> findAll() {
return repository.findAll().stream()
.map(StudentResponse::from)
.toList();
}
public StudentResponse findById(Long id) {
Student student = repository.findById(id)
.orElseThrow(() -> new StudentNotFoundException(
"ID=" + id + " оюутан олдсонгүй"
));
return StudentResponse.from(student);
}
@Transactional
public StudentResponse create(StudentCreateRequest request) {
if (repository.existsByEmail(request.email())) {
throw new DuplicateEmailException(
"'" + request.email() + "' имэйл бүртгэлтэй байна"
);
}
Student student = new Student(
request.name(), request.email(), request.gpa()
);
Student saved = repository.save(student);
return StudentResponse.from(saved);
}
@Transactional
public StudentResponse update(Long id, StudentCreateRequest request) {
Student student = repository.findById(id)
.orElseThrow(() -> new StudentNotFoundException(
"ID=" + id + " оюутан олдсонгүй"
));
student.setName(request.name());
student.setEmail(request.email());
student.setGpa(request.gpa());
Student saved = repository.save(student);
return StudentResponse.from(saved);
}
@Transactional
public void delete(Long id) {
if (!repository.existsById(id)) {
throw new StudentNotFoundException(
"ID=" + id + " оюутан олдсонгүй"
);
}
repository.deleteById(id);
}
}
Алхам 6: Controller үүсгэх
// src/main/java/com/example/studentapi/controller/StudentController.java
package com.example.studentapi.controller;
import com.example.studentapi.dto.*;
import com.example.studentapi.service.StudentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.util.List;
@RestController
@RequestMapping("/api/v1/students")
@Tag(name = "Student API", description = "Оюутны CRUD API")
public class StudentController {
private final StudentService studentService;
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
@GetMapping
@Operation(summary = "Бүх оюутан авах")
public ResponseEntity<List<StudentResponse>> findAll() {
return ResponseEntity.ok(studentService.findAll());
}
@GetMapping("/{id}")
@Operation(summary = "ID-аар оюутан авах")
public ResponseEntity<StudentResponse> findById(@PathVariable Long id) {
return ResponseEntity.ok(studentService.findById(id));
}
@PostMapping
@Operation(summary = "Шинэ оюутан үүсгэх")
public ResponseEntity<StudentResponse> create(
@Valid @RequestBody StudentCreateRequest request) {
StudentResponse created = studentService.create(request);
URI location = URI.create("/api/v1/students/" + created.id());
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
@Operation(summary = "Оюутны мэдээлэл шинэчлэх")
public ResponseEntity<StudentResponse> update(
@PathVariable Long id,
@Valid @RequestBody StudentCreateRequest request) {
return ResponseEntity.ok(studentService.update(id, request));
}
@DeleteMapping("/{id}")
@Operation(summary = "Оюутан устгах")
public ResponseEntity<Void> delete(@PathVariable Long id) {
studentService.delete(id);
return ResponseEntity.noContent().build();
}
}
Алхам 7: Global Exception Handler
// src/main/java/com/example/studentapi/exception/GlobalExceptionHandler.java
package com.example.studentapi.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(StudentNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(StudentNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(), "Not Found",
ex.getMessage(), LocalDateTime.now(), null
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<ErrorResponse> handleDuplicate(DuplicateEmailException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.CONFLICT.value(), "Conflict",
ex.getMessage(), LocalDateTime.now(), null
);
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(
MethodArgumentNotValidException ex) {
List<String> details = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.toList();
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(), "Validation Error",
"Оролтын өгөгдөл буруу", LocalDateTime.now(), details
);
return ResponseEntity.badRequest().body(error);
}
}
// src/main/java/com/example/studentapi/exception/ErrorResponse.java
package com.example.studentapi.exception;
import java.time.LocalDateTime;
import java.util.List;
public record ErrorResponse(
int status,
String error,
String message,
LocalDateTime timestamp,
List<String> details
) {}
3.4 Лаб 3: API тестлэх (cURL / Swagger)
cURL ашиглан тестлэх:
# 1. Шинэ оюутан үүсгэх (POST)
curl -X POST http://localhost:8080/api/v1/students \
-H "Content-Type: application/json" \
-d '{
"name": "Батболд",
"email": "batbold@example.com",
"gpa": 3.75
}'
# Хариу: 201 Created + StudentResponse JSON
# 2. Бүх оюутан авах (GET)
curl http://localhost:8080/api/v1/students
# Хариу: 200 OK + List<StudentResponse>
# 3. ID-аар оюутан авах (GET)
curl http://localhost:8080/api/v1/students/1
# Хариу: 200 OK + StudentResponse
# 4. Оюутны мэдээлэл шинэчлэх (PUT)
curl -X PUT http://localhost:8080/api/v1/students/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Батболд Дорж",
"email": "batbold.dorj@example.com",
"gpa": 3.80
}'
# Хариу: 200 OK + Шинэчлэгдсэн StudentResponse
# 5. Оюутан устгах (DELETE)
curl -X DELETE http://localhost:8080/api/v1/students/1
# Хариу: 204 No Content
# 6. Validation алдаа тестлэх
curl -X POST http://localhost:8080/api/v1/students \
-H "Content-Type: application/json" \
-d '{
"name": "",
"email": "буруу-имэйл",
"gpa": 5.0
}'
# Хариу: 400 Bad Request + Validation алдааны жагсаалт
# 7. Олдохгүй оюутан тестлэх
curl http://localhost:8080/api/v1/students/999
# Хариу: 404 Not Found + "ID=999 оюутан олдсонгүй"
Swagger UI ашиглан тестлэх:
http://localhost:8080/swagger-ui.htmlнээх- "Student API" бүлгийг дарж нээх
- "Try it out" товч дарах
- Параметр оруулж "Execute" дарах
- Response шалгах
3.5 Лаб 4: Хуудаслалт ба Шүүлт нэмэх
Controller-д хуудаслалт нэмэх:
// StudentController.java — findAll методыг шинэчлэх
@GetMapping
@Operation(summary = "Оюутны жагсаалт (хуудаслалттай)")
public ResponseEntity<Page<StudentResponse>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "asc") String order) {
Sort sort = order.equalsIgnoreCase("desc")
? Sort.by(sortBy).descending()
: Sort.by(sortBy).ascending();
Page<StudentResponse> result = studentService.findAll(
PageRequest.of(page, size, sort)
);
return ResponseEntity.ok(result);
}
// StudentService.java — findAll методыг шинэчлэх
public Page<StudentResponse> findAll(Pageable pageable) {
return repository.findAll(pageable)
.map(StudentResponse::from);
}
Тестлэх:
# 1-р хуудас, 5 бичлэг, GPA-аар буурах
curl "http://localhost:8080/api/v1/students?page=0&size=5&sortBy=gpa&order=desc"