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

دليل Spring Boot 3 2026: REST APIs وJPA والأمن والخيوط الافتراضية

⏱️4 min read  ·  864 words

Spring Boot هو إطار عمل Java المهيمن للواجهات الخلفية للمؤسسات في عام 2026. مع Spring Boot 3.3، والتجميع الأصلي عبر GraalVM، والخيوط الافتراضية عبر Project Loom، وSpring AI starter، أصبح بناء واجهات برمجة تطبيقات Java للإنتاج أسرع من أي وقت مضى. يأخذك هذا الدليل من helloworld إلى REST API على مستوى الإنتاج.

لماذا التمهيد الربيع؟

  • التكوين التلقائي– افتراضيات معقولة، صفر معياري للأنماط الشائعة
  • الخوادم المدمجة— تم تضمين Tomcat/Netty، ولا توجد ملفات WAR للنشر
  • جاهز للإنتاج— المحرك، ومقاييس الميكرومتر، والفحوصات الصحية المضمنة
  • GraalVM الأصلي– الترجمة إلى الملف الثنائي الأصلي، تبدأ خلال أقل من 50 مللي ثانية، وتستخدم ذاكرة وصول عشوائي أقل بنسبة 50%
  • النظام البيئي الضخم– بيانات الربيع، الأمن، السحابة، الدفعة، الذكاء الاصطناعي

إعداد المشروع

# 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();
    }
}

المواضيع الافتراضية (Java 21 + Spring Boot 3.3)

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

باستخدام الخيوط الافتراضية، يحصل كل طلب HTTP على سلسلة رسائل افتراضية خفيفة الوزن بدلاً من سلسلة رسائل النظام الأساسي. يؤدي هذا إلى تحسين الإنتاجية بشكل كبير للتطبيقات المرتبطة بالإدخال/الإخراج (استعلامات قاعدة البيانات، واستدعاءات واجهة برمجة التطبيقات الخارجية) دون أي تغييرات في التعليمات البرمجية.

الصورة الأصلية لـ 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

Spring Boot 3.3 في عام 2026 جاهز للإنتاج وسريع وسهل التطوير. تلغي الخيوط الافتراضية الحاجة إلى البرمجة التفاعلية لمعظم حالات الاستخدام. تجعل صور GraalVM الأصلية من Java منافسًا جديًا للخدمات الصغيرة المعبأة في حاويات حيث يكون وقت بدء التشغيل والذاكرة مهمًا.

✍️ Leave a Comment

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

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