π¨ Spring Boot + Lombok νΈλ¬λΈμν κ°μ΄λ
Spring Bootμ Lombokμ μ¬μ©νλ κ³Όμ μμ μ§μ λ§λ₯λ¨λ¦° μλ¬λ₯Ό ν΄κ²°νλ κ³Όμ μ κΈ°λ‘ν΄ λ³΄μμ΅λλ€.
π λ¬Έμ 1: Lombok Getter λ©μλλ₯Ό μ°Ύμ μ μμ
π μλ¬ μν©
ProductManagementApplication μ€ν μ λ€μκ³Ό κ°μ μ»΄νμΌ μλ¬κ° λ°μνμ΅λλ€.
/Users/kobe/Desktop/ProductManagement/src/main/java/com/kobe/productmanagement/dto/response/StockResponse.java:35:
error: cannot find symbol
stock.getStockId(),
^
symbol: method getStockId()
location: variable stock of type Stock
π― μμΈ λΆμ
cannot find symbol
μλ¬λ Java μ»΄νμΌλ¬κ° μ½λμμ μ°Έμ‘°νλ λ©μλλ λ³μλ₯Ό μ°Ύμ§ λͺ»ν λ λ°μν©λλ€.
-
μ»΄νμΌ μμ λ¬Έμ :
Stock
ν΄λμ€μμgetStockId()
λ©μλλ₯Ό μ°Ύμ μ μμ -
Lombok λμ μ€ν¨:
@Getter
μ΄λ Έν μ΄μ μ΄ μ λλ‘ μ²λ¦¬λμ§ μμ getter λ©μλκ° μμ±λμ§ μμ - IDE vs μ€μ λΉλ: IDEμμλ μ μ μλνμ§λ§ μ€μ λΉλ μ μ€ν¨
π§ ν΄κ²° λ°©λ²
1λ¨κ³: Stock μν°ν° ν΄λμ€ νμΈ
@Entity
@Table(name = "stocks")
@Getter // β μ΄ μ΄λ
Έν
μ΄μ
μ΄ μλμ§ λ°λμ νμΈ!
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stock extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "stock_id")
private Long stockId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id", nullable = false)
private Product product;
@Column(name = "barcode_number", unique = true)
private String barcodeNumber;
// Lombokμ΄ μλμΌλ‘ μμ±ν λ©μλλ€:
// - getStockId()
// - getProduct()
// - getBarcodeNumber()
}
2λ¨κ³: ν΄λ¦° λΉλ μ€ν
# Gradle μ¬μ©μ
./gradlew clean build
# Maven μ¬μ©μ
./mvnw clean install
β νμΈ ν¬μΈνΈ
-
@Getter
μ΄λ Έν μ΄μ μ΄ ν΄λμ€μ μΆκ°λμ΄ μλκ°? -
import lombok.Getter;
import κ΅¬λ¬Έμ΄ μλκ°? - ν΄λ¦° λΉλλ₯Ό μ€ννλκ°?
π λ¬Έμ 2: λλμ Lombok λ©μλ λλ½ μλ¬
π μλ¬ μν©
./gradlew clean build
μ€ν μ 25κ°μ μ»΄νμΌ μλ¬κ° νλ²μ λ°μνμ΅λλ€.
> Task :compileJava FAILED
error: cannot find symbol
symbol: method getStockId()
location: variable stock of type Stock
error: cannot find symbol
symbol: method getProduct()
location: variable stock of type Stock
error: cannot find symbol
symbol: method builder()
location: class Product
error: cannot find symbol
symbol: method getName()
location: variable request of type ProductCreateRequest
25 errors
π― μμΈ λΆμ
IDEμμλ Lombokμ΄ μ μ μλνμ§λ§, Gradle λΉλ μμλ Lombok μ΄λ Έν μ΄μ νλ‘μΈμκ° λμνμ§ μκ³ μμ΅λλ€.
π‘ ν΅μ¬ κ°λ μ΄ν΄
- IDEμ Lombok νλ¬κ·ΈμΈ: κ°λ° μ€ μ½λ νμ΄λΌμ΄ν κ³Ό μλμμ±μ μν¨
- λΉλ λꡬμ μ΄λ Έν μ΄μ νλ‘μΈμ: μ€μ .class νμΌ μμ± μ λ©μλλ₯Ό λ§λλ μν
π§ ν΄κ²° λ°©λ²
build.gradle μ€μ μμ
dependencies {
// κΈ°μ‘΄ μμ‘΄μ±λ€
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// β Lombok μ€μ μΆκ° (κ°μ₯ μ€μ!)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// ν
μ€νΈμ© μ΄λ
Έν
μ΄μ
νλ‘μΈμλ μΆκ°
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
// λ°μ΄ν°λ² μ΄μ€ λλΌμ΄λ²
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'com.h2database:h2' // ν
μ€νΈμ© H2 DB
// ν
μ€νΈ μμ‘΄μ±
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
μ€λ¬΄ μ½λ μμ
μ€μ ν λ€μκ³Ό κ°μ μ½λλ€μ΄ μ μ μλν©λλ€:
// β
ProductService.java
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
public Long createProduct(ProductCreateRequest request) {
Product newProduct = Product.builder() // @Builder μ΄λ
Έν
μ΄μ
μΌλ‘ μμ±
.name(request.getName()) // @Getterλ‘ μμ±λ λ©μλ
.price(request.getPrice())
.stockQuantity(request.getStockQuantity())
.category(request.getCategory())
.costPrice(request.getCostPrice())
.productSupplier(request.getProductSupplier())
.barcodeNumber(request.getBarcodeNumber())
.build();
Product savedProduct = productRepository.save(newProduct);
return savedProduct.getProductId(); // @Getterλ‘ μμ±λ λ©μλ
}
}
// β
StockResponse.java
public record StockResponse(
Long stockId,
String productName,
BigDecimal costPrice,
BigDecimal sellingPrice,
Integer stockQuantity,
String category,
LocalDateTime createdAt,
LocalDateTime updatedAt,
String productSupplier
) {
public static StockResponse from(Stock stock) {
return new StockResponse(
stock.getStockId(), // β
μ μ μλ
stock.getProduct().getName(), // β
μ μ μλ
stock.getProduct().getCostPrice(),
stock.getProduct().getCostPrice(),
stock.getProduct().getStockQuantity(),
stock.getProduct().getCategory(),
stock.getCreatedAt(), // BaseTimeEntityμμ μμ
stock.getUpdatedAt(),
stock.getProduct().getProductSupplier()
);
}
}
π μ΄λ Έν μ΄μ νλ‘μΈμ μ€μ μ€λͺ
μ€μ | μν |
---|---|
compileOnly |
μ»΄νμΌ μμ μλ§ νμνκ³ , λ°νμμλ λΆνμν μμ‘΄μ± |
annotationProcessor |
ν΅μ¬! μ»΄νμΌ μ μ΄λ Έν μ΄μ μ μ€μ μ½λλ‘ λ³ννλ νλ‘μΈμ |
testCompileOnly |
ν μ€νΈ μ½λ μ»΄νμΌ μμλ§ νμν μμ‘΄μ± |
testAnnotationProcessor |
ν μ€νΈ μ½λμμλ Lombok μ΄λ Έν μ΄μ μ²λ¦¬ |
π λ¬Έμ 3: ν μ€νΈ μ€ν μ λ°μ΄ν°λ² μ΄μ€ μ°κ²° μ€ν¨
π μλ¬ μν©
λΉλλ μ±κ³΅νμ§λ§ ν μ€νΈ μ€ν μ λ€μ μλ¬κ° λ°μνμ΅λλ€.
> Task :test FAILED
ProductManagementApplicationTests > contextLoads() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: org.springframework.beans.factory.BeanCreationException
Caused by: org.hibernate.service.spi.ServiceException
Caused by: org.hibernate.HibernateException at DialectFactoryImpl.java:191
FAILURE: Build failed with an exception.
π― μμΈ λΆμ
Hibernateκ° λ°μ΄ν°λ² μ΄μ€ λ°©μΈ(Dialect)μ κ²°μ νμ§ λͺ»ν΄ λ°μνλ λ¬Έμ μ λλ€.
-
ν
μ€νΈ νκ²½μ λλΌμ΄λ² λΆμ‘±:
runtimeOnly
λ‘ μ μΈλ MySQL λλΌμ΄λ²λ ν μ€νΈμμ μ¬μ©ν μ μμ -
μ€μ νμΌ μ€λ³΅: ν
μ€νΈλ λ©μΈ
application.properties
λ₯Ό μ¬μ©ν΄ MySQL μ°κ²° μλ
π§ ν΄κ²° λ°©λ²
1λ¨κ³: ν μ€νΈμ© μ€μ νμΌ μμ±
π src/test/resources/application.yml νμΌμ μλ‘ μμ±ν©λλ€.
# ===============================
# π§ͺ TEST DATABASE (H2 In-Memory)
# ===============================
# Spring Bootκ° H2 λ°μ΄ν°λ² μ΄μ€λ₯Ό μλμΌλ‘ μ€μ νλλ‘ ν©λλ€.
spring:
# JPA/Hibernate μ€μ
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
# H2 Console νμ±ν (λλ²κΉ
μ©)
h2:
console:
enabled: true
path: /h2-console
# λ‘κΉ
μ€μ
logging:
level:
org.springframework.web: DEBUG
com.kobe.productmanagement: DEBUG
2λ¨κ³: μ€μ ν μ€νΈ μ½λ μμ
@SpringBootTest
@Transactional
class ProductServiceTest {
@Autowired
private ProductService productService;
@Autowired
private ProductRepository productRepository;
@Test
@DisplayName("μν μμ± ν
μ€νΈ - H2 λ°μ΄ν°λ² μ΄μ€ μ¬μ©")
void createProduct_Success() {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.name("λ§₯λΆ νλ‘ 16μΈμΉ")
.price(new BigDecimal("2490000"))
.stockQuantity(10)
.category("μ μμ ν")
.costPrice(new BigDecimal("2000000"))
.productSupplier("Apple Korea")
.barcodeNumber("8801234567890")
.build();
// when
Long productId = productService.createProduct(request);
// then
assertThat(productId).isNotNull();
Optional<Product> savedProduct = productRepository.findById(productId);
assertThat(savedProduct).isPresent();
assertThat(savedProduct.get().getName()).isEqualTo("λ§₯λΆ νλ‘ 16μΈμΉ");
}
@Test
@DisplayName("μ¬κ³ μ‘°ν ν
μ€νΈ - μ°κ΄κ΄κ³ ν¬ν¨")
void findStockWithProduct_Success() {
// given - ν
μ€νΈ λ°μ΄ν° μ€λΉ
Product product = Product.builder()
.name("μμ΄ν° 15 Pro")
.price(new BigDecimal("1350000"))
.stockQuantity(5)
.category("μ€λ§νΈν°")
.costPrice(new BigDecimal("1100000"))
.productSupplier("Apple Korea")
.barcodeNumber("8801234567891")
.build();
Product savedProduct = productRepository.save(product);
Stock stock = Stock.builder()
.product(savedProduct)
.barcodeNumber("STOCK_8801234567891")
.build();
// when & then - H2 λ°μ΄ν°λ² μ΄μ€μμ μ μ μλ
assertThat(stock.getProduct().getName()).isEqualTo("μμ΄ν° 15 Pro");
}
}
ποΈ νκ²½λ³ μ€μ ꡬ쑰
src/
βββ main/
β βββ resources/
β βββ application.yml # π μ΄μ/κ°λ°μ© (MySQL)
βββ test/
βββ resources/
βββ application.yml # π§ͺ ν
μ€νΈμ© (H2)
μ΄μ νκ²½ μ€μ (main/resources)
# MySQL λ°μ΄ν°λ² μ΄μ€ μ°κ²°
spring:
datasource:
url: jdbc:mysql://localhost:3306/product_management
username: root
password: password
jpa:
hibernate:
ddl-auto: validate
ν μ€νΈ νκ²½ μ€μ (test/resources)
# H2 μΈλ©λͺ¨λ¦¬ λ°μ΄ν°λ² μ΄μ€ (μλ μ€μ )
spring:
jpa:
hibernate:
ddl-auto: create-drop
h2:
console:
enabled: true
π λ¬Έμ ν΄κ²° 체ν¬λ¦¬μ€νΈ
β Lombok μ€μ νμΈ
-
@Getter
,@Builder
λ± μ΄λ Έν μ΄μ μ΄ ν΄λμ€μ μλκ°? -
build.gradle
μannotationProcessor 'org.projectlombok:lombok'
μΆκ°λ¨? - IDEμ Lombok νλ¬κ·ΈμΈμ΄ μ€μΉλμ΄ μλκ°?
β λ°μ΄ν°λ² μ΄μ€ μ€μ νμΈ
-
ν
μ€νΈμ©
application.yml
νμΌμ΄ λ³λλ‘ μλκ°? -
testImplementation 'com.h2database:h2'
μμ‘΄μ±μ΄ μΆκ°λ¨? - μ΄μ νκ²½κ³Ό ν μ€νΈ νκ²½μ λ°μ΄ν°λ² μ΄μ€κ° λΆλ¦¬λμ΄ μλκ°?
β λΉλ λ° ν μ€νΈ
-
./gradlew clean build
λͺ λ Ήμ΄κ° μ±κ³΅νλκ°? - λͺ¨λ ν μ€νΈκ° ν΅κ³Όνλκ°?
- IDEμμ κ°λ³ ν μ€νΈ μ€νμ΄ κ°λ₯νκ°?
π λ§λ¬΄λ¦¬
μ΄μ Spring Boot + Lombok νλ‘μ νΈμμ λ°μνλ μ£Όμ λ¬Έμ λ€μ ν΄κ²°ν μ μμ΅λλ€!
π λ€μ λ¨κ³ κΆμ₯μ¬ν
- CI/CD νμ΄νλΌμΈ ꡬμΆ: GitHub Actionsλ Jenkinsλ₯Ό νμ©ν μλ λΉλ
- ν μ€νΈ 컀λ²λ¦¬μ§ μΈ‘μ : JaCoCoλ₯Ό νμ©ν μ½λ 컀λ²λ¦¬μ§ νμΈ
-
νλ‘νμΌλ³ μ€μ λΆλ¦¬:
application-dev.yml
,application-prod.yml
λ±
π μΆκ° λμμ΄ νμνλ€λ©΄?
- Spring Boot 곡μ λ¬Έμ: https://spring.io/projects/spring-boot
- Lombok 곡μ λ¬Έμ: https://projectlombok.org/