Home > Blog > tech

Java และ Spring Boot คืออะไร? สอนสร้าง Backend API ด้วย Spring Boot 3 สำหรับ Developer 2026

java spring boot guide
Java Spring Boot Guide 2026
2026-04-08 | tech | 3500 words

Java ยังคงเป็นภาษาโปรแกรมที่ได้รับความนิยมสูงสุดในโลกองค์กรขนาดใหญ่ ธนาคาร ระบบการเงิน และบริษัทเทคโนโลยีชั้นนำทั่วโลกยังคงเลือกใช้ Java เป็นภาษาหลักสำหรับ Backend ในปี 2026 Java ไม่ได้หยุดพัฒนา แต่กลับก้าวหน้าอย่างก้าวกระโดดด้วย Virtual Threads, Pattern Matching, Records และ Sealed Classes ที่ทำให้เขียนโค้ดได้สั้นกระชับและมีประสิทธิภาพมากยิ่งขึ้น

Spring Boot คือ Framework ที่ทำให้การพัฒนา Java Application กลายเป็นเรื่องง่าย ลดความซับซ้อนของ Configuration ทำให้สามารถสร้าง REST API, Microservices และ Web Application ได้อย่างรวดเร็ว บทความนี้จะสอนตั้งแต่พื้นฐาน Java สมัยใหม่ จนถึงการสร้าง Production-ready API ด้วย Spring Boot 3

ทำไม Java ยังสำคัญในปี 2026?

หลายคนอาจสงสัยว่าทำไม Java ที่มีอายุกว่า 30 ปี ยังคงเป็นที่ต้องการอยู่ คำตอบคือ Java มีข้อได้เปรียบหลายประการที่ภาษาใหม่ยังตามไม่ทัน

ตลาดงานขนาดใหญ่

Java ครองตลาดงาน Enterprise มาอย่างยาวนาน ระบบธนาคาร ประกันภัย สายการบิน โทรคมนาคม และหน่วยงานราชการส่วนใหญ่ใช้ Java เป็นหลัก นั่นหมายความว่ามีตำแหน่งงาน Java Developer เปิดรับอยู่ตลอดเวลา ทั้งการดูแลระบบเดิมและพัฒนาระบบใหม่

ประสิทธิภาพที่พิสูจน์แล้ว

JVM (Java Virtual Machine) ได้รับการปรับปรุงมากว่า 30 ปี มี JIT Compiler ที่ฉลาด Garbage Collector หลากหลายรูปแบบ (G1, ZGC, Shenandoah) และสามารถ Scale รองรับผู้ใช้งานหลายล้านคนได้อย่างเสถียร บริษัทอย่าง Netflix, LinkedIn, Amazon และ Google ล้วนใช้ Java ในระบบหลัก

Ecosystem ที่สมบูรณ์

Java มี Library และ Framework มากมายพร้อมใช้งาน ตั้งแต่ Spring, Hibernate, Apache Kafka, Elasticsearch Client ไปจนถึง Machine Learning Libraries ชุมชน Java เป็นหนึ่งในชุมชนที่ใหญ่ที่สุดในโลก มีเอกสารและตัวอย่างโค้ดมากมาย

Cross-platform ที่แท้จริง

ด้วยหลักการ Write Once Run Anywhere ของ JVM ทำให้ Java Application สามารถทำงานได้บนทุก Platform ไม่ว่าจะเป็น Linux, Windows, macOS หรือแม้แต่ระบบ Embedded โดยไม่ต้องแก้ไขโค้ด

Java 21+ ฟีเจอร์ใหม่ที่ต้องรู้

Java ได้รับการอัปเดตทุก 6 เดือน โดย Java 21 เป็น LTS (Long-Term Support) ตัวล่าสุดที่มีฟีเจอร์ใหม่น่าสนใจมากมาย

Virtual Threads (Project Loom)

Virtual Threads คือการปฏิวัติการเขียนโปรแกรม Concurrent ใน Java เดิม Java ใช้ Platform Threads ซึ่งแต่ละ Thread ใช้หน่วยความจำประมาณ 1MB ทำให้สร้างได้หลักพัน แต่ Virtual Threads ใช้หน่วยความจำน้อยมาก สามารถสร้างได้หลายล้าน Thread พร้อมกัน

// แบบเก่า — Platform Thread (หนัก)
Thread thread = new Thread(() -> {
    System.out.println("Hello from platform thread");
});
thread.start();

// แบบใหม่ — Virtual Thread (เบา สร้างได้เป็นล้าน)
Thread.startVirtualThread(() -> {
    System.out.println("Hello from virtual thread");
});

// ใช้กับ ExecutorService
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 100_000; i++) {
        executor.submit(() -> {
            // แต่ละ task ได้ Virtual Thread ของตัวเอง
            Thread.sleep(Duration.ofSeconds(1));
            return "done";
        });
    }
} // executor จะ close อัตโนมัติ รอทุก task เสร็จ
Virtual Threads สำคัญอย่างไร? สำหรับ Web Server แบบเก่าที่ใช้ Thread Pool 200 threads คุณรองรับได้แค่ 200 requests พร้อมกัน แต่ด้วย Virtual Threads คุณสามารถรองรับหลายหมื่น requests พร้อมกันโดยไม่ต้องเปลี่ยนโค้ดเป็น Reactive แบบ Spring WebFlux

Records — Data Class แบบกระชับ

Records ช่วยลดโค้ด Boilerplate ที่ต้องเขียนสำหรับ Data Class (POJO) จากเดิมที่ต้องเขียน Constructor, Getters, equals(), hashCode(), toString() เอง Records ทำให้ทุกอย่างอัตโนมัติในบรรทัดเดียว

// แบบเก่า — ต้องเขียนหลายสิบบรรทัด
public class User {
    private final String name;
    private final String email;
    private final int age;

    public User(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public String getName() { return name; }
    public String getEmail() { return email; }
    public int getAge() { return age; }
    // + equals(), hashCode(), toString() อีกหลายบรรทัด
}

// แบบใหม่ — Record (บรรทัดเดียว!)
public record User(String name, String email, int age) {}

// ใช้งาน
var user = new User("สมชาย", "somchai@email.com", 30);
System.out.println(user.name());  // "สมชาย"
System.out.println(user);          // User[name=สมชาย, email=somchai@email.com, age=30]

Pattern Matching — Switch ที่ฉลาดขึ้น

// Pattern Matching for instanceof
Object obj = "Hello";
if (obj instanceof String s) {
    System.out.println(s.toUpperCase()); // ไม่ต้อง cast แล้ว
}

// Pattern Matching for switch
static String formatValue(Object obj) {
    return switch (obj) {
        case Integer i when i > 0 -> "Positive: " + i;
        case Integer i            -> "Non-positive: " + i;
        case String s             -> "String: " + s;
        case null                 -> "null value";
        default                   -> "Unknown: " + obj;
    };
}

// Sealed Classes + Pattern Matching
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}

static double area(Shape shape) {
    return switch (shape) {
        case Circle c    -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t  -> 0.5 * t.base() * t.height();
    }; // compiler รู้ว่าครบทุก case แล้ว ไม่ต้อง default
}

Sealed Classes — ควบคุม Inheritance

Sealed Classes ช่วยให้คุณกำหนดได้ว่า Class ไหนสามารถ Extend หรือ Implement ได้ ทำให้ Compiler ช่วยตรวจสอบความครบถ้วนของ Pattern Matching และป้องกันการสร้าง Subclass ที่ไม่ต้องการ

// กำหนดว่ามีแค่ 3 ชนิดเท่านั้นที่ implement ได้
public sealed interface Payment
    permits CreditCard, BankTransfer, QRCode {}

public record CreditCard(String cardNumber, String cvv) implements Payment {}
public record BankTransfer(String bankCode, String accountNo) implements Payment {}
public record QRCode(String qrData) implements Payment {}

Spring Boot 3 คืออะไร?

Spring Boot เป็น Framework ที่สร้างขึ้นบน Spring Framework เพื่อลดความซับซ้อนของการตั้งค่า (Convention over Configuration) ทำให้สามารถสร้าง Application ที่พร้อมใช้งานได้ภายในไม่กี่นาที Spring Boot 3 ใช้ Jakarta EE 10 แทน Java EE เดิม และต้องการ Java 17 เป็นอย่างน้อย

เริ่มต้นด้วย Spring Initializr

วิธีง่ายที่สุดในการสร้างโปรเจกต์ Spring Boot คือใช้ start.spring.io

# วิธีที่ 1: ใช้เว็บ start.spring.io
# เลือก: Maven/Gradle, Java 21, Spring Boot 3.3+
# Dependencies: Spring Web, Spring Data JPA, H2 Database, Lombok, Validation

# วิธีที่ 2: ใช้ Spring Boot CLI
spring init --dependencies=web,data-jpa,h2,lombok,validation   --java-version=21 --type=maven-project my-api

# วิธีที่ 3: ใช้ curl
curl https://start.spring.io/starter.zip   -d dependencies=web,data-jpa,h2,lombok,validation   -d javaVersion=21 -d type=maven-project   -d groupId=com.example -d artifactId=my-api   -o my-api.zip

โครงสร้างโปรเจกต์ Spring Boot

my-api/
├── src/
│   ├── main/
│   │   ├── java/com/example/myapi/
│   │   │   ├── MyApiApplication.java          # Main class
│   │   │   ├── controller/                    # REST Controllers
│   │   │   │   └── UserController.java
│   │   │   ├── service/                       # Business Logic
│   │   │   │   └── UserService.java
│   │   │   ├── repository/                    # Data Access
│   │   │   │   └── UserRepository.java
│   │   │   ├── model/                         # Entities / DTOs
│   │   │   │   ├── User.java
│   │   │   │   └── UserDTO.java
│   │   │   ├── config/                        # Configuration
│   │   │   │   └── SecurityConfig.java
│   │   │   └── exception/                     # Error Handling
│   │   │       └── GlobalExceptionHandler.java
│   │   └── resources/
│   │       ├── application.yml                # Config file
│   │       ├── application-dev.yml
│   │       └── application-prod.yml
│   └── test/java/com/example/myapi/
│       ├── controller/
│       │   └── UserControllerTest.java
│       └── service/
│           └── UserServiceTest.java
├── pom.xml                                    # Maven dependencies
└── Dockerfile

pom.xml — Dependencies หลัก

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
</parent>

<properties>
    <java.version>21</java.version>
</properties>

<dependencies>
    <!-- Web (REST API) -->
    <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>

    <!-- Validation -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- PostgreSQL -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- H2 for dev -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- OpenAPI / Swagger -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.5.0</version>
    </dependency>

    <!-- Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

สร้าง REST API ด้วย Spring Boot

Main Application Class

package com.example.myapi;

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

@SpringBootApplication  // รวม @Configuration + @EnableAutoConfiguration + @ComponentScan
public class MyApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApiApplication.class, args);
    }
}

Entity — กำหนดโครงสร้างข้อมูล

package com.example.myapi.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "users")
@Data                    // Lombok: getter, setter, toString, equals, hashCode
@NoArgsConstructor       // Lombok: constructor ว่าง
@AllArgsConstructor      // Lombok: constructor ทุก field
@Builder                 // Lombok: Builder pattern
public class User {

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

    @NotBlank(message = "ชื่อห้ามว่าง")
    @Size(min = 2, max = 100, message = "ชื่อต้องมี 2-100 ตัวอักษร")
    @Column(nullable = false)
    private String name;

    @NotBlank(message = "อีเมลห้ามว่าง")
    @Email(message = "รูปแบบอีเมลไม่ถูกต้อง")
    @Column(nullable = false, unique = true)
    private String email;

    @Min(value = 1, message = "อายุต้องมากกว่า 0")
    @Max(value = 150, message = "อายุต้องไม่เกิน 150")
    private int age;

    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

Repository — Data Access Layer

package com.example.myapi.repository;

import com.example.myapi.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    // Spring Data JPA สร้าง Query ให้อัตโนมัติจากชื่อ Method!
    Optional<User> findByEmail(String email);

    List<User> findByNameContainingIgnoreCase(String name);

    List<User> findByAgeBetween(int minAge, int maxAge);

    List<User> findByNameContainingAndAgeLessThan(String name, int age);

    boolean existsByEmail(String email);

    // Custom JPQL Query
    @Query("SELECT u FROM User u WHERE u.age >= :minAge ORDER BY u.name")
    List<User> findAdults(@org.springframework.data.repository.query.Param("minAge") int minAge);

    // Native SQL Query
    @Query(value = "SELECT * FROM users WHERE created_at > CURRENT_DATE - INTERVAL '7 days'",
           nativeQuery = true)
    List<User> findRecentUsers();
}
Spring Data JPA Method Naming: แค่ตั้งชื่อ Method ตาม Convention เช่น findByNameAndAge Spring จะสร้าง SQL Query ให้อัตโนมัติ ไม่ต้องเขียน SQL เอง! รองรับ keywords เช่น And, Or, Between, LessThan, GreaterThan, Like, In, OrderBy และอื่นๆ

Service — Business Logic Layer

package com.example.myapi.service;

import com.example.myapi.model.User;
import com.example.myapi.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@RequiredArgsConstructor  // Lombok: inject dependencies ผ่าน constructor
public class UserService {

    private final UserRepository userRepository;  // Constructor Injection (แนะนำ)

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

    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
    }

    public User getUserByEmail(String email) {
        return userRepository.findByEmail(email)
            .orElseThrow(() -> new ResourceNotFoundException("User not found with email: " + email));
    }

    @Transactional
    public User createUser(User user) {
        if (userRepository.existsByEmail(user.getEmail())) {
            throw new DuplicateResourceException("Email already exists: " + user.getEmail());
        }
        return userRepository.save(user);
    }

    @Transactional
    public User updateUser(Long id, User userData) {
        User user = getUserById(id);
        user.setName(userData.getName());
        user.setEmail(userData.getEmail());
        user.setAge(userData.getAge());
        return userRepository.save(user);
    }

    @Transactional
    public void deleteUser(Long id) {
        User user = getUserById(id);
        userRepository.delete(user);
    }

    public List<User> searchUsers(String name) {
        return userRepository.findByNameContainingIgnoreCase(name);
    }
}

Controller — REST API Endpoints

package com.example.myapi.controller;

import com.example.myapi.model.User;
import com.example.myapi.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Tag(name = "User API", description = "จัดการข้อมูลผู้ใช้งาน")
public class UserController {

    private final UserService userService;

    @GetMapping
    @Operation(summary = "ดึงรายชื่อผู้ใช้ทั้งหมด")
    public ResponseEntity<List<User>> getAllUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }

    @GetMapping("/{id}")
    @Operation(summary = "ดึงข้อมูลผู้ใช้ตาม ID")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getUserById(id));
    }

    @PostMapping
    @Operation(summary = "สร้างผู้ใช้ใหม่")
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
        User created = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }

    @PutMapping("/{id}")
    @Operation(summary = "อัปเดตข้อมูลผู้ใช้")
    public ResponseEntity<User> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody User user) {
        return ResponseEntity.ok(userService.updateUser(id, user));
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "ลบผู้ใช้")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }

    @GetMapping("/search")
    @Operation(summary = "ค้นหาผู้ใช้ตามชื่อ")
    public ResponseEntity<List<User>> searchUsers(@RequestParam String name) {
        return ResponseEntity.ok(userService.searchUsers(name));
    }
}

Dependency Injection — หัวใจของ Spring

Dependency Injection (DI) คือหลักการที่ Spring ใช้ในการจัดการการสร้างและเชื่อมต่อ Object ต่างๆ แทนที่จะสร้าง Object เองด้วย new เราบอก Spring ว่าต้องการอะไร แล้ว Spring จะจัดการให้

// วิธีที่ 1: Constructor Injection (แนะนำที่สุด)
@Service
public class OrderService {
    private final UserService userService;
    private final PaymentService paymentService;

    // Spring จะ inject UserService และ PaymentService ให้อัตโนมัติ
    public OrderService(UserService userService, PaymentService paymentService) {
        this.userService = userService;
        this.paymentService = paymentService;
    }
}

// วิธีที่ 2: @Autowired (ไม่แนะนำสำหรับ required dependencies)
@Service
public class OrderService {
    @Autowired
    private UserService userService;  // Field injection — ยากต่อการ test
}

// วิธีที่ 3: ใช้ Lombok @RequiredArgsConstructor (สะดวกที่สุด)
@Service
@RequiredArgsConstructor
public class OrderService {
    private final UserService userService;      // final = required
    private final PaymentService paymentService; // Spring inject ให้อัตโนมัติ
}
ทำไม Constructor Injection ดีกว่า? เพราะทำให้ dependencies เป็น final (immutable), ง่ายต่อการเขียน Unit Test (ส่ง mock ผ่าน constructor), และ Compiler จะบังคับว่าต้องมี dependencies ครบก่อนสร้าง Object

Exception Handling — จัดการ Error อย่างมืออาชีพ

// Custom Exception Classes
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

public class DuplicateResourceException extends RuntimeException {
    public DuplicateResourceException(String message) {
        super(message);
    }
}

// Global Exception Handler
@RestControllerAdvice
public class GlobalExceptionHandler {

    // Record สำหรับ Error Response
    public record ErrorResponse(
        int status, String error, String message, String path, LocalDateTime timestamp
    ) {}

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(
            ResourceNotFoundException ex, HttpServletRequest request) {
        var error = new ErrorResponse(
            404, "Not Found", ex.getMessage(),
            request.getRequestURI(), LocalDateTime.now()
        );
        return ResponseEntity.status(404).body(error);
    }

    @ExceptionHandler(DuplicateResourceException.class)
    public ResponseEntity<ErrorResponse> handleDuplicate(
            DuplicateResourceException ex, HttpServletRequest request) {
        var error = new ErrorResponse(
            409, "Conflict", ex.getMessage(),
            request.getRequestURI(), LocalDateTime.now()
        );
        return ResponseEntity.status(409).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex, HttpServletRequest request) {
        String message = ex.getBindingResult().getFieldErrors().stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .collect(Collectors.joining(", "));
        var error = new ErrorResponse(
            400, "Bad Request", message,
            request.getRequestURI(), LocalDateTime.now()
        );
        return ResponseEntity.badRequest().body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneral(
            Exception ex, HttpServletRequest request) {
        var error = new ErrorResponse(
            500, "Internal Server Error", "เกิดข้อผิดพลาดภายในระบบ",
            request.getRequestURI(), LocalDateTime.now()
        );
        return ResponseEntity.status(500).body(error);
    }
}

Spring Security พื้นฐาน

Spring Security เป็น Framework สำหรับจัดการ Authentication และ Authorization ที่ทรงพลังที่สุดใน Java ecosystem

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())  // ปิด CSRF สำหรับ API
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**").permitAll()  // Public endpoints
                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/v1/users/**").permitAll()
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()  // ที่เหลือต้อง login
            )
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .httpBasic(Customizer.withDefaults());  // Basic Auth

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        var admin = org.springframework.security.core.userdetails.User
            .withUsername("admin")
            .password(encoder.encode("admin123"))
            .roles("ADMIN")
            .build();
        return new InMemoryUserDetailsManager(admin);
    }
}

Swagger/OpenAPI Documentation

การทำเอกสาร API เป็นสิ่งสำคัญมากสำหรับทีม Frontend และ Mobile Developer ที่ต้องใช้ API ของเรา Spring Boot สามารถสร้าง API Documentation อัตโนมัติด้วย SpringDoc OpenAPI

# application.yml
springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha

# เข้าถึงได้ที่:
# Swagger UI: http://localhost:8080/swagger-ui.html
# OpenAPI JSON: http://localhost:8080/v3/api-docs

application.yml — Configuration

# src/main/resources/application.yml
spring:
  application:
    name: my-api
  profiles:
    active: dev

  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:

  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.H2Dialect

  h2:
    console:
      enabled: true
      path: /h2-console

server:
  port: 8080

logging:
  level:
    root: INFO
    com.example.myapi: DEBUG
    org.springframework.security: DEBUG

---
# Production profile
spring:
  config:
    activate:
      on-profile: prod

  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    driver-class-name: org.postgresql.Driver
    username: ${DB_USER}
    password: ${DB_PASS}

  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false

server:
  port: ${SERVER_PORT:8080}

Testing — ทดสอบอย่างมืออาชีพ

Unit Test ด้วย JUnit 5 + Mockito

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    @DisplayName("ดึงผู้ใช้ตาม ID สำเร็จ")
    void getUserById_Success() {
        // Given (เตรียมข้อมูล)
        User mockUser = User.builder()
            .id(1L).name("สมชาย").email("somchai@test.com").age(30)
            .build();
        when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));

        // When (เรียก method ที่ทดสอบ)
        User result = userService.getUserById(1L);

        // Then (ตรวจสอบผลลัพธ์)
        assertNotNull(result);
        assertEquals("สมชาย", result.getName());
        assertEquals("somchai@test.com", result.getEmail());
        verify(userRepository, times(1)).findById(1L);
    }

    @Test
    @DisplayName("ดึงผู้ใช้ตาม ID ไม่พบ — โยน Exception")
    void getUserById_NotFound() {
        when(userRepository.findById(99L)).thenReturn(Optional.empty());

        assertThrows(ResourceNotFoundException.class,
            () -> userService.getUserById(99L));
    }

    @Test
    @DisplayName("สร้างผู้ใช้ใหม่ — email ซ้ำ")
    void createUser_DuplicateEmail() {
        User user = User.builder().email("dup@test.com").build();
        when(userRepository.existsByEmail("dup@test.com")).thenReturn(true);

        assertThrows(DuplicateResourceException.class,
            () -> userService.createUser(user));
    }
}

Integration Test ด้วย @SpringBootTest

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class UserControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private UserRepository userRepository;

    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
    }

    @Test
    @DisplayName("POST /api/v1/users — สร้างผู้ใช้ใหม่สำเร็จ")
    void createUser_Success() throws Exception {
        User user = User.builder()
            .name("สมหญิง").email("somying@test.com").age(25)
            .build();

        mockMvc.perform(post("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(user)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("สมหญิง"))
            .andExpect(jsonPath("$.email").value("somying@test.com"))
            .andExpect(jsonPath("$.id").exists());
    }

    @Test
    @DisplayName("POST /api/v1/users — Validation Error")
    void createUser_ValidationFail() throws Exception {
        User user = User.builder().name("").email("invalid").age(-1).build();

        mockMvc.perform(post("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(user)))
            .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("GET /api/v1/users — ดึงรายชื่อทั้งหมด")
    void getAllUsers() throws Exception {
        userRepository.save(User.builder().name("User1").email("u1@test.com").age(20).build());
        userRepository.save(User.builder().name("User2").email("u2@test.com").age(30).build());

        mockMvc.perform(get("/api/v1/users"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$", hasSize(2)));
    }
}

Docker Deployment

# Dockerfile — Multi-stage build
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml .
COPY mvnw .
COPY .mvn .mvn
RUN ./mvnw dependency:go-offline -B
COPY src src
RUN ./mvnw package -DskipTests -B

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar

# Security: ไม่รันเป็น root
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s   CMD wget -q --spider http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]
# docker-compose.yml
version: "3.8"
services:
  api:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_USER=myuser
      - DB_PASS=mypassword
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

Spring Boot vs Node.js vs Go — เปรียบเทียบ

ด้านSpring Boot (Java)Express (Node.js)Gin (Go)
ประสิทธิภาพสูง (JIT, Virtual Threads)ปานกลาง (Single-threaded)สูงมาก (Native binary)
Startup Timeช้า (2-5 วินาที)เร็ว (<1 วินาที)เร็วมาก (<0.5 วินาที)
Memoryสูง (200MB+)ต่ำ (50MB+)ต่ำมาก (10MB+)
Ecosystemสมบูรณ์ที่สุดมาก (npm)กำลังเติบโต
Type SafetyStrong (Compile-time)Weak (TS ช่วยได้)Strong (Compile-time)
Learning Curveสูงต่ำปานกลาง
เหมาะกับEnterprise, Large-scaleStartup, Real-timeHigh-performance, CLI
ตลาดงานไทยมากที่สุด (ธนาคาร, รัฐ)มาก (Startup)กำลังเพิ่ม

Spring Ecosystem ที่ควรรู้

Spring Cloud — สำหรับ Microservices

Spring Cloud ช่วยจัดการความซับซ้อนของ Microservices Architecture ครอบคลุม Service Discovery (Eureka), Load Balancing, Circuit Breaker (Resilience4j), Config Server, API Gateway และอื่นๆ

Spring Batch — สำหรับ Batch Processing

Spring Batch เป็น Framework สำหรับประมวลผลข้อมูลจำนวนมาก เช่น การ Import ข้อมูลจากไฟล์ CSV หลายล้านรายการ การสร้างรายงาน หรือการ Migrate ข้อมูลระหว่างระบบ

Spring WebFlux — Reactive Programming

Spring WebFlux เป็นทางเลือกของ Spring MVC สำหรับการเขียนโปรแกรมแบบ Reactive (Non-blocking) เหมาะกับระบบที่ต้องรองรับ Connection จำนวนมากพร้อมกัน เช่น Streaming, Chat หรือ Real-time Dashboard แต่ด้วย Virtual Threads ใน Java 21 ทำให้หลายกรณีอาจไม่จำเป็นต้องใช้ WebFlux อีกต่อไป

Spring Data — รองรับหลาย Database

นอกจาก JPA แล้ว Spring Data ยังรองรับ MongoDB, Redis, Elasticsearch, Cassandra, Neo4j และอื่นๆ โดยใช้ Pattern เดียวกันคือ Repository Interface ทำให้เปลี่ยน Database ได้ง่าย

หัวข้อ Interview ที่ต้องเตรียม

สำหรับคนที่กำลังเตรียมสัมภาษณ์ตำแหน่ง Java Developer นี่คือหัวข้อที่มักถูกถามบ่อยที่สุด

Java Core

Spring Framework

Database

Design & Architecture

สรุป

Java และ Spring Boot ยังคงเป็นตัวเลือกอันดับหนึ่งสำหรับ Enterprise Backend Development ในปี 2026 ด้วยฟีเจอร์ใหม่อย่าง Virtual Threads ที่ทำให้ Performance ดีขึ้นมาก Records และ Pattern Matching ที่ทำให้โค้ดกระชับ และ Spring Boot 3 ที่ทำให้การพัฒนา API เป็นเรื่องง่ายดาย

สิ่งสำคัญคือการเรียนรู้แบบ Layer by Layer: เริ่มจาก Java Core ให้แน่น จากนั้นเรียน Spring Boot ทำ REST API สร้าง CRUD Application จากนั้นค่อยขยายไปยัง Security, Testing, Docker Deployment และ Microservices ขอให้ทุกคนสนุกกับการเขียน Java ครับ!


Back to Blog | iCafe Forex | SiamLanCard | Siam2R