spring-boot-development by manutej/luxor-claude-marketplace
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill spring-boot-development本技能基于官方 Spring Boot 文档,为使用自动配置、依赖注入、REST API、Spring Data、Spring Security 和企业级 Java 模式构建现代 Spring Boot 应用程序提供全面指导。
在以下场景中使用此技能:
Spring Boot 根据您添加到项目中的依赖项自动配置您的应用程序。这显著减少了样板配置。
自动配置的工作原理:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@SpringBootApplication 注解是以下注解的组合:
@Configuration:将类标记为 Bean 定义的来源@EnableAutoConfiguration:启用 Spring Boot 的自动配置机制广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
@ComponentScan:启用当前包及其子包中的组件扫描条件自动配置:
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.url")
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
自定义自动配置:
// 排除特定的自动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
// ...
}
// 或在 application.properties 中
// spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Spring 的 IoC(控制反转)容器管理对象创建和依赖注入。
构造函数注入(推荐):
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// 构造函数注入 - 推荐方法
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User createUser(User user) {
User saved = userRepository.save(user);
emailService.sendWelcomeEmail(saved);
return saved;
}
}
字段注入(不推荐):
@Service
public class UserService {
@Autowired // 避免字段注入
private UserRepository userRepository;
// 难以测试并创建紧耦合
}
Setter 注入(可选依赖项):
@Service
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
组件原型注解:
@Component // 通用组件
public class MyComponent { }
@Service // 业务逻辑层
public class MyService { }
@Repository // 数据访问层
public class MyRepository { }
@Controller // 表示层(Web)
public class MyController { }
@RestController // REST API 控制器
public class MyRestController { }
使用 Spring MVC 注解构建 RESTful Web 服务。
基本 REST 控制器:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody @Valid User user) {
User created = userService.save(user);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@RequestBody @Valid User user) {
return userService.update(id, user)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userService.delete(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
请求映射变体:
@RestController
@RequestMapping("/api/products")
public class ProductController {
// 查询参数
@GetMapping("/search")
public List<Product> search(@RequestParam String name,
@RequestParam(required = false) String category) {
return productService.search(name, category);
}
// 多个路径变量
@GetMapping("/categories/{categoryId}/products/{productId}")
public Product getProductInCategory(@PathVariable Long categoryId,
@PathVariable Long productId) {
return productService.findInCategory(categoryId, productId);
}
// 请求头
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id,
@RequestHeader("Accept-Language") String language) {
return productService.find(id, language);
}
// 矩阵变量
@GetMapping("/{id}")
public Product getProductWithMatrix(@PathVariable Long id,
@MatrixVariable Map<String, String> filters) {
return productService.findWithFilters(id, filters);
}
}
响应处理:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// 返回不同的状态码
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
Order created = orderService.create(order);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
// 自定义头
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id) {
Order order = orderService.findById(id);
return ResponseEntity.ok()
.header("X-Order-Version", order.getVersion().toString())
.body(order);
}
// 无内容响应
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
orderService.delete(id);
return ResponseEntity.noContent().build();
}
}
Spring Data JPA 为数据库访问提供存储库抽象。
实体定义:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and setters
}
存储库接口:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 查询方法 - Spring Data 生成实现
Optional<User> findByEmail(String email);
List<User> findByNameContaining(String name);
List<User> findByDepartmentId(Long departmentId);
// 自定义 JPQL 查询
@Query("SELECT u FROM User u WHERE u.email = ?1")
Optional<User> findByEmailQuery(String email);
// 命名参数
@Query("SELECT u FROM User u WHERE u.name LIKE %:name% AND u.department.id = :deptId")
List<User> searchByNameAndDepartment(@Param("name") String name,
@Param("deptId") Long deptId);
// 原生 SQL 查询
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
Optional<User> findByEmailNative(String email);
// 修改查询
@Modifying
@Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);
// 分页和排序
Page<User> findByDepartmentId(Long departmentId, Pageable pageable);
List<User> findByNameContaining(String name, Sort sort);
}
存储库使用:
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
public User save(User user) {
return userRepository.save(user);
}
public List<User> findAll() {
return userRepository.findAll();
}
public Page<User> findAll(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("name"));
return userRepository.findAll(pageable);
}
public boolean delete(Long id) {
if (userRepository.existsById(id)) {
userRepository.deleteById(id);
return true;
}
return false;
}
}
关系:
// 一对多
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
}
// 多对多
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
Spring Boot 使用 application.properties 或 application.yml 进行配置。
应用程序属性:
# Server configuration
server.port=8080
server.servlet.context-path=/api
# Database configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
# JPA configuration
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# Logging
logging.level.root=INFO
logging.level.com.example=DEBUG
logging.level.org.springframework.web=DEBUG
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# Custom properties
app.name=My Application
app.version=1.0.0
应用程序 YAML:
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: password
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: validate
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
logging:
level:
root: INFO
com.example: DEBUG
org.springframework.web: DEBUG
app:
name: My Application
version: 1.0.0
配置属性类:
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private String name;
private String version;
private Security security = new Security();
public static class Security {
private int tokenExpiration = 3600;
private String secretKey;
// Getters and setters
}
// Getters and setters
}
// Usage
@Service
public class MyService {
private final AppConfig appConfig;
public MyService(AppConfig appConfig) {
this.appConfig = appConfig;
}
public void printConfig() {
System.out.println("App: " + appConfig.getName());
System.out.println("Version: " + appConfig.getVersion());
}
}
环境特定配置:
# application.properties (default)
spring.profiles.active=dev
# application-dev.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb_dev
logging.level.root=DEBUG
# application-prod.properties
spring.datasource.url=jdbc:postgresql://prod-server:5432/mydb_prod
logging.level.root=WARN
特定于配置文件的 Bean:
@Configuration
public class DatabaseConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
return DataSourceBuilder.create().build();
}
}
在您的应用程序中实现身份验证和授权。
基本安全配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
内存身份验证:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder.encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder.encode("admin"))
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
数据库身份验证:
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword())
.roles(user.getRoles().toArray(new String[0]))
.build();
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
public SecurityConfig(CustomUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public DaoAuthenticationProvider authenticationProvider(PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}
JWT 身份验证:
@Component
public class JwtTokenProvider {
@Value("${app.security.jwt.secret}")
private String jwtSecret;
@Value("${app.security.jwt.expiration}")
private int jwtExpiration;
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);
return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public Long getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException | MalformedJwtException | ExpiredJwtException |
UnsupportedJwtException | IllegalArgumentException ex) {
return false;
}
}
}
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
private final CustomUserDetailsService customUserDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider tokenProvider,
CustomUserDetailsService customUserDetailsService) {
this.tokenProvider = tokenProvider;
this.customUserDetailsService = customUserDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (jwt != null && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
核心 Spring 注解:
@SpringBootApplication:主应用程序类@Component:通用组件@Service:服务层组件@Repository:数据访问层组件@Configuration:配置类@Bean:Bean 定义方法@Autowired:依赖注入@Value:注入属性值@Profile:基于配置文件的 BeanWeb 注解:
@RestController:REST API 控制器@Controller:MVC 控制器@RequestMapping:映射 HTTP 请求@GetMapping:映射 GET 请求@PostMapping:映射 POST 请求@PutMapping:映射 PUT 请求@DeleteMapping:映射 DELETE 请求@PatchMapping:映射 PATCH 请求@PathVariable:提取路径变量@RequestParam:提取查询参数@RequestBody:提取请求体@RequestHeader:提取请求头@ResponseStatus:设置响应状态数据注解:
@Entity:JPA 实体@Table:表映射@Id:主键@GeneratedValue:自动生成值@Column:列映射@OneToOne:一对一关系@OneToMany:一对多关系@ManyToOne:多对一关系@ManyToMany:多对多关系@JoinColumn:连接列@JoinTable:连接表验证注解:
@Valid:启用验证@NotNull:字段不能为空@NotEmpty:字段不能为空@NotBlank:字段不能为空白@Size:字符串或集合大小@Min:最小值@Max:最大值@Email:电子邮件格式@Pattern:正则表达式模式事务注解:
@Transactional:启用事务管理@Transactional(readOnly = true):只读事务安全注解:
@EnableWebSecurity:启用安全@PreAuthorize:方法级授权@PostAuthorize:方法后授权@Secured:基于角色的访问异步和调度:
@EnableAsync:启用异步处理@Async:异步方法@EnableScheduling:启用调度@Scheduled:调度方法完整的 CRUD REST API:
// Entity
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Name is required")
private String name;
@NotBlank(message = "Description is required")
private String description;
@NotNull(message = "Price is required")
@Min(value = 0, message = "Price must be positive")
private BigDecimal price;
@NotNull(message = "Stock is required")
@Min(value = 0, message = "Stock must be positive")
private Integer stock;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and setters
}
// Repository
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByNameContaining(String name);
List<Product> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);
}
// Service
@Service
@Transactional
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Transactional(readOnly = true)
public Page<Product> findAll(Pageable pageable) {
return productRepository.findAll(pageable);
}
@Transactional(readOnly = true)
public Optional<Product> findById(Long id) {
return productRepository.findById(id);
}
public Product create(Product product) {
return productRepository.save(product);
}
public Optional<Product> update(Long id, Product productDetails) {
return productRepository.findById(id)
.map(product -> {
product.setName(productDetails.getName());
product.setDescription(productDetails.getDescription());
product.setPrice(productDetails.getPrice());
product.setStock(productDetails.getStock());
return productRepository.save(product);
});
}
public boolean delete(Long id) {
return productRepository.findById(id)
.map(product -> {
productRepository.delete(product);
return true;
})
.orElse(false);
}
}
// Controller
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public Page<Product> getAllProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return productService.findAll(pageable);
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) {
Product created = productService.create(product);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(
@PathVariable Long id,
@Valid @RequestBody Product product) {
return productService.update(id, product)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
if (productService.delete(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
全局异常处理器:
// Custom exceptions
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
// Error response
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
// Constructors, getters, setters
}
// Global exception handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(
ResourceNotFoundException ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
"Not Found",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequest(
BadRequestException ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.BAD_REQUEST.value(),
"Bad Request",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationErrors(
MethodArgumentNotValidException ex) {
Map<String, Object> errors = new HashMap<>();
errors.put("timestamp", LocalDateTime.now());
errors.put("status", HttpStatus.BAD_REQUEST.value());
Map<String, String> fieldErrors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
fieldErrors.put(error.getField(), error.getDefaultMessage())
);
errors.put("errors", fieldErrors);
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(
Exception ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal Server Error",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
完整的数据库设置:
// application.yml
/*
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: password
jpa:
hibernate:
ddl-auto: validate
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
*/
// Flyway migrations (db/migration/V1__Create_users_table.sql)
/*
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_users_email ON users(email);
*/
// Entity with auditing
@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Getters and setters
}
// Enable JPA auditing
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}
单元测试:
@SpringBootTest
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testFindById_Success() {
User user = new User();
user.setId(1L);
user.setEmail("test@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
Optional<User> result = userService.findById(1L);
assertTrue(result.isPresent());
assertEquals("test@example.com", result.get().getEmail());
verify(userRepository, times(1)).findById(1L);
}
@Test
void testFindById_NotFound() {
when(userRepository.findById(1L)).thenReturn(Optional.empty());
Optional<User> result = userService.findById(1L);
assertFalse(result.isPresent());
}
}
集成测试:
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@Test
void testCreateUser_Success() throws Exception {
User user = new User();
user.setEmail("test@example.com");
user.setName("Test User");
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.email").value("test@example.com"))
.andExpect(jsonPath("$.name").value("Test User"));
}
@Test
void testGetUser_Success() throws Exception {
User user = new User();
user.setEmail("test@example.com");
user.setName("Test User");
User saved = userRepository.save(user);
mockMvc.perform(get("/api/users/" + saved.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(saved.getId()))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
void testGetUser_NotFound() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound());
}
}
构造函数注入是依赖注入的推荐方法。
// Good - Constructor injection
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
// Bad - Field injection
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
不要通过 REST API 直接暴露实体。
// DTO
public class UserDTO {
private Long id;
private String email;
private String name;
// No password field exposed
// Getters and setters
}
// Mapper
@Component
public class UserMapper {
public UserDTO toDTO(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setEmail(user.getEmail());
dto.setName(user.getName());
return dto;
}
public User toEntity(UserDTO dto) {
User user = new User();
user.setEmail(dto.getEmail());
user.setName(dto.getName());
return user;
}
}
// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
private final UserMapper userMapper;
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(userMapper::toDTO)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
始终验证输入数据。
// Entity with validation
@Entity
public class User {
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@NotBlank(message = "Name is required")
@Size(min = 2, max = 100, message
This skill provides comprehensive guidance for building modern Spring Boot applications using auto-configuration, dependency injection, REST APIs, Spring Data, Spring Security, and enterprise Java patterns based on official Spring Boot documentation.
Use this skill when:
Spring Boot automatically configures your application based on the dependencies you have added to the project. This reduces boilerplate configuration significantly.
How Auto-Configuration Works:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
The @SpringBootApplication annotation is a combination of:
@Configuration: Tags the class as a source of bean definitions@EnableAutoConfiguration: Enables Spring Boot's auto-configuration mechanism@ComponentScan: Enables component scanning in the current package and sub-packagesConditional Auto-Configuration:
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.url")
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
Customizing Auto-Configuration:
// Exclude specific auto-configurations
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
// ...
}
// Or in application.properties
// spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Spring's IoC (Inversion of Control) container manages object creation and dependency injection.
Constructor Injection (Recommended):
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// Constructor injection - recommended approach
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User createUser(User user) {
User saved = userRepository.save(user);
emailService.sendWelcomeEmail(saved);
return saved;
}
}
Field Injection (Not Recommended):
@Service
public class UserService {
@Autowired // Avoid field injection
private UserRepository userRepository;
// Difficult to test and creates tight coupling
}
Setter Injection (Optional Dependencies):
@Service
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
Component Stereotypes:
@Component // Generic component
public class MyComponent { }
@Service // Business logic layer
public class MyService { }
@Repository // Data access layer
public class MyRepository { }
@Controller // Presentation layer (web)
public class MyController { }
@RestController // REST API controller
public class MyRestController { }
Build RESTful web services with Spring MVC annotations.
Basic REST Controller:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody @Valid User user) {
User created = userService.save(user);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@RequestBody @Valid User user) {
return userService.update(id, user)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userService.delete(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
Request Mapping Variations:
@RestController
@RequestMapping("/api/products")
public class ProductController {
// Query parameters
@GetMapping("/search")
public List<Product> search(@RequestParam String name,
@RequestParam(required = false) String category) {
return productService.search(name, category);
}
// Multiple path variables
@GetMapping("/categories/{categoryId}/products/{productId}")
public Product getProductInCategory(@PathVariable Long categoryId,
@PathVariable Long productId) {
return productService.findInCategory(categoryId, productId);
}
// Request headers
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id,
@RequestHeader("Accept-Language") String language) {
return productService.find(id, language);
}
// Matrix variables
@GetMapping("/{id}")
public Product getProductWithMatrix(@PathVariable Long id,
@MatrixVariable Map<String, String> filters) {
return productService.findWithFilters(id, filters);
}
}
Response Handling:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// Return different status codes
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
Order created = orderService.create(order);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
// Custom headers
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id) {
Order order = orderService.findById(id);
return ResponseEntity.ok()
.header("X-Order-Version", order.getVersion().toString())
.body(order);
}
// No content response
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
orderService.delete(id);
return ResponseEntity.noContent().build();
}
}
Spring Data JPA provides repository abstractions for database access.
Entity Definition:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and setters
}
Repository Interface:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Query method - Spring Data generates implementation
Optional<User> findByEmail(String email);
List<User> findByNameContaining(String name);
List<User> findByDepartmentId(Long departmentId);
// Custom JPQL query
@Query("SELECT u FROM User u WHERE u.email = ?1")
Optional<User> findByEmailQuery(String email);
// Named parameters
@Query("SELECT u FROM User u WHERE u.name LIKE %:name% AND u.department.id = :deptId")
List<User> searchByNameAndDepartment(@Param("name") String name,
@Param("deptId") Long deptId);
// Native SQL query
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
Optional<User> findByEmailNative(String email);
// Modifying query
@Modifying
@Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);
// Pagination and sorting
Page<User> findByDepartmentId(Long departmentId, Pageable pageable);
List<User> findByNameContaining(String name, Sort sort);
}
Repository Usage:
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
public User save(User user) {
return userRepository.save(user);
}
public List<User> findAll() {
return userRepository.findAll();
}
public Page<User> findAll(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("name"));
return userRepository.findAll(pageable);
}
public boolean delete(Long id) {
if (userRepository.existsById(id)) {
userRepository.deleteById(id);
return true;
}
return false;
}
}
Relationships:
// One-to-Many
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
}
// Many-to-Many
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
Spring Boot uses application.properties or application.yml for configuration.
Application Properties:
# Server configuration
server.port=8080
server.servlet.context-path=/api
# Database configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
# JPA configuration
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# Logging
logging.level.root=INFO
logging.level.com.example=DEBUG
logging.level.org.springframework.web=DEBUG
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# Custom properties
app.name=My Application
app.version=1.0.0
Application YAML:
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: password
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: validate
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
logging:
level:
root: INFO
com.example: DEBUG
org.springframework.web: DEBUG
app:
name: My Application
version: 1.0.0
Configuration Properties Class:
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private String name;
private String version;
private Security security = new Security();
public static class Security {
private int tokenExpiration = 3600;
private String secretKey;
// Getters and setters
}
// Getters and setters
}
// Usage
@Service
public class MyService {
private final AppConfig appConfig;
public MyService(AppConfig appConfig) {
this.appConfig = appConfig;
}
public void printConfig() {
System.out.println("App: " + appConfig.getName());
System.out.println("Version: " + appConfig.getVersion());
}
}
Environment-Specific Configuration:
# application.properties (default)
spring.profiles.active=dev
# application-dev.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb_dev
logging.level.root=DEBUG
# application-prod.properties
spring.datasource.url=jdbc:postgresql://prod-server:5432/mydb_prod
logging.level.root=WARN
Profile-Specific Beans:
@Configuration
public class DatabaseConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
return DataSourceBuilder.create().build();
}
}
Implement authentication and authorization in your application.
Basic Security Configuration:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
In-Memory Authentication:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder.encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder.encode("admin"))
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
Database Authentication:
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword())
.roles(user.getRoles().toArray(new String[0]))
.build();
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
public SecurityConfig(CustomUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public DaoAuthenticationProvider authenticationProvider(PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}
JWT Authentication:
@Component
public class JwtTokenProvider {
@Value("${app.security.jwt.secret}")
private String jwtSecret;
@Value("${app.security.jwt.expiration}")
private int jwtExpiration;
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);
return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public Long getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException | MalformedJwtException | ExpiredJwtException |
UnsupportedJwtException | IllegalArgumentException ex) {
return false;
}
}
}
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
private final CustomUserDetailsService customUserDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider tokenProvider,
CustomUserDetailsService customUserDetailsService) {
this.tokenProvider = tokenProvider;
this.customUserDetailsService = customUserDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (jwt != null && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
Core Spring Annotations:
@SpringBootApplication: Main application class@Component: Generic component@Service: Service layer component@Repository: Data access layer component@Configuration: Configuration class@Bean: Bean definition method@Autowired: Dependency injection@Value: Inject property values@Profile: Conditional beans based on profilesWeb Annotations:
@RestController: REST API controller@Controller: MVC controller@RequestMapping: Map HTTP requests@GetMapping: Map GET requests@PostMapping: Map POST requests@PutMapping: Map PUT requests@DeleteMapping: Map DELETE requests@PatchMapping: Map PATCH requests@PathVariable: Extract path variables@RequestParam: Extract query parametersData Annotations:
@Entity: JPA entity@Table: Table mapping@Id: Primary key@GeneratedValue: Auto-generated values@Column: Column mapping@OneToOne: One-to-one relationship@OneToMany: One-to-many relationship@ManyToOne: Many-to-one relationship@ManyToMany: Many-to-many relationship@JoinColumn: Join columnValidation Annotations:
@Valid: Enable validation@NotNull: Field cannot be null@NotEmpty: Field cannot be empty@NotBlank: Field cannot be blank@Size: String or collection size@Min: Minimum value@Max: Maximum value@Email: Email format@Pattern: Regex patternTransaction Annotations:
@Transactional: Enable transaction management@Transactional(readOnly = true): Read-only transactionSecurity Annotations:
@EnableWebSecurity: Enable security@PreAuthorize: Method-level authorization@PostAuthorize: Post-method authorization@Secured: Role-based accessAsync and Scheduling:
@EnableAsync: Enable async processing@Async: Async method@EnableScheduling: Enable scheduling@Scheduled: Scheduled methodComplete CRUD REST API:
// Entity
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Name is required")
private String name;
@NotBlank(message = "Description is required")
private String description;
@NotNull(message = "Price is required")
@Min(value = 0, message = "Price must be positive")
private BigDecimal price;
@NotNull(message = "Stock is required")
@Min(value = 0, message = "Stock must be positive")
private Integer stock;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and setters
}
// Repository
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByNameContaining(String name);
List<Product> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);
}
// Service
@Service
@Transactional
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Transactional(readOnly = true)
public Page<Product> findAll(Pageable pageable) {
return productRepository.findAll(pageable);
}
@Transactional(readOnly = true)
public Optional<Product> findById(Long id) {
return productRepository.findById(id);
}
public Product create(Product product) {
return productRepository.save(product);
}
public Optional<Product> update(Long id, Product productDetails) {
return productRepository.findById(id)
.map(product -> {
product.setName(productDetails.getName());
product.setDescription(productDetails.getDescription());
product.setPrice(productDetails.getPrice());
product.setStock(productDetails.getStock());
return productRepository.save(product);
});
}
public boolean delete(Long id) {
return productRepository.findById(id)
.map(product -> {
productRepository.delete(product);
return true;
})
.orElse(false);
}
}
// Controller
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public Page<Product> getAllProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return productService.findAll(pageable);
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) {
Product created = productService.create(product);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(
@PathVariable Long id,
@Valid @RequestBody Product product) {
return productService.update(id, product)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
if (productService.delete(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
Global Exception Handler:
// Custom exceptions
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
// Error response
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
// Constructors, getters, setters
}
// Global exception handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(
ResourceNotFoundException ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
"Not Found",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequest(
BadRequestException ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.BAD_REQUEST.value(),
"Bad Request",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationErrors(
MethodArgumentNotValidException ex) {
Map<String, Object> errors = new HashMap<>();
errors.put("timestamp", LocalDateTime.now());
errors.put("status", HttpStatus.BAD_REQUEST.value());
Map<String, String> fieldErrors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
fieldErrors.put(error.getField(), error.getDefaultMessage())
);
errors.put("errors", fieldErrors);
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(
Exception ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal Server Error",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Complete Database Setup:
// application.yml
/*
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: password
jpa:
hibernate:
ddl-auto: validate
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
*/
// Flyway migrations (db/migration/V1__Create_users_table.sql)
/*
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_users_email ON users(email);
*/
// Entity with auditing
@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Getters and setters
}
// Enable JPA auditing
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}
Unit Tests:
@SpringBootTest
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testFindById_Success() {
User user = new User();
user.setId(1L);
user.setEmail("test@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
Optional<User> result = userService.findById(1L);
assertTrue(result.isPresent());
assertEquals("test@example.com", result.get().getEmail());
verify(userRepository, times(1)).findById(1L);
}
@Test
void testFindById_NotFound() {
when(userRepository.findById(1L)).thenReturn(Optional.empty());
Optional<User> result = userService.findById(1L);
assertFalse(result.isPresent());
}
}
Integration Tests:
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@Test
void testCreateUser_Success() throws Exception {
User user = new User();
user.setEmail("test@example.com");
user.setName("Test User");
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.email").value("test@example.com"))
.andExpect(jsonPath("$.name").value("Test User"));
}
@Test
void testGetUser_Success() throws Exception {
User user = new User();
user.setEmail("test@example.com");
user.setName("Test User");
User saved = userRepository.save(user);
mockMvc.perform(get("/api/users/" + saved.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(saved.getId()))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
void testGetUser_NotFound() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound());
}
}
Constructor injection is the recommended approach for dependency injection.
// Good - Constructor injection
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
// Bad - Field injection
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
Don't expose entities directly through REST APIs.
// DTO
public class UserDTO {
private Long id;
private String email;
private String name;
// No password field exposed
// Getters and setters
}
// Mapper
@Component
public class UserMapper {
public UserDTO toDTO(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setEmail(user.getEmail());
dto.setName(user.getName());
return dto;
}
public User toEntity(UserDTO dto) {
User user = new User();
user.setEmail(dto.getEmail());
user.setName(dto.getName());
return user;
}
}
// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
private final UserMapper userMapper;
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(userMapper::toDTO)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
Always validate input data.
// Entity with validation
@Entity
public class User {
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@NotBlank(message = "Name is required")
@Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters")
private String name;
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
}
// Controller
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// Validation happens automatically
return ResponseEntity.ok(userService.save(user));
}
Mark service methods with appropriate transaction settings.
@Service
@Transactional
public class OrderService {
@Transactional(readOnly = true)
public List<Order> findAll() {
return orderRepository.findAll();
}
@Transactional
public Order createOrder(Order order) {
// Multiple database operations in one transaction
Order saved = orderRepository.save(order);
inventoryService.decreaseStock(order.getItems());
emailService.sendOrderConfirmation(saved);
return saved;
}
}
Always paginate large datasets.
@GetMapping
public Page<Product> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return productService.findAll(pageable);
}
Use @RestControllerAdvice for centralized exception handling.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(ex.getMessage()));
}
}
Implement proper logging throughout your application.
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public User createUser(User user) {
logger.info("Creating user with email: {}", user.getEmail());
try {
User saved = userRepository.save(user);
logger.info("User created successfully with id: {}", saved.getId());
return saved;
} catch (Exception e) {
logger.error("Error creating user: {}", e.getMessage(), e);
throw e;
}
}
}
Implement proper authentication and authorization.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer().jwt();
return http.build();
}
}
Use Flyway or Liquibase for database version control.
-- V1__Create_users_table.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL
);
-- V2__Add_password_column.sql
ALTER TABLE users ADD COLUMN password VARCHAR(255);
Use Spring Boot Actuator for monitoring.
# application.properties
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
See EXAMPLES.md for detailed code examples including:
This Spring Boot development skill covers:
The patterns and examples are based on official Spring Boot documentation (Trust Score: 7.5) and represent modern enterprise Java development practices.
Weekly Installs
113
Repository
GitHub Stars
46
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
opencode89
gemini-cli86
codex85
github-copilot77
claude-code71
cursor70
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
116,600 周安装
@RequestBody: Extract request body@RequestHeader: Extract request headers@ResponseStatus: Set response status@JoinTable: Join table