Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@
@Configuration
public class DataSourceConfig {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SpringBoot AutoConfiguration이 제공해주는 Bean들을 사용하지 않고
직접 등록하신 의도가 궁금합니다

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 궁금

Copy link

@young970 young970 Aug 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SpringBoot AutoConfiguration이 제공하는 Bean을 사용해도 되나
transactionManager가 만들어지려면 EntityManagerFactory가 필요하고 Factory가 만들어지려면 DataSource, JpaVendorAdapter가 필요한 이런 개념들을 파악해 두고 싶어
학습에 목적을 두고 빈을 직접 등록했었던 것 같습니다.

말씀하신대로 SpringBoot가 제공하는 Bean을 사용하는 방식으로 바꿔보겠습니다!!

private static final String DRIVER = "org.h2.Driver";
private static final String DB_URL = "jdbc:h2:tcp://localhost/~/test";
private static final String USER = "sa";
private static final String PASSWORD= "";

@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:tcp://localhost/~/test");
dataSource.setUsername("sa");
dataSource.setPassword("");
dataSource.setDriverClassName(DRIVER);
dataSource.setUrl(DB_URL);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWORD);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 필드로 정보를 관리하고 DataSource를 직접 생성하는 의도가 궁금해요.

설정 정보를 읽어다 Spring AutoConfiguration이 Datasource를 만들어 줍니다.
또한 저렇게 바꾸고 고정시켜놓는다면 데이타소스 변경 등 유연한 대처가 어려울것같은데요?


return dataSource;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
package com.example.springjpamission.customer.domain;

import com.example.springjpamission.gobal.BaseEntity;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import com.example.springjpamission.order.domain.Order;
import jakarta.persistence.*;
import jakarta.validation.Valid;

import java.util.ArrayList;
import java.util.List;

@Table(name = "customers")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Customer extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Valid
@Embedded
@Column(nullable = false)
private Name name;

@OneToMany(mappedBy = "customer")
private List<Order> orders = new ArrayList<>();

protected Customer() { }

Comment on lines +24 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch가 default로 LAZY긴 하지만 명시해두는것이 의도가 보여 좋을때도 있어요

public Customer(Name name) {
this.name = name;
}
Expand All @@ -30,4 +34,16 @@ public void changeName(Name name) {
this.name = name;
}

public Long getId() {
return id;
}

public Name getName() {
return name;
}

public List<Order> getOrders() {
return orders;
}

}
Original file line number Diff line number Diff line change
@@ -1,26 +1,55 @@
package com.example.springjpamission.customer.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@EqualsAndHashCode
@Getter
import java.util.Objects;

@Embeddable
public class Name {

@Size(min = 1, max = 50)
private String firstName;

@Size(min = 1, max = 50)
private String lastName;

protected Name() { }

public Name(String firstName, String lastName) {
validateName(firstName,lastName );
this.firstName = firstName;
this.lastName = lastName;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 정리해서 깔끔하게 유지하도록 노력해봐요. 공백한칸 띄우고 빈칸 지워도 될것같네요

validate 하는 행위랑 초기화 하는 행위랑 문맥이 다르니

한칸 띄워서 가독성을 높여보는것은 어떻게 생각하세요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문맥을 고려해서 개행하도록 하겠습니다. 🐣

}

private void validateName(String firstName, String lastName) {
if (firstName == null || firstName.isBlank()) {
throw new IllegalArgumentException();
}
if (lastName == null || lastName.isBlank()) {
throw new IllegalArgumentException();
}
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Name name = (Name) o;
return Objects.equals(firstName, name.firstName) && Objects.equals(lastName, name.lastName);
}

@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
import com.example.springjpamission.customer.service.dto.SaveCustomerRequest;
import com.example.springjpamission.customer.service.dto.CustomerResponse;
import com.example.springjpamission.customer.service.dto.UpdateCustomerRequest;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Service
public class CustomerService {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스 레벨로 트랜잭셔널 어노테이션을 사용해도 될것같습니다.

다른 의도가 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스 레벨로 설정하면 모든 public method에 @Transactional이 적용되는데
이 부분을 살펴보지 않았던 것 같습니다. 😂

Expand All @@ -19,30 +22,30 @@ public CustomerService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}

@Transactional
public CustomerResponse saveCustomer(SaveCustomerRequest saveCustomerRequest) {
Customer customer = new Customer(
new Name(saveCustomerRequest.firstName(), saveCustomerRequest.lastName()));
return new CustomerResponse(customerRepository.save(customer));
return CustomerResponse.of(customerRepository.save(customer));
}

@Transactional
public CustomerResponse updateCustomer(UpdateCustomerRequest updateCustomerRequest) {
Customer findCustomer = customerRepository.findById(updateCustomerRequest.id())
.orElseThrow(() -> new RuntimeException("해당 customer는 존재하지 않습니다."));
.orElseThrow(() -> new EntityNotFoundException("해당 customer는 존재하지 않습니다."));

findCustomer.changeName(
new Name(updateCustomerRequest.firstName(), updateCustomerRequest.lastName()));
return new CustomerResponse(findCustomer);
return CustomerResponse.of(findCustomer);
}

@Transactional
public CustomerResponses findAll() {
return CustomerResponses.of(customerRepository.findAll());
@Transactional(readOnly = true)
public CustomerResponses findAll(Pageable pageable) {
return CustomerResponses.of(customerRepository.findAll(pageable));
}

@Transactional
public void deleteById(Long id) {
customerRepository.findById(id)
.orElseThrow(()->new EntityNotFoundException("해당 customer는 존재하지 않습니다."));

customerRepository.deleteById(id);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

public record CustomerResponse(Long id, String firstName, String lastName) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개행할것인지 말것인지 통일성을 맞춰주면 좋겠습니다.


public CustomerResponse(Customer customer) {
this(customer.getId(), customer.getName().getFirstName(), customer.getName().getLastName());
public static CustomerResponse of(Customer customer) {
return new CustomerResponse(customer.getId(),
customer.getName().getFirstName(),
customer.getName().getLastName());
Comment on lines +9 to +10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가르 get get get get get

객체A 를 조작하는 객체 B 는 객체 A가 어떠한 자료를 갖고 어떤 속사정을 갖고있는지 몰라도 됩니다.

디미터의 법칙에 대해 알아보세요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디미터의 법칙은 준수하도록 코드를 수정하도록 하겠습니다. 🐣

}

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.example.springjpamission.customer.service.dto;

import com.example.springjpamission.customer.domain.Customer;
import org.springframework.data.domain.Slice;

import java.util.List;
import java.util.stream.Collectors;

public record CustomerResponses(List<CustomerResponse> customerResponses) {

public static CustomerResponses of(List<Customer> customers) {
List<CustomerResponse> responses = customers.stream().map(CustomerResponse::new)
.collect(Collectors.toList());
public static CustomerResponses of(Slice<Customer> customers) {
List<CustomerResponse> responses = customers.stream()
.map(CustomerResponse::of)
.collect(Collectors.toList());
Comment on lines +11 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋네요 ㅎㅎ

정적팩토리 메소드에 바로 넣어서 생성 할까? 말까? 도 생각해보면 좋을거같아요

return new CustomerResponses(responses);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기는 정적 팩토리 메소드를 사용하셨네요.

각 케이스에 대해 고민해보고 컨벤션을 통일하는것은 어떨까요?


Expand Down
18 changes: 10 additions & 8 deletions src/main/java/com/example/springjpamission/gobal/BaseEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,31 @@
import jakarta.persistence.PreUpdate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import lombok.Getter;

@Getter
@MappedSuperclass
public class BaseEntity {
public abstract class BaseEntity {

@Column(name = "created_at", updatable = false, nullable = false)
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DATE_FORMAT은 뭔가 엄청 범용적인거 같기도 해요.

public하게 열어 시간 관련 모듈이 있는곳에 두거나,
지금 하는 포맷의 행위를 명확하게 해주면 좋을것같아요


@Column(updatable = false, nullable = false)
private String createAt;

@Column(name = "last_updated_at", nullable = false)
@Column(nullable = false)
private String lastUpdatedAt;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timestamp나 datetime을 사용할 수 있습니다.

둘의 차이는 무엇이 있을까요?

createdAt updatedAt 둘의 DB 자료형은 어떤것이 유리할까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timestamp : '1970-01-01 00:00:01' UTC부터 '2038-01-1903:14:07' UTC까지
datetime : '1000-01-01 00:00:00'부터 '9999-12-31 23:59:59'까지
로 지원하는 시간과 날짜의 범위가 다르다는 차이점이 있습니다.

따라서 createdAt과 updatedAt은 비교적 지금의 날짜를 나타내어 공간을 덜 차지하는 timestamp를 사용하는 것이 더 유리할 것이라고 생각합니다.

protected BaseEntity() { }

@PrePersist
public void prePersist() {
LocalDateTime now = LocalDateTime.now();
createAt = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
lastUpdatedAt = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
createAt = now.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
lastUpdatedAt = now.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
}

@PreUpdate
public void preUpdate() {
lastUpdatedAt = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
}

}
28 changes: 18 additions & 10 deletions src/main/java/com/example/springjpamission/order/domain/Car.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
package com.example.springjpamission.order.domain;

import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@DiscriminatorValue("CAR")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Car extends Item {

private static final int ZERO = 0;

@Column
Comment on lines +11 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ZERO = 0 딸랑 이렇게 두면 0이구나만 알지 우리의 중요한 도메인에서 어떤 역할을 하는지 파악하기 어려울 수 있어요

자동차의 POWER가 ZERO 미만이면 안된다는 비즈니스 규칙을 명확하게 하기 위해
MIN_CAR_POWER 라든지(사실 뭘 의미하는지 몰라서 잘 못짓겠어요)
더 명확한 이름을 지어 도메인을 단단하게 해주세요

private int power;

public Car(int power) {
this.power = power;
}
protected Car() { }

public Car(int price, int stockQuantity, int power) {
public Car(Price price, int stockQuantity, int power) {
super(price, stockQuantity);
validatePower(power);
this.power = power;
}

private void validatePower(int power) {
if(power < ZERO) {
throw new IllegalArgumentException();
}
}

public int getPower() {
return power;
}

}
22 changes: 12 additions & 10 deletions src/main/java/com/example/springjpamission/order/domain/Food.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
package com.example.springjpamission.order.domain;

import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@DiscriminatorValue("FOOD")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Food extends Item {

@Column
private String chef;

public Food(String chef) {
this.chef = chef;
}
protected Food() { }

public Food(int price, int stockQuantity, String chef) {
public Food(Price price, int stockQuantity, String chef) {
super(price, stockQuantity);
validateChef(chef);
this.chef = chef;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

값객체가 null로 들어올수는 없을까요?

}

private void validateChef(String chef) {
if (chef == null || chef.isBlank()) {
throw new IllegalArgumentException();
}
}

}
Loading