🌱 Spring Framework — ភាសាខ្មែរ
🌱 Java Backend Framework

Spring Framework
មេរៀនពេញលេញ

រៀន Spring Framework ពីចំនុចចាប់ផ្តើមរហូតដល់កម្រឹតខ្ពស់ជាភាសាខ្មែរ។ គ្របដណ្តប់ Spring Core, Spring Boot, MVC, Security, JPA, REST API និង Microservices។

📚12 មេរៀន
💻50+ Code Examples
🎯Beginner → Expert
🇰🇭ភាសាខ្មែរ 100%
01
មេរៀនទី ១ · ចំនុចចាប់ផ្តើម

ការណែនាំ Spring Framework

🌱

Spring Framework គឺជាអ្វី?

Spring គឺជា Java framework ដ៏ល្បីបំផុតសម្រាប់ការសរសេរ enterprise application។ វាត្រូវបានបង្កើតឡើងដើម្បីធ្វើឲ្យការអភិវឌ្ឍ Java ងាយស្រួលជាងមុន។

ហេតុអ្វីត្រូវប្រើ Spring?

Spring ផ្ដល់នូវ dependency injection, AOP, MVC pattern, security, data access ក្នុងទម្រង់ modular ដែលអាចប្រើបន្ថែមបាន។

🏗️

Spring Ecosystem

Spring Boot, Spring MVC, Spring Data, Spring Security, Spring Cloud — ជា modules ផ្សេងៗក្នុង Spring ecosystem។

📌 ចំណាំ: Spring Framework ត្រូវបានបង្កើតឡើងដោយ Rod Johnson ក្នុងឆ្នាំ 2003 ហើយបច្ចុប្បន្នគ្រប់គ្រងដោយ VMware (Pivotal)។ Version ថ្មីបំផុតគឺ Spring 6.x ។

🔧 Setup Project ជាមួយ Maven

ដើម្បីចាប់ផ្ដើម Spring project, អ្នកត្រូវការ Java 17+ និង Maven/Gradle។

pom.xml — Spring Boot Dependency XML
<!-- pom.xml -->
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.2.0</version>
</parent>

<dependencies>
  <!-- Spring Boot Web (MVC + REST) -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!-- Spring Data JPA -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
</dependencies>
02
មេរៀនទី ២ · Core Concept

IoC Container & Dependency Injection

🔄 IoC (Inversion of Control) គឺជាអ្វី?

IoC មានន័យថា "ការបញ្ច្រាសសិទ្ធិគ្រប់គ្រង"។ ជំនួសឲ្យ object បង្កើត dependency ខ្លួនឯង, Spring Container ទទួលខុសត្រូវបង្កើតនិងគ្រប់គ្រង object ទាំងអស់ជំនួស។

💉

Constructor Injection

Inject dependency តាមរយៈ constructor — វិធីល្អបំផុតដែល Spring ណែនាំ។

🔧

Setter Injection

Inject dependency តាម setter method — ល្អសម្រាប់ optional dependencies។

🏷️

Field Injection

Inject ដោយប្រើ @Autowired ដោយផ្ទាល់លើ field — ងាយប្រើប៉ុន្តែ Spring មិនណែនាំ។

Constructor Injection (Best Practice) Java
// ❌ មិនល្អ — Object បង្កើត dependency ខ្លួនឯង
public class OrderService {
  private EmailService emailService = new EmailService(); // tight coupling!
}

// ✅ ល្អ — Constructor Injection (IoC principle)
@Service
public class OrderService {

  private final EmailService emailService;
  private final PaymentService paymentService;

  // Spring inject dependencies តាម constructor ដោយស្វ័យប្រវត្តិ
  public OrderService(EmailService emailService,
                       PaymentService paymentService) {
    this.emailService = emailService;
    this.paymentService = paymentService;
  }

  public void placeOrder(Order order) {
    paymentService.processPayment(order);
    emailService.sendConfirmation(order);
  }
}
💡 គន្លឹះ: ប្រើ final field ជាមួយ Constructor Injection ដើម្បីធានាថា dependency មិនអាចផ្លាស់ប្ដូរបន្ទាប់ពី inject។ Lombok @RequiredArgsConstructor ក៏អាចកាត់ code ដ៏ច្រើន។
ប្រើ Lombok @RequiredArgsConstructor Java
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor // Lombok auto-generates constructor
public class OrderService {

  private final EmailService emailService;
  private final PaymentService paymentService;

  // Constructor ត្រូវបាន generate ដោយ Lombok ស្វ័យប្រវត្តិ
}
03
មេរៀនទី ៣ · Core Concept

Spring Beans & Application Context

🫘 Spring Bean គឺជាអ្វី?

Bean គឺជា object ណាមួយដែល Spring IoC Container គ្រប់គ្រង។ Spring Container ទទួលខុសត្រូវចំពោះ lifecycle ទាំងមូលរបស់ bean: ការបង្កើត, ការ configure និងការ destroy។

Scope ន័យ ករណីប្រើ
singleton Bean មួយ instance (default) Services, Repositories
prototype Bean ថ្មីរៀងរាល់ request Stateful objects
request Bean ១ ក្នុង HTTP request Web-layer beans
session Bean ១ ក្នុង HTTP session User session data
Bean Definition & Configuration Java
// 1. Java-based Configuration
@Configuration
public class AppConfig {

  @Bean
  public DataSource dataSource() {
    HikariDataSource ds = new HikariDataSource();
    ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
    ds.setUsername("root");
    ds.setPassword("password");
    return ds;
  }

  @Bean
  @Scope("prototype") // ថ្មីរៀងរាល់ request
  public ReportGenerator reportGenerator() {
    return new ReportGenerator();
  }
}

// 2. Bean Lifecycle Callbacks
@Component
public class DatabaseInitializer {

  @PostConstruct // ហៅបន្ទាប់ bean inject រួច
  public void init() {
    System.out.println("Database initialized!");
  }

  @PreDestroy // ហៅនៅពេល application shutdown
  public void cleanup() {
    System.out.println("Cleaning up resources...");
  }
}
04
មេរៀនទី ៤ · Core Concept

Spring Annotations សំខាន់ៗ

Annotation ន័យ ប្រើក្នុង Layer
@Component Generic Spring bean ណាក៏បាន
@Service Business logic layer Service Layer
@Repository Data access layer + exception translation DAO/Repository
@Controller Web MVC controller Web Layer
@RestController @Controller + @ResponseBody REST API
@Configuration Java config class Config
@Autowired Auto-inject dependency ណាក៏បាន
@Value Inject properties value ណាក៏បាន
@Transactional Database transaction management Service/Repository
Annotations ក្នុង Layered Architecture Java
// === Controller Layer ===
@RestController
@RequestMapping("/api/users")
public class UserController {

  private final UserService userService;

  public UserController(UserService userService) {
    this.userService = userService;
  }

  @GetMapping
  public List<User> getAllUsers() {
    return userService.findAll();
  }
}

// === Service Layer ===
@Service
@Transactional
public class UserService {

  private final UserRepository userRepository;

  public UserService(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  public List<User> findAll() {
    return userRepository.findAll();
  }
}

// === Repository Layer ===
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
  // Spring Data auto-implements CRUD methods
}
05
មេរៀនទី ៥ · Spring Boot

Spring Boot — Auto-Configuration

🚀 Spring Boot ដោះស្រាយអ្វី?

  • Auto-configure dependencies ដោយស្វ័យប្រវត្តិ
  • Embedded server (Tomcat/Jetty) — មិនចាំបាច់ deploy WAR ដាច់ដោយឡែក
  • Production-ready features (Actuator, Metrics, Health checks)
  • Opinionated defaults ដើម្បីចាប់ផ្ដើមលឿន
Main Application Class Java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// @SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan
@SpringBootApplication
public class DemoApplication {

  public static void main(String[] args) {
    // ចាប់ផ្ដើម embedded Tomcat server
    SpringApplication.run(DemoApplication.class, args);
  }
}
application.properties — Configuration Properties
# Server Port
server.port=8080

# Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA / Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# Logging Level
logging.level.org.springframework=INFO
logging.level.com.example=DEBUG
1

ទៅ start.spring.io

ចូលទៅ https://start.spring.io ហើយជ្រើស dependencies ដែលត្រូវការ (Web, JPA, Security, etc.)

2

Generate & Download

ចុច Generate ដើម្បីទាញ ZIP file ហើយ extract វា។

3

Import ទៅ IntelliJ/Eclipse

បើក IDE ហើយ import project ជា Maven/Gradle project។

4

Run Application

Run DemoApplication.java ឬ mvn spring-boot:run

06
មេរៀនទី ៦ · Web Layer

Spring MVC — Web Controller

🌐 Spring MVC Request Flow

នៅពេល HTTP request ចូលមក, Spring MVC ដំណើរការតាមដំណាក់ការដូចខាងក្រោម:

HTTP RequestDispatcherServlet (Front Controller) ↓ HandlerMapping — ស្វែងរក Controller ត្រឹមត្រូវ ↓ @Controller Method ↓ ModelAndView ← Business Logic ↓ ViewResolver (Thymeleaf/JSON) ↓ HTTP Response
Spring MVC Controller ពេញលេញ Java
@RestController
@RequestMapping("/api/products")
@CrossOrigin(origins = "*") // Allow CORS
public class ProductController {

  private final ProductService productService;

  public ProductController(ProductService productService) {
    this.productService = productService;
  }

  // GET /api/products — ទទួលបញ្ជី products ទាំងអស់
  @GetMapping
  public ResponseEntity<List<Product>> getAllProducts() {
    return ResponseEntity.ok(productService.findAll());
  }

  // GET /api/products/5 — ទទួល product ១
  @GetMapping("/{id}")
  public ResponseEntity<Product> getById(@PathVariable Long id) {
    return productService.findById(id)
      .map(ResponseEntity::ok)
      .orElse(ResponseEntity.notFound().build());
  }

  // POST /api/products — បង្កើត product ថ្មី
  @PostMapping
  public ResponseEntity<Product> create(
      @Valid @RequestBody ProductRequest request) {
    Product created = productService.create(request);
    return ResponseEntity.status(201).body(created);
  }

  // PUT /api/products/5 — update product
  @PutMapping("/{id}")
  public ResponseEntity<Product> update(
      @PathVariable Long id,
      @Valid @RequestBody ProductRequest request) {
    return ResponseEntity.ok(productService.update(id, request));
  }

  // DELETE /api/products/5 — លុប product
  @DeleteMapping("/{id}")
  public ResponseEntity<Void> delete(@PathVariable Long id) {
    productService.deleteById(id);
    return ResponseEntity.noContent().build();
  }

  // GET /api/products/search?name=phone&minPrice=100
  @GetMapping("/search")
  public List<Product> search(
      @RequestParam(required = false) String name,
      @RequestParam(defaultValue = "0") Double minPrice) {
    return productService.search(name, minPrice);
  }
}
07
មេរៀនទី ៧ · Database

Spring Data JPA — Database Access

Entity Class — ពិណ័រ Database Table Java
@Entity
@Table(name = "products")
@Data                   // Lombok: getters, setters, toString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(nullable = false, length = 200)
  private String name;

  @Column(precision = 10, scale = 2)
  private BigDecimal price;

  @Column(name = "stock_quantity")
  private Integer stockQuantity;

  @ManyToOne
  @JoinColumn(name = "category_id")
  private Category category;

  @CreatedDate
  @Column(updatable = false)
  private LocalDateTime createdAt;
}
JPA Repository — CRUD + Custom Queries Java
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {

  // Spring Data auto-generates query from method name!
  List<Product> findByNameContainingIgnoreCase(String name);

  List<Product> findByPriceBetween(BigDecimal min, BigDecimal max);

  List<Product> findByCategoryNameAndStockQuantityGreaterThan(
    String categoryName, Integer quantity);

  // Custom JPQL Query
  @Query("SELECT p FROM Product p WHERE p.price > :minPrice ORDER BY p.price DESC")
  List<Product> findExpensiveProducts(@Param("minPrice") BigDecimal minPrice);

  // Native SQL Query
  @Query(value = "SELECT * FROM products WHERE YEAR(created_at) = :year",
         nativeQuery = true)
  List<Product> findByYear(@Param("year") int year);

  // Pagination
  Page<Product> findByCategoryId(Long categoryId, Pageable pageable);
}
💡 Pagination Example:
productRepository.findAll(PageRequest.of(0, 10, Sort.by("price").descending()))
ទទួលបាន 10 products ដំបូង តម្រៀបតម្លៃ ពីខ្ពស់ទៅទាប។
08
មេរៀនទី ៨ · Security

Spring Security & JWT Authentication

🔐

Authentication

ផ្ទៀងផ្ទាត់ "នរណាមួយ?" — Login ជាមួយ username/password ហើយទទួល JWT token។

🛡️

Authorization

ចម្លើយ "អ្នកទទួលបានសិទ្ធិអ្វី?" — ROLE_USER, ROLE_ADMIN control access។

🎟️

JWT Token

JSON Web Token — stateless authentication ដែលប្រើ encode user info ទៅក្នុង token ។

Security Configuration ជាមួយ JWT Java
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

  private final JwtAuthFilter jwtAuthFilter;
  private final UserDetailsService userDetailsService;

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
      .csrf(AbstractHttpConfigurer::disable) // Disable CSRF for REST API
      .sessionManagement(session -> session
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // JWT = Stateless
      .authorizeHttpRequests(auth -> auth
        // Public endpoints
        .requestMatchers("/api/auth/**").permitAll()
        .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
        // Admin only
        .requestMatchers("/api/admin/**").hasRole("ADMIN")
        // Require authentication for everything else
        .anyRequest().authenticated())
      .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(); // Bcrypt hash password
  }
}
JWT Service — Generate & Validate Token Java
@Service
public class JwtService {

  @Value("${jwt.secret}")
  private String secretKey;

  @Value("${jwt.expiration}")
  private long jwtExpiration;

  public String generateToken(UserDetails userDetails) {
    return Jwts.builder()
      .setSubject(userDetails.getUsername())
      .setIssuedAt(new Date())
      .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
      .signWith(getSignKey(), SignatureAlgorithm.HS256)
      .compact();
  }

  public boolean isTokenValid(String token, UserDetails userDetails) {
    final String username = extractUsername(token);
    return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
  }
}
09
មេរៀនទី ៩ · Advanced

REST API Best Practices & Exception Handling

Global Exception Handler Java
@RestControllerAdvice // Handle exceptions globally for all controllers
public class GlobalExceptionHandler {

  // Resource not found
  @ExceptionHandler(ResourceNotFoundException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
    return new ErrorResponse(404, ex.getMessage(), LocalDateTime.now());
  }

  // Validation errors (@Valid)
  @ExceptionHandler(MethodArgumentNotValidException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getFieldErrors().forEach(error ->
      errors.put(error.getField(), error.getDefaultMessage()));
    return errors;
  }

  // Unauthorized
  @ExceptionHandler(AccessDeniedException.class)
  @ResponseStatus(HttpStatus.FORBIDDEN)
  public ErrorResponse handleForbidden(AccessDeniedException ex) {
    return new ErrorResponse(403, "Access denied", LocalDateTime.now());
  }
}

// DTO Validation Example
public record ProductRequest(
  @NotBlank(message = "ឈ្មោះមិនអាចទទេ")
  @Size(max = 200, message = "ឈ្មោះវែងពេក")
  String name,

  @NotNull(message = "តម្លៃចាំបាច់")
  @Positive(message = "តម្លៃត្រូវតែជាចំនួនវិជ្ជមាន")
  BigDecimal price,

  @Min(value = 0, message = "ស្តុកមិនអាចតិចជាង 0")
  Integer stockQuantity
) {}
10
មេរៀនទី ១០ · Advanced

AOP — Aspect Oriented Programming

🎯 AOP គឺជាអ្វី?

AOP (Aspect-Oriented Programming) អនុញ្ញាតឲ្យអ្នកបញ្ចូល cross-cutting concerns (Logging, Security, Transaction, Caching) ទៅក្នុង code ដោយមិនចាំបាច់ copy-paste logic ក្នុងគ្រប់ method។

@Aspect @Before @After @Around Pointcut JoinPoint
Logging Aspect — ចូល/ចេញ method ដោយស្វ័យប្រវត្តិ Java
@Aspect
@Component
public class LoggingAspect {

  private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

  // Pointcut: apply to all methods in service package
  @Pointcut("execution(* com.example.service.*.*(..))")
  public void serviceMethods() {}

  @Before("serviceMethods()")
  public void logBefore(JoinPoint joinPoint) {
    log.info("▶ Calling: {} with args: {}",
      joinPoint.getSignature().getName(),
      Arrays.toString(joinPoint.getArgs()));
  }

  @Around("serviceMethods()")
  public Object measureExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = pjp.proceed(); // Execute the actual method
    long duration = System.currentTimeMillis() - start;
    log.info("⏱ {} completed in {}ms", pjp.getSignature().getName(), duration);
    return result;
  }

  // Custom annotation-based pointcut
  @Around("@annotation(com.example.annotation.Cacheable)")
  public Object cacheResult(ProceedingJoinPoint pjp) throws Throwable {
    // Implement caching logic here
    return pjp.proceed();
  }
}
11
មេរៀនទី ១១ · Expert Level

Spring Cloud Microservices

🏗️ Microservices Architecture: ជំនួសឲ្យ monolithic application មួយ, Microservices បំបែក application ជា services តូចៗ ដែលដំណើរការ និង deploy ដោយឯករាជ្យ។
🗺️

Eureka Service Discovery

Services ចុះឈ្មោះខ្លួននៅ Eureka Server ហើយ services ផ្សេងអាចស្វែងរកគ្នាបាន។

🚪

API Gateway

ច្រកតែមួយសម្រាប់ client requests ទាំងអស់ — routing, load balancing, auth។

⚙️

Config Server

Centralize configuration ពី Git repository សម្រាប់ services ទាំងអស់។

🔄

Feign Client

HTTP client ដ៏ងាយស្រួលសម្រាប់ call services ផ្សេង ដូចជាហៅ local method។

Feign Client — Inter-service Communication Java
// Order Service calling Inventory Service via Feign
@FeignClient(name = "inventory-service", fallback = InventoryFallback.class)
public interface InventoryClient {

  @GetMapping("/api/inventory/{productId}")
  InventoryResponse checkStock(@PathVariable Long productId);

  @PostMapping("/api/inventory/reserve")
  void reserveItems(@RequestBody ReservationRequest request);
}

// Fallback when Inventory Service is down (Circuit Breaker)
@Component
public class InventoryFallback implements InventoryClient {

  @Override
  public InventoryResponse checkStock(Long productId) {
    return new InventoryResponse(productId, 0, false); // Default: out of stock
  }
}

// Order Service using Feign Client
@Service
@RequiredArgsConstructor
public class OrderService {

  private final InventoryClient inventoryClient;
  private final OrderRepository orderRepository;

  @Transactional
  public Order createOrder(OrderRequest request) {
    // Check inventory in another microservice
    InventoryResponse inventory = inventoryClient.checkStock(request.getProductId());

    if (!inventory.isAvailable()) {
      throw new InsufficientStockException("ស្តុកអស់ហើយ!");
    }

    inventoryClient.reserveItems(new ReservationRequest(request));
    return orderRepository.save(buildOrder(request));
  }
}
12
មេរៀនទី ១២ · Production

Testing, Docker & Deployment

Unit Test ជាមួយ JUnit 5 + Mockito Java
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {

  @Mock
  private ProductRepository productRepository;

  @InjectMocks
  private ProductService productService;

  @Test
  @DisplayName("ត្រូវ return product នៅពេល ID ត្រឹមត្រូវ")
  void shouldReturnProductWhenIdExists() {
    // Arrange
    Product mockProduct = Product.builder()
      .id(1L).name("iPhone 15").price(new BigDecimal("999.99"))
      .build();

    Mockito.when(productRepository.findById(1L))
      .thenReturn(Optional.of(mockProduct));

    // Act
    Product result = productService.findById(1L);

    // Assert
    assertNotNull(result);
    assertEquals("iPhone 15", result.getName());
    Mockito.verify(productRepository, Mockito.times(1)).findById(1L);
  }

  @Test
  @DisplayName("ត្រូវ throw exception នៅពេល ID មិនពិត")
  void shouldThrowExceptionWhenNotFound() {
    Mockito.when(productRepository.findById(99L))
      .thenReturn(Optional.empty());

    assertThrows(ResourceNotFoundException.class,
      () -> productService.findById(99L));
  }
}
Dockerfile — Package Spring Boot App Dockerfile
# Stage 1: Build
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# Stage 2: Run (smaller image)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app

# Create non-root user for security
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

COPY --from=builder /app/target/*.jar app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "-Xmx512m", "app.jar"]
docker-compose.yml — App + Database YAML
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/mydb
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: secret
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mysql:8
    environment:
      MYSQL_DATABASE: mydb
      MYSQL_ROOT_PASSWORD: secret
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      retries: 5

volumes:
  mysql-data:

🎓 ការរីកចម្រើនក្នុងការសិក្សា

Spring Core & IoCExpert
Spring BootExpert
Spring MVC / REST APIAdvanced
Spring Data JPAAdvanced
Spring Security & JWTIntermediate
Microservices & CloudAdvanced

🎉 អបអរសាទរ! អ្នកបានបញ្ចប់ Spring Framework

  • ✅ IoC Container & Dependency Injection
  • ✅ Spring Boot Auto-Configuration
  • ✅ RESTful API ជាមួយ Spring MVC
  • ✅ Database Access ជាមួយ Spring Data JPA
  • ✅ Security ជាមួយ JWT Authentication
  • ✅ AOP — Cross-cutting Concerns
  • ✅ Microservices ជាមួយ Spring Cloud
  • ✅ Testing & Docker Deployment