Spring을 처음 배울 때 헷갈리는 개념들을 차근차근 정리해보았습니다. 실무에서 가장 많이 사용하는 핵심 기능들을 중심으로 설명하겠습니다!
Bean이란? Spring이 실행될 때 @Component 계열 어노테이션을 확인해서 객체로 등록하는 것
@Service
public class UserService {
public void saveUser(User user) {
// 비즈니스 로직
}
}
@Repository
public class UserRepository {
public void save(User user) {
// 데이터 저장 로직
}
}
핵심 어노테이션들:
@Component: 일반적인 Bean@Service: 비즈니스 로직 담당@Repository: 데이터 접근 담당@Controller: 웹 요청 처리 담당DI란? Spring 객체를 가져다 편하게 사용할 수 있게 의존성 주입을 하는 것
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
왜 생성자 주입이 좋을까?
final 키워드 사용 가능)@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 간단해 보이지만 문제점이 많음
}
@Qualifier란? 의존성이 두 가지 이상일 때 어떤 것을 사용할지 정해주는 것
@Repository
@Qualifier("mysql")
public class MySqlUserRepository implements UserRepository { }
@Repository
@Qualifier("mongo")
public class MongoUserRepository implements UserRepository { }
@Service
public class UserService {
public UserService(@Qualifier("mysql") UserRepository userRepository) {
this.userRepository = userRepository;
}
}
언제 사용? 외부 라이브러리를 Spring Bean으로 등록할 때
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
}
과정:
1. dependency 추가 (pom.xml) → 라이브러리 파일을 프로젝트에 포함
2. @Bean 등록 → 라이브러리의 객체를 Spring Bean으로 등록
@Transactional이란? 데이터베이스 작업의 안전성을 보장하는 어노테이션
public void transfer(String from, String to, int amount) {
accountRepository.withdraw(from, amount); // 성공
accountRepository.deposit(to, amount); // 실패!
// 결과: 돈만 사라짐 😱
}
@Transactional
public void transfer(String from, String to, int amount) {
accountRepository.withdraw(from, amount); // 성공
accountRepository.deposit(to, amount); // 실패!
// Spring이 자동으로 롤백 → 원상복구 😊
}
동작 원리 (프록시 패턴):
// Spring이 자동으로 이렇게 처리
public void transfer() {
TransactionManager.begin(); // 트랜잭션 시작
try {
실제_transfer_메서드(); // 진짜 메서드 실행
TransactionManager.commit(); // 성공시 커밋
} catch (Exception e) {
TransactionManager.rollback(); // 실패시 롤백
throw e;
}
}
# 서버 설정
server.port=8080
# 데이터베이스 설정
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=1234
# JPA 설정
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
@Service
public class EmailService {
@Value("${app.email.from}")
private String fromEmail;
@Value("${app.email.enabled:true}") // 기본값 설정
private boolean emailEnabled;
}
application-dev.properties (개발용)
spring.datasource.url=jdbc:h2:mem:testdb
logging.level.com.example=DEBUG
application-prod.properties (운영용)
spring.datasource.url=jdbc:mysql://prod-server:3306/prod_db
logging.level.com.example=WARN
활성화:
spring.profiles.active=dev
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String name;
private String email;
private Integer age;
// 생성자, getter, setter
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 기본 CRUD 자동 제공: save(), findById(), findAll(), delete()
// 메서드 이름으로 자동 쿼리 생성!
List<User> findByName(String name);
List<User> findByAgeGreaterThan(Integer age);
List<User> findByNameContainingAndAgeGreaterThan(String name, Integer age);
// 커스텀 쿼리
@Query("SELECT u FROM User u WHERE u.email LIKE %:domain%")
List<User> findByEmailDomain(@Param("domain") String domain);
}
@Service
public class UserService {
public User findUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다"));
}
public User findUserByIdOrNull(Long id) {
return userRepository.findById(id).orElse(null);
}
}
@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.getAllUsers();
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.findUserById(id);
}
@PostMapping
public User createUser(@RequestBody User user) {
return userService.saveUser(user);
}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.saveUser(user);
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}
@GetMapping("/search")
public List<User> searchUsers(@RequestParam String name,
@RequestParam(required = false) Integer age) {
if (age != null) {
return userService.findUsersByNameAndAge(name, age);
} else {
return userService.findUsersByName(name);
}
}
HTTP 요청 (JSON)
↓
@RestController (Bean)
↓ DI로 주입받은
@Service (Bean) + @Transactional
↓ DI로 주입받은
@Repository (Bean) + JPA
↓
Database
// 1. HTTP 요청: POST /api/users
@RestController
public class UserController {
private final UserService userService; // DI
@PostMapping("/api/users")
public User createUser(@RequestBody User user) {
return userService.saveUser(user);
}
}
// 2. 비즈니스 로직 처리
@Service
public class UserService {
private final UserRepository userRepository; // DI
@Transactional // AOP로 트랜잭션 관리
public User saveUser(User user) {
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
throw new IllegalArgumentException("이미 존재하는 이메일");
}
return userRepository.save(user);
}
}
// 3. 데이터베이스 접근
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email); // JPA가 SQL 자동 생성
}
핵심 개념들:
이 개념들만 제대로 이해해도 Spring Boot로 실무 프로젝트를 시작할 수 있습니다! 🚀