import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
} # Server
server.port=8080
# Database
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=user
spring.datasource.password=pass
# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# Logging
logging.level.root=INFO
logging.level.com.example=DEBUG ./mvnw spring-boot:run | Maven으로 실행 |
./gradlew bootRun | Gradle로 실행 |
./mvnw package | JAR 빌드 |
java -jar app.jar | JAR 실행 |
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public List<User> getAll() {
return userService.findAll();
}
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping
public User create(@RequestBody User user) {
return userService.save(user);
}
@PutMapping("/{id}")
public User update(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
userService.delete(id);
}
} // Path variable
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { ... }
// Query params
@GetMapping("/search")
public List<User> search(
@RequestParam String name,
@RequestParam(defaultValue = "0") int page,
@RequestParam(required = false) String sort
) { ... }
// Request body
@PostMapping
public User create(@RequestBody @Valid UserDto dto) { ... }
// Headers
@GetMapping
public String get(@RequestHeader("Authorization") String token) { ... } import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> findAll() {
return userRepository.findAll();
}
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("User not found"));
}
@Transactional
public User save(User user) {
return userRepository.save(user);
}
} import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(unique = true)
private String email;
@Enumerated(EnumType.STRING)
private Status status;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Post> posts;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
@CreatedDate
private LocalDateTime createdAt;
// Getters and setters
} import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface UserRepository extends JpaRepository<User, Long> {
// Derived queries
List<User> findByName(String name);
List<User> findByNameContaining(String name);
Optional<User> findByEmail(String email);
List<User> findByStatusOrderByCreatedAtDesc(Status status);
// Custom query
@Query("SELECT u FROM User u WHERE u.status = :status")
List<User> findActiveUsers(@Param("status") Status status);
// Native query
@Query(value = "SELECT * FROM users WHERE email LIKE %?1%", nativeQuery = true)
List<User> searchByEmail(String email);
} import jakarta.validation.constraints.*;
public class UserDto {
@NotBlank(message = "Name is required")
@Size(min = 2, max = 100)
private String name;
@NotNull
@Email(message = "Invalid email")
private String email;
@Min(0)
@Max(150)
private Integer age;
@Pattern(regexp = "^\\+?[0-9]{10,14}$")
private String phone;
@NotEmpty
private List<String> roles;
} @RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity.badRequest().body(errors);
}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<String> handleNotFound(NotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
} import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
} @WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldGetAllUsers() throws Exception {
when(userService.findAll()).thenReturn(List.of(new User("John")));
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("John"));
}
} @SpringBootTest
@AutoConfigureMockMvc
class UserIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldCreateUser() throws Exception {
String json = "{\"name\":\"John\",\"email\":\"john@example.com\"}";
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isCreated());
}
}