🐯[TIL] 250721-035

byoΒ·2025λ…„ 7μ›” 21일

πŸ’« JAVA

🏁 Inventory_app

🌿 git

🧠 Warehouse

@Entity
@Table(name = "warehouses")
@Getter
@Setter
@NoArgsConstructor
public class Warehouse {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    private String location;

    @OneToMany(mappedBy = "warehouse", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonManagedReference
    private List<Stock> stocks = new ArrayList<>();
}

🧠 Product

@Entity
@Table(name = "products")
@Getter
@Setter
@NoArgsConstructor
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private BigDecimal price;

    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonManagedReference
    private List<Stock> stocks = new ArrayList<>();
}

🧠 Stock

@Entity
@Table(name = "stocks")
@Getter
@Setter
@NoArgsConstructor
public class Stock {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id", nullable = false)
    @JsonBackReference
    private Product product;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "warehouse_id", nullable = false)
    @JsonBackReference
    private Warehouse warehouse;

    private Integer quantity;
}

πŸ“¦ WarehouseDto

@Getter
@Setter
public class WarehouseDto {
    @NotBlank(message = "μ°½κ³ λͺ…을 μž…λ ₯ν•΄μ£Όμ„Έμš”")
    private String name;

    @Size(max = 200, message = "μœ„μΉ˜ μ •λ³΄λŠ” μ΅œλŒ€ 200μžκΉŒμ§€ ν—ˆμš©λ©λ‹ˆλ‹€")
    private String location;
}

πŸ“¦ ProductDto

@Getter
@Setter
public class ProductDto {
    @NotBlank(message = "μ œν’ˆλͺ…을 μž…λ ₯ν•΄μ£Όμ„Έμš”")
    private String name;

    @NotNull(message = "가격을 μž…λ ₯ν•΄μ£Όμ„Έμš”")
    @PositiveOrZero(message = "가격은 0 λ˜λŠ” μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€")
    private BigDecimal price;
}

πŸ“¦ StockDto

@Getter
@Setter
public class StockDto {
    @NotNull(message = "μ œν’ˆ IDλ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”.")
    private Long productId;

    @NotNull(message = "μ°½κ³  IDλ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”.")
    private Long warehouseId;

    @NotNull(message = "μˆ˜λŸ‰μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.")
    @Min(value = 0, message = "μˆ˜λŸ‰μ€ 0 이상이어야 ν•©λ‹ˆλ‹€.")
    private Integer quantity;
}

πŸ“¦ StockUpdateDto

@Getter
@Setter
public class StockUpdateDto {
    @NotNull(message = "μˆ˜λŸ‰μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.")
    @Min(value = 0, message = "μˆ˜λŸ‰μ€ 0 이상이어야 ν•©λ‹ˆλ‹€.")
    private Integer quantity;
}

πŸ—„οΈ WarehouseRepository

public interface WarehouseRepository extends JpaRepository<Warehouse, Long>, QuerydslPredicateExecutor<Warehouse> {
}

πŸ—„οΈ ProductRepository

public interface ProductRepository extends JpaRepository<Product, Long>, QuerydslPredicateExecutor<Product> {
}

πŸ—„οΈ StockRepository

public interface StockRepository extends JpaRepository<Stock, Long>, QuerydslPredicateExecutor<Stock> {
    Optional<Stock> findByProductIdAndWarehouseId(Long productId, Long warehouseId);
}

πŸ”§ WarehouseSearchCondition

@Getter
@Setter
public class WarehouseSearchCondition {
    private String name;
    private String location;
}

πŸ—„οΈ WarehouseQueryRepository

@Repository
@RequiredArgsConstructor
public class WarehouseQueryRepository {
    private final JPAQueryFactory queryFactory;

    public List<Warehouse> search(WarehouseSearchCondition condition) {
        QWarehouse warehouse = QWarehouse.warehouse;
        BooleanBuilder builder = new BooleanBuilder();

        if (condition.getName() != null && !condition.getName().isBlank()) {
            builder.and(warehouse.name.containsIgnoreCase(condition.getName()));
        }
        if (condition.getLocation() != null && !condition.getLocation().isBlank()) {
            builder.and(warehouse.location.containsIgnoreCase(condition.getLocation()));
        }

        return queryFactory.selectFrom(warehouse).where(builder).orderBy(warehouse.name.asc()).fetch();
    }
}

πŸ”§ ProductSearchCondition

@Getter
@Setter
public class ProductSearchCondition {
    private String name;
    private BigDecimal minPrice;
    private BigDecimal maxPrice;
}

πŸ—„οΈ ProductQueryRepository

@Repository
@RequiredArgsConstructor
public class ProductQueryRepository {
    private final JPAQueryFactory queryFactory;

    public List<Product> search(ProductSearchCondition condition) {
        QProduct product = QProduct.product;
        BooleanBuilder builder = new BooleanBuilder();

        if (condition.getName() != null && !condition.getName().isBlank()) {
            builder.and(product.name.containsIgnoreCase(condition.getName()));
        }
        if (condition.getMinPrice() != null) {
            builder.and(product.price.goe(condition.getMinPrice()));
        }
        if (condition.getMaxPrice() != null) {
            builder.and(product.price.loe(condition.getMaxPrice()));
        }

        return queryFactory.selectFrom(product).where(builder).orderBy(product.name.asc()).fetch();
    }
}

πŸ”§ StockSearchCondition

@Getter
@Setter
public class StockSearchCondition {
    private String productName;
    private String warehouseName;
    private Integer minQuantity;
    private Integer maxQuantity;
}

πŸ—„οΈ StockQueryRepository

@Repository
@RequiredArgsConstructor
public class StockQueryRepository {
    private final JPAQueryFactory queryFactory;

    public List<Stock> search(StockSearchCondition condition) {
        QStock stock = QStock.stock;
        QProduct product = QProduct.product;
        QWarehouse warehouse = QWarehouse.warehouse;

        BooleanBuilder builder = new BooleanBuilder();

        builder.and(stock.product.id.isNotNull());
        builder.and(stock.warehouse.id.isNotNull());

        if (condition.getProductName() != null && !condition.getProductName().isBlank()) {
            builder.and(product.name.containsIgnoreCase(condition.getProductName()));
        }
        if (condition.getWarehouseName() != null && !condition.getWarehouseName().isBlank()) {
            builder.and(warehouse.name.containsIgnoreCase(condition.getWarehouseName()));
        }
        if (condition.getMinQuantity() != null) {
            builder.and(stock.quantity.goe(condition.getMinQuantity()));
        }
        if (condition.getMaxQuantity() != null) {
            builder.and(stock.quantity.loe(condition.getMaxQuantity()));
        }

        return queryFactory
                .selectFrom(stock)
                .join(stock.product, product)
                .join(stock.warehouse, warehouse)
                .where(builder)
                .orderBy(product.name.asc(), warehouse.name.asc())
                .fetch();
    }
}

πŸ“‘ WarehouseController

@RestController
@RequestMapping("/api/warehouses")
@RequiredArgsConstructor
public class WarehouseController {
    private final WarehouseService warehouseService;

    @GetMapping
    public List<Warehouse> search(WarehouseSearchCondition condition) {
        return warehouseService.search(condition);
    }

    @GetMapping("/{id}")
    public Warehouse getById(@PathVariable Long id) {
        return warehouseService.getById(id);
    }

    @PostMapping
    public Warehouse create(@Valid @RequestBody WarehouseDto dto) {
        return warehouseService.create(dto);
    }

    @PutMapping("/{id}")
    public Warehouse update(@PathVariable Long id, @Valid @RequestBody WarehouseDto dto) {
        return warehouseService.update(id, dto);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        warehouseService.delete(id);
    }
}

πŸ“‘ ProductController

@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {
    private final ProductService productService;

    @GetMapping
    public List<Product> search(ProductSearchCondition condition) {
        return productService.search(condition);
    }

    @GetMapping("/{id}")
    public Product getById(@PathVariable Long id) {
        return productService.getById(id);
    }

    @PostMapping
    public Product create(@Valid @RequestBody ProductDto dto) {
        return productService.create(dto);
    }

    @PutMapping("/{id}")
    public Product update(@PathVariable Long id, @Valid @RequestBody ProductDto dto) {
        return productService.update(id, dto);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        productService.delete(id);
    }
}

πŸ“‘ StockController

@RestController
@RequestMapping("/api/stocks")
@RequiredArgsConstructor
public class StockController {
    private final StockService stockService;

    @GetMapping
    public List<Stock> search(StockSearchCondition condition) {
        return stockService.search(condition);
    }

    @GetMapping("/{id}")
    public Stock getById(@PathVariable Long id) {
        return stockService.getById(id);
    }

    @PostMapping
    public Stock create(@Valid @RequestBody StockDto dto) {
        return stockService.create(dto);
    }

    @PutMapping("/{id}")
    public Stock update(@PathVariable Long id, @Valid @RequestBody StockUpdateDto dto) {
        return stockService.update(id, dto);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        stockService.delete(id);
    }

    @PostMapping("/transfer")
    public void transfer(
            @RequestParam Long productId,
            @RequestParam Long srcWarehouseId,
            @RequestParam Long destWarehouseId,
            @RequestParam int quantity
    ) {
        stockService.transferStock(productId, srcWarehouseId, destWarehouseId, quantity);
    }
}

πŸ’« POSTMAN

base_url = http://localhost:8080/api/

POST {{base_url}}warehouses
{
    "name": "κ΅­μ œμ§€μ›μ ",
    "location": "μ„œλŒ€λ¬Έκ΅¬"
}
POST {{base_url}}products
{
    "name": "λ‚˜μ΄ν‚€ μ‹ λ°œ",
    "price": 150000
}
POST {{base_url}}stocks
{
    "productId": 2,
    "warehouseId": 4,
    "quantity": 100
}

GET {{base_url}}stocks/transfer?productId=2&srcWarehouseId=4&destWarehouseId=3&quantity=100
profile
πŸ—‚οΈ hamstern

0개의 λŒ“κΈ€