🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

স্প্রিং বুট 3 গাইড 2026: REST API, JPA, নিরাপত্তা এবং ভার্চুয়াল থ্রেড

⏱️4 min read  ·  853 words

স্প্রিং বুট হল 2026 সালে এন্টারপ্রাইজ ব্যাকএন্ডের জন্য প্রভাবশালী জাভা ফ্রেমওয়ার্ক। স্প্রিং বুট 3.3, গ্রালভিএম-এর মাধ্যমে নেটিভ কম্পাইলেশন, প্রজেক্ট লুমের মাধ্যমে ভার্চুয়াল থ্রেড এবং স্প্রিং এআই স্টার্টার, জাভা এপিআই তৈরির কাজ কখনোই দ্রুত হয়নি। এই নির্দেশিকা আপনাকে হ্যালো ওয়ার্ল্ড থেকে প্রোডাকশন-গ্রেড REST API-এ নিয়ে যায়।

কেন বসন্ত বুট?

  • স্বয়ংক্রিয় কনফিগারেশন— সংবেদনশীল ডিফল্ট, সাধারণ প্যাটার্নের জন্য শূন্য বয়লারপ্লেট
  • এমবেডেড সার্ভার— Tomcat/Netty অন্তর্ভুক্ত, কোন স্থাপনার WAR ফাইল নেই
  • উৎপাদন-প্রস্তুত— অ্যাকচুয়েটর, মাইক্রোমিটার মেট্রিক্স, স্বাস্থ্য পরীক্ষা অন্তর্নির্মিত
  • GraalVM নেটিভ— নেটিভ বাইনারিতে কম্পাইল করুন, <50ms এ শুরু হয়, 50% কম RAM ব্যবহার করে
  • বিশাল ইকোসিস্টেম— স্প্রিং ডেটা, সিকিউরিটি, ক্লাউড, ব্যাচ, এআই

প্রকল্প সেটআপ

# Spring Initializr (start.spring.io)
# Or via curl:
curl https://start.spring.io/starter.zip   -d dependencies=web,data-jpa,postgresql,security,actuator,validation,lombok   -d type=maven-project   -d language=java   -d javaVersion=21   -d name=techpulse-api   -d groupId=com.techpulse   -d artifactId=api   -o techpulse-api.zip

unzip techpulse-api.zip
cd techpulse-api
mvn spring-boot:run

application.yml কনফিগারেশন

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/techpulse_db
    username: ${DB_USER}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5

  jpa:
    hibernate:
      ddl-auto: validate  # use Flyway/Liquibase for production
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true

  flyway:
    enabled: true
    locations: classpath:db/migration

server:
  port: 8080
  compression:
    enabled: true

management:
  endpoints:
    web:
      exposure:
        include: health,metrics,info,prometheus
  endpoint:
    health:
      show-details: always

সত্তা এবং সংগ্রহস্থল

// User.java
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    @Size(min = 2, max = 50)
    private String name;

    @Column(unique = true, nullable = false)
    @Email
    private String email;

    @Column(nullable = false)
    @JsonIgnore
    private String passwordHash;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role = Role.USER;

    @Column(nullable = false)
    private boolean active = true;

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;

    public enum Role { USER, ADMIN, MODERATOR }
}

// UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
    Page<User> findByActiveTrue(Pageable pageable);
    List<User> findByRole(User.Role role);
    boolean existsByEmail(String email);

    @Query("SELECT u FROM User u WHERE u.name ILIKE %:name%")
    List<User> searchByName(@Param("name") String name);
}

সার্ভিস লেয়ার

// UserService.java
@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class UserService {
    private final UserRepository userRepo;
    private final PasswordEncoder passwordEncoder;

    @Transactional(readOnly = true)
    public Page<UserDTO> listUsers(Pageable pageable) {
        return userRepo.findByActiveTrue(pageable)
            .map(UserDTO::from);
    }

    @Transactional(readOnly = true)
    public UserDTO getUser(Long id) {
        return userRepo.findById(id)
            .map(UserDTO::from)
            .orElseThrow(() -> new ResourceNotFoundException("User not found: " + id));
    }

    public UserDTO createUser(CreateUserRequest request) {
        if (userRepo.existsByEmail(request.email())) {
            throw new ConflictException("Email already exists: " + request.email());
        }
        User user = User.builder()
            .name(request.name())
            .email(request.email())
            .passwordHash(passwordEncoder.encode(request.password()))
            .build();
        User saved = userRepo.save(user);
        log.info("Created user: {} ({})", saved.getEmail(), saved.getId());
        return UserDTO.from(saved);
    }

    public UserDTO updateUser(Long id, UpdateUserRequest request) {
        User user = userRepo.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("User not found"));
        if (request.name() != null) user.setName(request.name());
        return UserDTO.from(userRepo.save(user));
    }

    public void deleteUser(Long id) {
        User user = userRepo.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("User not found"));
        user.setActive(false);  // soft delete
        userRepo.save(user);
    }
}

REST কন্ট্রোলার

// UserController.java
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Validated
@Slf4j
public class UserController {
    private final UserService userService;

    @GetMapping
    public ResponseEntity<Page<UserDTO>> listUsers(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size,
        @RequestParam(defaultValue = "createdAt") String sortBy,
        @RequestParam(defaultValue = "DESC") Sort.Direction direction
    ) {
        Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
        return ResponseEntity.ok(userService.listUsers(pageable));
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getUser(id));
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(
        @Valid @RequestBody CreateUserRequest request
    ) {
        UserDTO created = userService.createUser(request);
        URI location = URI.create("/api/v1/users/" + created.id());
        return ResponseEntity.created(location).body(created);
    }

    @PatchMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(
        @PathVariable Long id,
        @Valid @RequestBody UpdateUserRequest request
    ) {
        return ResponseEntity.ok(userService.updateUser(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

// DTOs (Java records)
public record UserDTO(Long id, String name, String email, User.Role role, LocalDateTime createdAt) {
    public static UserDTO from(User user) {
        return new UserDTO(user.getId(), user.getName(), user.getEmail(),
                           user.getRole(), user.getCreatedAt());
    }
}

public record CreateUserRequest(
    @NotBlank @Size(min=2, max=50) String name,
    @NotBlank @Email String email,
    @NotBlank @Size(min=8) String password
) {}

গ্লোবাল এক্সেপশন হ্যান্ডলার

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ProblemDetail handleNotFound(ResourceNotFoundException ex) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
        problem.setTitle("Resource Not Found");
        return problem;
    }

    @ExceptionHandler(ConflictException.class)
    public ProblemDetail handleConflict(ConflictException ex) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
        Map<String, String> errors = ex.getBindingResult().getFieldErrors().stream()
            .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed");
        problem.setProperty("errors", errors);
        return problem;
    }
}

JWT এর সাথে বসন্ত নিরাপত্তা

// JwtService.java
@Service
public class JwtService {
    @Value("${app.jwt.secret}")
    private String secret;
    private static final long EXPIRY = 86400000L; // 24h

    public String generateToken(UserDetails user) {
        return Jwts.builder()
            .subject(user.getUsername())
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + EXPIRY))
            .signWith(Keys.hmacShaKeyFor(secret.getBytes()))
            .compact();
    }

    public String extractUsername(String token) {
        return Jwts.parser()
            .verifyWith(Keys.hmacShaKeyFor(secret.getBytes()))
            .build()
            .parseSignedClaims(token)
            .getPayload()
            .getSubject();
    }
}

ভার্চুয়াল থ্রেড (জাভা 21 + স্প্রিং বুট 3.3)

# application.yml — enable virtual threads
spring:
  threads:
    virtual:
      enabled: true  # uses Project Loom virtual threads for Tomcat

ভার্চুয়াল থ্রেডের সাথে, প্রতিটি HTTP অনুরোধ একটি প্ল্যাটফর্ম থ্রেডের পরিবর্তে একটি হালকা ভার্চুয়াল থ্রেড পায়। এটি কোন কোড পরিবর্তন ছাড়াই I/O-বাউন্ড অ্যাপ্লিকেশনের (ডাটাবেস কোয়েরি, বাহ্যিক API কল) থ্রুপুটকে নাটকীয়ভাবে উন্নত করে।

GraalVM নেটিভ ইমেজ

# Add to pom.xml
# <plugin>spring-boot-maven-plugin with native goal</plugin>

# Build native image (requires GraalVM 22+)
mvn -Pnative native:compile

# Run (starts in ~50ms, uses ~60MB RAM vs 300MB JVM)
./target/techpulse-api

2026 সালে স্প্রিং বুট 3.3 উত্পাদন-প্রস্তুত, দ্রুত এবং বিকাশকারী-বান্ধব। ভার্চুয়াল থ্রেডগুলি বেশিরভাগ ব্যবহারের ক্ষেত্রে প্রতিক্রিয়াশীল প্রোগ্রামিংয়ের প্রয়োজনীয়তা দূর করে। GraalVM নেটিভ ইমেজ জাভাকে কনটেইনারাইজড মাইক্রোসার্ভিসের জন্য একটি গুরুতর প্রতিযোগী করে তোলে যেখানে শুরুর সময় এবং মেমরি গুরুত্বপূর্ণ।

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা