JAVA/DDD
DDD - ORM과 투명한 영속성 8부 9부
100win10
2021. 1. 24. 07:43
- OrderLineItem에도 Long 타입의 IDENTITY FILED를 추가한다.
- @Cofigurable Annotation이 계속 사용되고 있음에 주목하자.
- @Cofigurable Annotation은 Spring 컨테이너 외부에서 생성되는 객체에 Spring 컨테이너에서 선언된 빈을 의존 삽입하기 위해 사용된다.
- 여기에서는 Hibernate가 생성하는 OrderLineItem 객체에 ProductRepository 타입의 빈을 의존 삽입하기 위해 사용되고 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Configurable(value="orderLineItem",preConstruction=true)
public class OrderLineItem {
private Long id;
private Product product;
private int quantity;
private ProductRepository productRepository;
public OrderLineItem() {
}
public OrderLineItem(String productName, int quantity) {
this.product = productRepository.find(productName);
this.quantity = quantity;
}
public void setProductRepository(ProductRepository productRepository) {
this.productRepository = productRepository;
}
}
|
cs |
- OrderLineItem은 Order의 HashSet에 저장되기 때문에 equals()와 hashCode()를 반드시 오버라이딩해야 한다.
- 하나의 Order 내에는 하나의 Product에 대해 하나의 OrderLineItem 만이 존재해야 한다는 도메인 규칙이 있으므로 Product와 quantity를 사용하여 비교를 수행하면 될 것 같다.
- OrderLineItem 역시 지연 로딩 문제를 방지하기 위해 getQuantity() 메소드를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
...
int getQuantity() {
return quantity;
}
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (!(object instanceof OrderLineItem)) {
return false;
}
final OrderLineItem other = (OrderLineItem)object;
return this.product.equals(other.getProduct())
&& this.quantity == other.getQuantity();
}
public int hashCode() {
int result = 17;
result = 37*result + product.hashCode();
result = 37*result + quantity;
return result;
}
}
|
cs |
- Order 클래스를 ORDERS 테이블에, OrderLineItem 클래스를 ORDER_LINE_ITEMS 테이블에 맵핑한다.
- Hibernate는 테이블의 컬럼을 도메인 객체의 속성이나 프로퍼티 둘 중 하나에 맵핑되도록 할 수 있다.
- 개인적으로 속성 맵핑을 선호한다. 프로퍼티 맵핑을 위해 불필요한 gettter/setter 메소드를 추가하는 것은 클래스 추상화의 일관성을 깨기 쉬우며 개발이 번거롭기 때문이다.
- 프로퍼티 접근에 의한 캡슐화 보장이라는 장점은 ORM 이라는 문맥 상에서 볼 때 그다지 설득력이 없어 보인다.
- 위 맵핑 정보에서 눈여겨 볼 부분은 Order 클래스의 lineItems 속성에 OrderLineItem을 Set으로 맵핑하는 부분이다.
- Hibernate의 경우 맵핑 파일 내에 cascade 속성을 사용하여 영속성 전이(transitive persistence)를 지원한다.
- OrderLineItem의 생명주기는 Order에 종속적이므로 Order가 OrderRepository를 통해 데이터베이스에 저장되거나 삭제되었을 때 함께 저장되어야 한다.
- 또한 Order의 Set으로부터 제거되었을 때 데이터베이스에서 삭제되어야 한다. cascade = CascadeType.ALL 을 명시함으로써 AGGREGATE의 생명주기 제약 조건을 만족시킬 수 있다.
- 이제 Produc 클래스의 변경 사항을 살펴 보자. 데이터베이스의 주 키를 추적하기 위한 id 속성과 함께 기본 생성자가 추가되었다.
- 이 역시 하부 인프라스트럭처의 제약 사항이 도메인 클래스의 구현에 영향을 미치는 예로 Hibernate의 경우 객체를 생성할 때 newInstance()를 호출하기 때문에 기본 생성자가 존재해야 한다.
- 기본 생성자는 public일 필요는 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Product {
private Long id;
private Money price;
private String name;
Product() {
}
public Product(String name, long price) {
this.name = name;
this.price = new Money(price);
}
}
|
cs |
- Product는 VALUE OBJECT인 Money를 속성으로 포함한다.
- 앞에서 설명한 바와 같이 VALUE OBJECT는 별도의 테이블로 맵핑되지 않고 의존하는 ENTITY가 맵핑되는 테이블의 컬럼으로 맵핑된다.
- Hibernate는 component를 사용하여 VALUE OBJECT의 개념을 지원한다.
- 이제 REPOSITORY를 구현하자. 우리는 이전 아티클에서 REPOSITORY의 인터페이스와 구현 클래스를 분리했으므로 도메인 레이어의 다른 부분에는 영향을 미치지 않고 Hibernate 전용 REPOSITORY를 구현할 수 있다.
- Spring은 Hibernate를 쉽게 연동할 수 있는 기반 클래스인 org.springframework.orm.hibernate3.support.HibernateDaoSupport를 제공한다.
- 이 클래스는 앞에서 선언한 SessionFactory를 사용하여 내부적으로 HibernateTemplate을 생성한다.
- 하위 클래스에서는 getHibernateTemplate()를 호출함으로써 Hibernate의 Session 관련 기능을 사용할 수 있다. OrderRepository 인터페이스의 구현 클래스인 HibernateOrderRepository의 코드를 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public class HibernateOrderRepository extends HibernateDaoSupport
implements OrderRepository {
public Order delete(Order order) {
getHibernateTemplate().delete(order);
return order;
}
@SuppressWarnings("unchecked")
public Order find(String orderId) {
List<Order> result = (List<Order>)getHibernateTemplate()
.find("from Order where orderId=?", orderId);
if (result != null && result.size() > 0) {
return result.get(0);
}
return null;
}
@SuppressWarnings("unchecked")
public Set<Order> findAll() {
return new HashSet<Order>(getHibernateTemplate()
.loadAll(Order.class));
}
@SuppressWarnings("unchecked")
public Set<Order> findByCustomer(Customer customer) {
return new HashSet<Order>(getHibernateTemplate()
.find("from Order where customer=?", customer));
}
public void save(Order order) {
getHibernateTemplate().save(order);
}
}
|
cs |
- HibernateOrderRepository는 HibernateDaoSupport를 상속 받고 OrderRepository를 실체화한다.
- 각 메소들은 getHibernateTemplate()를 사용하여 데이터베이스 관련 작업을 수행한다.
- 이제 REPOSITORY를 Hibernate와 연동하도록 수정했으므로 Spring 빈 컨텍스트에 추가하자.
- Hibernate 기반 REPOSITORY를 설정하기 위해 “sessionFactory” 프로퍼티에 앞에서 설정한 “sessionFactory” 빈을 의존 주입하도록 설정한다.
참조