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 เสร็จ
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();
}
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 ให้อัตโนมัติ
}
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 Safety | Strong (Compile-time) | Weak (TS ช่วยได้) | Strong (Compile-time) |
| Learning Curve | สูง | ต่ำ | ปานกลาง |
| เหมาะกับ | Enterprise, Large-scale | Startup, Real-time | High-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
- OOP Concepts: Encapsulation, Inheritance, Polymorphism, Abstraction
- Collections Framework: List, Set, Map ความแตกต่างและเมื่อไหร่ควรใช้อะไร
- Generics และ Wildcards (<? extends T> vs <? super T>)
- Stream API และ Lambda Expressions
- Concurrency: Thread, Synchronized, ExecutorService, CompletableFuture
- Memory Management: Heap vs Stack, Garbage Collection
- Exception Handling: Checked vs Unchecked Exceptions
- Java 21+ ฟีเจอร์ใหม่: Virtual Threads, Records, Sealed Classes
Spring Framework
- IoC Container และ Dependency Injection (Constructor vs Field vs Setter)
- Bean Lifecycle และ Scopes (Singleton, Prototype, Request, Session)
- AOP (Aspect-Oriented Programming) ใช้เมื่อไหร่ อย่างไร
- @Transactional ทำงานอย่างไร Propagation levels
- Spring Security: Authentication vs Authorization, JWT integration
- Spring Boot Auto-configuration ทำงานอย่างไร
- Profiles และ Configuration Management
- Caching strategies (@Cacheable, @CacheEvict)
Database
- JPA vs Hibernate: ความสัมพันธ์
- Entity Relationships: @OneToMany, @ManyToMany, FetchType
- N+1 Problem และวิธีแก้ (JOIN FETCH, @EntityGraph)
- Database Migration: Flyway, Liquibase
- Connection Pool: HikariCP configuration
Design & Architecture
- REST API Best Practices: HTTP Methods, Status Codes, Versioning
- Design Patterns ที่ใช้บ่อย: Singleton, Factory, Strategy, Observer
- SOLID Principles
- Microservices vs Monolith: เมื่อไหร่ควรแยก
- Testing: Unit vs Integration vs E2E, TDD
สรุป
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 ครับ!
