모델링 연습 리뷰 
신림프로그래머 최범균 2014-11-25
요구사항 1 
• 요청 메시지의 A옵션이 1인 클라이언트C1과 B옵 
션이 2인 클라이언트 C2는 서브넷S1의 IP 대역에 
서 네트워크 설정(IP 포함)을 할당받는다. 
• 맥주소가 M3인 클라이언트C3와 M4인 클라이언 
트 C4는 서브넷S2의 IP 대역 중에서 앞 쪽 절반에 
서 네트워크 설정을 할당받는다. 
• 요청 메시지의 E옵션이 5인 클라이언트C5와 맥주 
소가 M6인 클라이언트C6은 서브넷S2의 IP 대역 
중에서 뒤 쪽 절반에서 네트워크 설정을 할당받는 
다. 
• 맥주소 일치가 우선한다. 
• 임대 IP는 클라이언트 단위로 관리된다. 
2
그림으로 보면 
C1 
C2 
C3 
(M3) 
C4 
(M4) 
C5 
IP 
대역 
S1 
IP 
대역 
S2 
A=1 
B=1 
C6 
(M6) 
E=1 
3
그림에서 도출 
C1 
C2 
C3 
(M3) 
C4 
(M4) 
C5 
IP 
대역 
S1 
IP 
대역 
S2 
A=1 
B=1 
C6 
(M6) 
E=1 
옵션으로 
클라이언트 
구분 
맥주소로 
클라이언트 
구분 
다른 조건을 따르는 클라이언트들이 
한 대역으로 묶임 
한 대역을 
나눠 쓸 수 
있음 
4
요구사항 2 
• 응답 옵션 
– IP 대역 별로 응답 옵션을 다르게 설정 
– IP 풀 별로 응답 옵션을 다르게 설정 
– 클라이언트 그룹(클래스) 별로 응답 옵션을 다르 
게 설정 
– 특정 조건을 충족하는 클라이언트들 별로 응답 옵 
션을 다르게 설정 
– 같은 응답 옵션이 존재할 경우 우선 순위 
• 클라이언트별 > 클라이언트 그룹 > IP 풀 > 대역 
• 네트워크 설정(DNS, GW, 서브넷마스크) 
– IP 대역 별로 네트워크 설정 
– IP 풀 별로 다른 네트워크 설정 
• IP 풀에 네트워크 설정이 있을 경우, 우선 적용 
5
최초 끄적임 
6
영역 구분 
7
설정 영역 
8
Optional 써 봄 
9 
public 
interface 
ClientFinder 
{ 
OpFonal<? 
extends 
Client> 
find(DhcpMessage 
message); 
} 
public 
interface 
Client 
{ 
public 
OpFonal<ClientClass> 
getClientClass(); 
... 
} 
public 
class 
ClientClass 
{ 
private 
Pool 
pool; 
public 
OpFonal<Pool> 
getPool() 
{ 
return 
OpFonal.ofNullable(pool); 
} 
... 
}
사용 가능 IP 범위 찾기 
public 
class 
UsableIpRangeFinderImpl 
implements 
UsableIpRangeFinder 
{ 
private 
ClientFinder 
clientFinder; 
@TransacFonal 
@Override 
public 
RangeResult 
find(DhcpMessage 
message) 
{ 
if 
(message 
== 
null) 
throw 
new 
IllegalArgumentExcepFon(); 
OpFonal<? 
extends 
Client> 
clientOpt 
= 
clientFinder.find(message); 
OpFonal<Pool> 
poolOpt 
= 
clientOpt.flatMap(c 
-­‐> 
c.getClientClass()).flatMap(cc 
-­‐> 
cc.getPool()); 
if 
(!poolOpt.isPresent()) 
return 
emptyResult(); 
Pool 
pool 
= 
poolOpt.get(); 
return 
new 
RangeResult( 
pool.getIpRange(), 
getNetworkConfig(pool), 
getMergedOpFons(clientOpt.get())); 
} 
private 
NetworkConfig 
getNetworkConfig(Pool 
pool) 
{ 
NetworkConfig 
nc 
= 
pool.getNetworkConfig(); 
return 
nc 
!= 
null 
? 
nc 
: 
pool.getSubnect().getNetworkConfig(); 
} 
private 
DhcpOpFons 
getMergedOpFons(Client 
client) 
{ 
... 
// 
다음 장에 코드 표시 
} 
10 
// 
OpFonal 
대신 null 
사용 경우 
Client 
client 
= 
clientFinder.find(message); 
if 
(client 
== 
null) 
return 
emptyResult(); 
if 
(client.getClientClass() 
== 
null) 
return 
emptyResult(); 
Pool 
pool 
= 
client.getClientClass().getPool(); 
if 
(pool 
== 
null) 
return 
emptyResult();
응답 옵션 생성 부분 
11 
// 
UsableIpRangeFinderImpl 
클래스 
private 
DhcpOpFons 
getMergedOpFons(Client 
client) 
{ 
DhcpOpFons 
cOpFons 
= 
client.getDhcpOpFons(); 
ClientClass 
clientClass 
= 
client.getClientClass().get(); 
DhcpOpFons 
ccOpFons 
= 
clientClass.getDhcpOpFons(); 
Pool 
pool 
= 
clientClass.getPool().get(); 
DhcpOpFons 
poolOpFons 
= 
pool.getDhcpOpFons(); 
DhcpOpFons 
subnetOpFons 
= 
pool.getSubnect().getDhcpOpFons(); 
return 
subnetOp9ons.merge(poolOpFons).merge(ccOpFons).merge(cOpFons); 
} 
// 
DhcpOpFons 
클래스 
public 
DhcpOpFons 
merge(DhcpOpFons 
other) 
{ 
DhcpOpFons 
newOpFons 
= 
new 
DhcpOpFons(this.opFonMap); 
if 
(other 
== 
null 
|| 
other.isEmpty()) 
return 
newOpFons; 
other.opFonMap.values().forEach(ov 
-­‐> 
newOpFons.add(ov)); 
return 
newOpFons; 
}
클라이언트 찾기 
• 두 종류의 클라이언트 매칭 
12 
public 
class 
HardwareAddressClient 
implements 
Client 
{ 
private 
HardwareAddress 
hardwareAddress; 
... 
} 
public 
class 
MatchClient 
implements 
Client 
{ 
private 
List<MatchPredicate> 
predicates 
= 
new 
ArrayList<>(); 
public 
boolean 
match(DhcpMessage 
message) 
{ 
return 
predicates.stream() 
.allMatch(p 
-­‐> 
p.match(message)); 
} 
... 
} 
1. 하드웨어 주소 
2. 
조건 일치
클라이언트 찾기 
13 
public 
class 
ClientFinderImpl 
implements 
ClientFinder 
{ 
private 
HardwareAddressClientRepository 
hardwareAddressClientRepository; 
private 
MatchClientRepository 
matchClientRepository; 
@Override 
public 
OpFonal<? 
extends 
Client> 
find(DhcpMessage 
message) 
{ 
if 
(message 
== 
null) 
return 
OpFonal.empty(); 
// 
맥주소 일치가 먼저 
OpFonal<HardwareAddressClient> 
hwAddrClient 
= 
hardwareAddressClientRepository.findByHardwareAddress(message.getChaddr()); 
if 
(hwAddrClient.isPresent()) 
return 
hwAddrClient; 
// 맥주소 일치 없으면, 조건 충족하는 Client 
검색 
List<MatchClient> 
clients 
= 
matchClientRepository.findAll(); 
for 
(MatchClient 
mclient 
: 
clients) 
if 
(mclient.match(message)) 
return 
OpFonal.of(mclient); 
return 
OpFonal.empty(); 
} 
...
MatchClient의 MatchPredicate 
• 다양한 MatchPredicate 가능성 
– 인터페이스/종류별 하위 타입으로 설계 
14
JPA 적용 
• DB 연동은 JPA를 사용하기로 결정 
– 기본 데이터 타입은 단순 매핑 설정 사용 
– InetAddress 등에 커스텀 변환기 사용 
• 적용하기 위한 몇 가지 코드 변경 
– DhcpOptions 필드 à List<DhcpOption> 
– MatchPredicate 계층 à 단일 클래스 
15
일부 매핑 설정 예 
16 
@Entity @Table(name = "SUBNET_CONFIG") 
public class SubnetConfig { 
@Id @Column(name = "ID") 
private Long id; 
@Column(name = "SUBNET") 
@Convert(converter = SubnetConverter.class) 
private Subnet subnet; 
@Embedded 
private NetworkConfig networkConfig; 
...// DhcpOptions는 다다다...음 장에 
@Embeddable 
public class NetworkConfig { 
@Column(name = "SUBNETMASK") 
@Convert(converter = SubnetMaskConverter.class) 
private SubnetMask subnetMask; 
@Column(name = "GATEWAY") 
@Convert(converter = InetAddressConverter.class) 
private InetAddress gateway; 
@Column(name = "DNSLIST") 
@Convert(converter = IpListConverter.class) 
private IpList domainServers; 
SUBNET_CONFIG 
-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ 
ID: 
int 
SUBNET: 
VARCHAR 
SUBNETMASK: 
VARCHAR 
GATEWAY: 
VARCHAR 
DNSLIST: 
VARCHAR
커스텀 변환기 
• 값 타입과 DB 한 개 컬럼 간의 변환 위함 
17 
@Entity @Table(name = "SUBNET_CONFIG") 
public class SubnetConfig { 
@Id @Column(name = "ID") 
private Long id; 
@Column(name = "SUBNET") 
@Convert(converter = SubnetConverter.class) 
private Subnet subnet; 
... 
} 
public class Subnet { 
private InetAddress networkAddress; 
private int bits; 
... 
} 
SUBNET_CONFIG 
-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ 
ID: 
int 
SUBNET: 
VARCHAR 
SUBNETMASK: 
VARCHAR 
GATEWAY: 
VARCHAR 
DNSLIST: 
VARCHAR 
Java 
new 
Subnet("192.168.0.1", 
24) 
컬럼 값 
192.168.0.1/24
커스텀 컨버터 구현 예 
18 
@Converter 
public class SubnetConverter implements AttributeConverter<Subnet, String> { 
@Override 
public String convertToDatabaseColumn(Subnet subnet) { 
if (subnet == null) return null; 
return subnet.toString(); 
} 
@Override 
public Subnet convertToEntityAttribute(String dbData) { 
if (dbData == null || dbData.isEmpty()) return null; 
return new Subnet(dbData); 
} 
}
커스텀 컨버터 구현 예 
19 
// List<InetAddress>와 "123.0.2.1,192.168.0.254" DB 데이터 간 변환 처리 
@Converter 
public class IpListConverter implements AttributeConverter<IpList, String> { 
@Override 
public String convertToDatabaseColumn(IpList attribute) { 
if (attribute == null || attribute.isEmpty()) return ""; 
else return attribute.toString(); 
} 
@Override 
public IpList convertToEntityAttribute(String dbData) { 
if (dbData == null) return null; 
String[] ips = dbData.split(","); 
List<InetAddress> addresses = new ArrayList<>(); 
for (String ip : ips) { 
try { 
addresses.add(InetAddress.getByName(ip)); 
} catch (UnknownHostException e) { 
throw new RuntimeException(....생략, e); 
} 
} 
return new IpList(addresses); 
} 
}
JPA 적용 과정에서의 모델 변화 
• DhcpOptions 구현 변경 
– 모델과 DB간 불일치 
20 
@Entity 
@Table(name = "CLIENT_MATCH") 
public class MatchClient implements Client { 
// @Embedded ?? 
private DhcpOptions options; 
public DhcpOptions getDhcpOptions() { 
return options; 
} 
... 
} 
// @Embeddable ?? 
public class DhcpOptions { 
// ?? 
private Map<DhcpOption, OptionValue> optionMap; 
} 
CM_DHCP_OPTIONS 
-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ 
CM_ID: 
int 
LIST_INDEX: 
int 
OPTION_CODE: 
int 
OPTION_VALUE: 
String 
키 
vs 
인덱스 
관리 화면에서 
입력한 순서대로 
보여줄 필요
JPA 적용 과정에서의 모델 변화 
• DhcpOptions 구현 변경 
– 모델과 DB간 불일치 
21 
@Entity @Table(name = "CLIENT_MATCH") 
public class MatchClient implements Client { 
private DhcpOptions options; 
public DhcpOptions getDhcpOptions() { 
return options; 
} 
... 
} 
@ElementCollection 
@CollectionTable(name = "CLIENT_MATCH_DHCP_OPTION", 
joinColumns = @JoinColumn(name = "CLIENT_MATCH_ID")) 
@OrderColumn(name = "LIST_INDEX") 
private List<OptionValue> options = new ArrayList<>(); 
public DhcpOptions getDhcpOptions() { // 메서드 시그너쳐 유지 
return new DhcpOptions(options); 
} 
CM_DHCP_OPTIONS 
-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ 
CM_ID: 
int 
LIST_INDEX: 
int 
OPTION_CODE: 
int 
OPTION_VALUE: 
String
JPA 적용 과정에서의 모델 변화 
• MatchClient 구현 변경 
– @Embeddable 타입의 상속 지원하지 않음 
– 계층을 단일 클래스로 변경 
22
23 
끝

모델링 연습 리뷰

  • 1.
    모델링 연습 리뷰 신림프로그래머 최범균 2014-11-25
  • 2.
    요구사항 1 •요청 메시지의 A옵션이 1인 클라이언트C1과 B옵 션이 2인 클라이언트 C2는 서브넷S1의 IP 대역에 서 네트워크 설정(IP 포함)을 할당받는다. • 맥주소가 M3인 클라이언트C3와 M4인 클라이언 트 C4는 서브넷S2의 IP 대역 중에서 앞 쪽 절반에 서 네트워크 설정을 할당받는다. • 요청 메시지의 E옵션이 5인 클라이언트C5와 맥주 소가 M6인 클라이언트C6은 서브넷S2의 IP 대역 중에서 뒤 쪽 절반에서 네트워크 설정을 할당받는 다. • 맥주소 일치가 우선한다. • 임대 IP는 클라이언트 단위로 관리된다. 2
  • 3.
    그림으로 보면 C1 C2 C3 (M3) C4 (M4) C5 IP 대역 S1 IP 대역 S2 A=1 B=1 C6 (M6) E=1 3
  • 4.
    그림에서 도출 C1 C2 C3 (M3) C4 (M4) C5 IP 대역 S1 IP 대역 S2 A=1 B=1 C6 (M6) E=1 옵션으로 클라이언트 구분 맥주소로 클라이언트 구분 다른 조건을 따르는 클라이언트들이 한 대역으로 묶임 한 대역을 나눠 쓸 수 있음 4
  • 5.
    요구사항 2 •응답 옵션 – IP 대역 별로 응답 옵션을 다르게 설정 – IP 풀 별로 응답 옵션을 다르게 설정 – 클라이언트 그룹(클래스) 별로 응답 옵션을 다르 게 설정 – 특정 조건을 충족하는 클라이언트들 별로 응답 옵 션을 다르게 설정 – 같은 응답 옵션이 존재할 경우 우선 순위 • 클라이언트별 > 클라이언트 그룹 > IP 풀 > 대역 • 네트워크 설정(DNS, GW, 서브넷마스크) – IP 대역 별로 네트워크 설정 – IP 풀 별로 다른 네트워크 설정 • IP 풀에 네트워크 설정이 있을 경우, 우선 적용 5
  • 6.
  • 7.
  • 8.
  • 9.
    Optional 써 봄 9 public interface ClientFinder { OpFonal<? extends Client> find(DhcpMessage message); } public interface Client { public OpFonal<ClientClass> getClientClass(); ... } public class ClientClass { private Pool pool; public OpFonal<Pool> getPool() { return OpFonal.ofNullable(pool); } ... }
  • 10.
    사용 가능 IP범위 찾기 public class UsableIpRangeFinderImpl implements UsableIpRangeFinder { private ClientFinder clientFinder; @TransacFonal @Override public RangeResult find(DhcpMessage message) { if (message == null) throw new IllegalArgumentExcepFon(); OpFonal<? extends Client> clientOpt = clientFinder.find(message); OpFonal<Pool> poolOpt = clientOpt.flatMap(c -­‐> c.getClientClass()).flatMap(cc -­‐> cc.getPool()); if (!poolOpt.isPresent()) return emptyResult(); Pool pool = poolOpt.get(); return new RangeResult( pool.getIpRange(), getNetworkConfig(pool), getMergedOpFons(clientOpt.get())); } private NetworkConfig getNetworkConfig(Pool pool) { NetworkConfig nc = pool.getNetworkConfig(); return nc != null ? nc : pool.getSubnect().getNetworkConfig(); } private DhcpOpFons getMergedOpFons(Client client) { ... // 다음 장에 코드 표시 } 10 // OpFonal 대신 null 사용 경우 Client client = clientFinder.find(message); if (client == null) return emptyResult(); if (client.getClientClass() == null) return emptyResult(); Pool pool = client.getClientClass().getPool(); if (pool == null) return emptyResult();
  • 11.
    응답 옵션 생성부분 11 // UsableIpRangeFinderImpl 클래스 private DhcpOpFons getMergedOpFons(Client client) { DhcpOpFons cOpFons = client.getDhcpOpFons(); ClientClass clientClass = client.getClientClass().get(); DhcpOpFons ccOpFons = clientClass.getDhcpOpFons(); Pool pool = clientClass.getPool().get(); DhcpOpFons poolOpFons = pool.getDhcpOpFons(); DhcpOpFons subnetOpFons = pool.getSubnect().getDhcpOpFons(); return subnetOp9ons.merge(poolOpFons).merge(ccOpFons).merge(cOpFons); } // DhcpOpFons 클래스 public DhcpOpFons merge(DhcpOpFons other) { DhcpOpFons newOpFons = new DhcpOpFons(this.opFonMap); if (other == null || other.isEmpty()) return newOpFons; other.opFonMap.values().forEach(ov -­‐> newOpFons.add(ov)); return newOpFons; }
  • 12.
    클라이언트 찾기 •두 종류의 클라이언트 매칭 12 public class HardwareAddressClient implements Client { private HardwareAddress hardwareAddress; ... } public class MatchClient implements Client { private List<MatchPredicate> predicates = new ArrayList<>(); public boolean match(DhcpMessage message) { return predicates.stream() .allMatch(p -­‐> p.match(message)); } ... } 1. 하드웨어 주소 2. 조건 일치
  • 13.
    클라이언트 찾기 13 public class ClientFinderImpl implements ClientFinder { private HardwareAddressClientRepository hardwareAddressClientRepository; private MatchClientRepository matchClientRepository; @Override public OpFonal<? extends Client> find(DhcpMessage message) { if (message == null) return OpFonal.empty(); // 맥주소 일치가 먼저 OpFonal<HardwareAddressClient> hwAddrClient = hardwareAddressClientRepository.findByHardwareAddress(message.getChaddr()); if (hwAddrClient.isPresent()) return hwAddrClient; // 맥주소 일치 없으면, 조건 충족하는 Client 검색 List<MatchClient> clients = matchClientRepository.findAll(); for (MatchClient mclient : clients) if (mclient.match(message)) return OpFonal.of(mclient); return OpFonal.empty(); } ...
  • 14.
    MatchClient의 MatchPredicate •다양한 MatchPredicate 가능성 – 인터페이스/종류별 하위 타입으로 설계 14
  • 15.
    JPA 적용 •DB 연동은 JPA를 사용하기로 결정 – 기본 데이터 타입은 단순 매핑 설정 사용 – InetAddress 등에 커스텀 변환기 사용 • 적용하기 위한 몇 가지 코드 변경 – DhcpOptions 필드 à List<DhcpOption> – MatchPredicate 계층 à 단일 클래스 15
  • 16.
    일부 매핑 설정예 16 @Entity @Table(name = "SUBNET_CONFIG") public class SubnetConfig { @Id @Column(name = "ID") private Long id; @Column(name = "SUBNET") @Convert(converter = SubnetConverter.class) private Subnet subnet; @Embedded private NetworkConfig networkConfig; ...// DhcpOptions는 다다다...음 장에 @Embeddable public class NetworkConfig { @Column(name = "SUBNETMASK") @Convert(converter = SubnetMaskConverter.class) private SubnetMask subnetMask; @Column(name = "GATEWAY") @Convert(converter = InetAddressConverter.class) private InetAddress gateway; @Column(name = "DNSLIST") @Convert(converter = IpListConverter.class) private IpList domainServers; SUBNET_CONFIG -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ ID: int SUBNET: VARCHAR SUBNETMASK: VARCHAR GATEWAY: VARCHAR DNSLIST: VARCHAR
  • 17.
    커스텀 변환기 •값 타입과 DB 한 개 컬럼 간의 변환 위함 17 @Entity @Table(name = "SUBNET_CONFIG") public class SubnetConfig { @Id @Column(name = "ID") private Long id; @Column(name = "SUBNET") @Convert(converter = SubnetConverter.class) private Subnet subnet; ... } public class Subnet { private InetAddress networkAddress; private int bits; ... } SUBNET_CONFIG -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ ID: int SUBNET: VARCHAR SUBNETMASK: VARCHAR GATEWAY: VARCHAR DNSLIST: VARCHAR Java new Subnet("192.168.0.1", 24) 컬럼 값 192.168.0.1/24
  • 18.
    커스텀 컨버터 구현예 18 @Converter public class SubnetConverter implements AttributeConverter<Subnet, String> { @Override public String convertToDatabaseColumn(Subnet subnet) { if (subnet == null) return null; return subnet.toString(); } @Override public Subnet convertToEntityAttribute(String dbData) { if (dbData == null || dbData.isEmpty()) return null; return new Subnet(dbData); } }
  • 19.
    커스텀 컨버터 구현예 19 // List<InetAddress>와 "123.0.2.1,192.168.0.254" DB 데이터 간 변환 처리 @Converter public class IpListConverter implements AttributeConverter<IpList, String> { @Override public String convertToDatabaseColumn(IpList attribute) { if (attribute == null || attribute.isEmpty()) return ""; else return attribute.toString(); } @Override public IpList convertToEntityAttribute(String dbData) { if (dbData == null) return null; String[] ips = dbData.split(","); List<InetAddress> addresses = new ArrayList<>(); for (String ip : ips) { try { addresses.add(InetAddress.getByName(ip)); } catch (UnknownHostException e) { throw new RuntimeException(....생략, e); } } return new IpList(addresses); } }
  • 20.
    JPA 적용 과정에서의모델 변화 • DhcpOptions 구현 변경 – 모델과 DB간 불일치 20 @Entity @Table(name = "CLIENT_MATCH") public class MatchClient implements Client { // @Embedded ?? private DhcpOptions options; public DhcpOptions getDhcpOptions() { return options; } ... } // @Embeddable ?? public class DhcpOptions { // ?? private Map<DhcpOption, OptionValue> optionMap; } CM_DHCP_OPTIONS -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ CM_ID: int LIST_INDEX: int OPTION_CODE: int OPTION_VALUE: String 키 vs 인덱스 관리 화면에서 입력한 순서대로 보여줄 필요
  • 21.
    JPA 적용 과정에서의모델 변화 • DhcpOptions 구현 변경 – 모델과 DB간 불일치 21 @Entity @Table(name = "CLIENT_MATCH") public class MatchClient implements Client { private DhcpOptions options; public DhcpOptions getDhcpOptions() { return options; } ... } @ElementCollection @CollectionTable(name = "CLIENT_MATCH_DHCP_OPTION", joinColumns = @JoinColumn(name = "CLIENT_MATCH_ID")) @OrderColumn(name = "LIST_INDEX") private List<OptionValue> options = new ArrayList<>(); public DhcpOptions getDhcpOptions() { // 메서드 시그너쳐 유지 return new DhcpOptions(options); } CM_DHCP_OPTIONS -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ CM_ID: int LIST_INDEX: int OPTION_CODE: int OPTION_VALUE: String
  • 22.
    JPA 적용 과정에서의모델 변화 • MatchClient 구현 변경 – @Embeddable 타입의 상속 지원하지 않음 – 계층을 단일 클래스로 변경 22
  • 23.