CHAP08 - DDD-START/ONLINE-STUDY GitHub Wiki
8 ์ ๊ทธ๋ฆฌ๊ฑฐํธ ํธ๋์ญ์ ๊ด๋ฆฌ
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ํธ๋์ญ์
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์ ๊ธ ๊ธฐ๋ฒ
ํ ์ ๊ทธ๋ฆฌ๊ฑฐํธ(์ฃผ๋ฌธ)๋ฅผ ๋ ์ฌ์ฉ์๊ฐ ๊ฑฐ์ ๋์์ ๋ณ๊ฒฝํ ๋ ํธ๋์ญ์ ์ด ํ์ํ๋ค.
๋ฌธ์ ์ : ๋ฉ๋ชจ๋ฆฌ ์บ์ ์ฌ์ฉ ์ ํ๋ฉด, ๋ค๋ฅธ ๊ฐ์ฒด๋ฅผ ๊ตฌํด์จ๋ค.
์ด์์๋ ๋ฐฐ์ก์ค ์ํ๋ก ๋ณ๊ฒฝํ๋๋ฐ ๊ทธ ์ฌ์ด ๊ณ ๊ฐ์ ๋ฐฐ์ก์ง ์ ๋ณด๋ฅผ ๋ณ๊ฒฝํ ์๋ ์๊ฒ ๋๋ค. (์ผ๊ด์ฑ x)
ํธ๋์ญ์ ์ ์ด์ฉํ ํด๊ฒฐ ๋ฐฉ๋ฒ
- ์ด์์๊ฐ ๋ณ๊ฒฝํ๋ ๋์ ๊ณ ๊ฐ์ด ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ๋ชปํ๊ฒ ํ๋๊ฐ,
- ์ด์์๊ฐ ์กฐํํ ์ดํ ๊ณ ๊ฐ์ด ์ ๋ณด๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์ด์์๊ฐ (์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ) ๋ค์ ์กฐํํ ๋ค ์์ ํ๋๋ก ํ๋ค.
๊ฐ๋ฅํ๊ฒ ํ๋ ํธ๋์ญ์ ์ข ๋ฅ
- ์ ์ (Pessimistic)
- ๋น์ ์ (Optimistic)
- ์ฌ์ฉ์ด ๋๋ ๋๊น์ง ๋ค๋ฅธ ์ค๋ ๋๊ฐ ํด๋น ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ํ๋ ๊ฒ์ ๋ง๋ ๋ฐฉ์
- ๋ณดํต DBMS๊ฐ ์ ๊ณตํ๋ ํ (row) ๋จ์ ์ ๊ธ์ ์ฌ์ฉํ๋ค
์ค๋ ๋ 2๊ฐ ๋ธ๋กํน ๋์๋ค
์์ ์์ ์ ์ ์ฉ์
- ๊ณ ๊ฐ์ "์ด๋ฏธ ๋ฐฐ์ก์ด ์์๋์ด ๋ฐฐ์ก์ง ๋ฅผ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค" ์๋ด ๋ฌธ๊ตฌ ํ์ธ
JPA ์์ Pessimistic Lock ์ฌ์ฉ์ PESSIMISTIC_WRITE
Order order = entityManager.find(Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE);
์ ์ ์ ๊ทผ์ ์ฌ์ฉํ๋ฉด ๋ฐ๋๋ฝ ๋ฐ์๊ฐ๋ฅ์ฑ์ด ์๋ค (ํนํ ์ฌ์ฉ์ ๋ง์๋).
๋ฐ๋๋ฝ ๋ฐ์ ์๋๋ฆฌ์ค
- ์ค๋ ๋1: A ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ์ ์ ๊ธ ๊ตฌํจ
- ์ค๋ ๋2: B ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ์ ์ ๊ธ ๊ตฌํจ
- ์ค๋ ๋1: B ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ์ ์ ๊ธ ์๋
- ์ค๋ ๋2: A ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ์ ์ ๊ธ ์๋
์๋ก ์ ๊ทธ๊ณ ์๋ ์์์ ๊ธฐ๋ค๋ฆฌ๊ณ ์๋ค.
// ํด๊ฒฐ๋ฐฉ๋ฒ: Time Out ์ค์
Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.lock.timeout", 2000); // 2์ด
Order order = entityManager.find( Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE, hints);
์ฃผ์
DBMS์ ๋ฐ๋ผ ํํธ๊ฐ ์ ์ฉ๋์ง ์์ ์ ์๋ค. ๋ฒค๋์ ๋ฐ๋ผ, ๋๊ธฐ ์๊ฐ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ ํ์ธ ํ ์ฌ์ฉํ๊ธฐ.
- ์ ๊ธ์ ํด์ ๋์์ ์ ๊ทผํ๋ ๊ฒ์ ๋ง๋ ๋์ ๋ณ๊ฒฝํ ๋ฐ์ดํฐ๋ฅผ ์ค์ DBMS์ ๋ฐ์ํ๋ ์์ ์ ๋ณ๊ฒฝ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ๋ฐฉ์์ด๋ค.
์ ์ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ ์ ์๋ ์๋๋ฆฌ์ค
๋ฌธ์ ์ : ์ด์์๋ ๋ค๋ฅธ ๋ฐฐ์ก์ง๋ก ๋ฌผ๊ฑด์ ๋ฐ์กํ๊ฒ ๋๊ณ , ๋ฐฐ์ก์ ์๋ฑํ ๊ณณ์ผ๋ก~
๊ตฌํ ๋ฐฉ๋ฒ
// ๋ฒ์ ์ฌ์ฉ: 1์ฉ ์ฆ๊ฐ
UPDATE aggtable SET version = version + 1, colx = ?, coly = ?
WHERE aggid = ? and version = ํ์ฌ ๋ฒ์
์๋ก ๊ฐ์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์์ ์
JPA์ @Version
์ฌ์ฉ์
@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
@EmbeddedId
private OrderNo number;
@Version // UPDATE ์ฟผ๋ฆฌ ์คํ ๋ฒ์ UP
private long version;
//...
}
์ฟผ๋ฆฌ
UPDATE purchase_order SET ...์๋ต, version = version + 1
WHERE number = ? and version = 10
์ปจํธ๋กค๋ฌ์ ์์ฉ ์๋น์ค ์ฝ๋
@Controller
public class OrderController {
private ChangeShippingService changeShippingService;
@RequestMapping(value = "/changeshipping", method = RequestMethod.POST)
public String changeShipping(ChangeShippingRequest changeReq) {
try {
ChangeShippingService.changeShipping(changeReq);
return "changeShippingSuccess";
} catch(OptimisticLockingFailureException ex) {
// ํธ๋์ญ์
์ถฉ๋ ๋ฉ์์ง
return "changeShippingTxConflict";
}
//...
}
}
public class ChangeShippingService {
@Transactional
public void changeShipping(ChangeShippingRequest changeReq) {
Order order = orderRepository.findById(new OrderNo(changeReq. getNumber()));
checkNoOrder(order);
order.changeShippingInfo(changeReq.getShippingInfo()); // ํธ๋์ญ์
์ด ์ถฉ๋์ ์ฌ๊ธฐ์ OptimisticLockingFailureException ์์ธ ๋ฐ์ (์๋์ฝ๋ํ์ธ)
}
//...
}
์ด์ ์ฌ๋ก์ ์ ์ฉ์ ์ํ์ค ๋ค์ด์ด๊ทธ๋จ
๋น์ ์ ์ ๊ธ ๋ฐฉ์์ ์ฌ๋ฌ ํธ๋์ญ์ ์ผ๋ก ํ์ฅํ๋ ค๋ฉด ๋ฒ์ ์ ๋ณด๋ ํ๋ฉด ๋ทฐ์ ์ ๋ฌํ๋ค.
<!-- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ค ๋ ๋ทฐ ์ฝ๋๋ ๋ฒ์ ๊ฐ์ ํจ๊ป ์ ์กํ๋ค. ->
<form action="startShipping" method="post">
< input type="hidden" name="version" value="${orderDto.version}" > // ๋ฒ์
<input type="text" name="orderNumber" value="${orderDto.orderNumber}"
readonly>
//....
<input type="submit" value="๋ฐฐ์ก ์ํ๋ก ๋ณ๊ฒฝํ๊ธฐ">
</form>
์ฌ์ฉ์ ์์ฒญ ์ฒ๋ฆฌ ๋ฐ์ดํฐ ๋ฐ์๋
public class StartShippingRequest {
private String orderNumber;
private long version;
//...์์ฑ์, getter
}
์ด์์๊ฐ ๋ฐฐ์ก ์ํ ๋ณ๊ฒฝ ์ปจํธ๋กค๋ฌ์ ์๋น์ค
- 2 ์ข
๋ฅ ์์ธ: ๊ฐ๋ฐ์์๊ฒ ํธ๋์ญ์
์ถฉ๋์ด ๋ฐ์ํ ์์ ์ด ๋ค๋ฅธ ๊ฒ์ ๋ช
ํํ ์ ์ ์๊ฒ ํ๋ค.
- VersionConflictExcepton = ์ด๋ฏธ ๋๊ตฐ๊ฐ๊ฐ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ํ๋ค๋ ๊ฒ์ ์๋ฏธ
- OptimisticLockingFailureException = ๋๊ตฐ๊ฐ๊ฐ ๊ฑฐ์ ๋์์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ํ๋ค๋ ๊ฒ
@Controller
public class OrderAdminController {
private StartShippingService startShippingService;
@RequestMapping(value = "/startShipping", method = RequestMethod.POST)
public String startshipping(StartShippingRequest startReq) {
try {
startShippingService.startShipping(startReq);
return "shippingstarted";
} catch(OptimisticLockingFailureException | VersionConflictException ex) {
return "startShippingTxConflict";
}
}
//...
}
public class StartShippingService {
@PreAuthorize("hasRole('ADMIN')")
@Transactional
public void startShipping(StartShippingRequest req) {
Order order = orderRepository.findById(new OrderNo(req.getOrderNumber()));
checkOrder(order);
// ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ฒ์ ๊ณผ ์ผ์นํ๋ ๊ฒฝ์ฐ๋ง ๋ฐฐ์ก ์ํ (์ถ๋ฐ๋ก) ๋ณ๊ฒฝ
if (!order.matchversion(req.getVersion())){
throw new VersionConflictException(); // ํํ ๊ณ์ธต์ผ๋ก ๋๊ธด๋ค
}
order.startShipping();
}
//..
}
๋ฒ์ ์ถฉ๋ ์ํฉ์ ๋ํ ํ์ธ์ด ๋ช ์์ ์ผ๋ก ํ์ ์๋ค๋ฉด ์์ฉ ์๋น์ค์์ ํ๋ ์์ํฌ์ฉ ์ต์ ์ ๋ง ๋ฐ์์ํค๋๋ก ๊ตฌํํ๋ค.
public void startShipping(StartShippingRequest req) {
Order order = oNerRepository.findById(new OrderNo(req.gettrOrderNumber()));
checkOrder(order);
if (!order.matchversion(req.getVersion())) {
// ํ๋ ์์ํฌ๊ฐ ์ ๊ณตํ๋ ๋น์ ์ ํธ๋์ญ์
์ถฉ๋ ๊ด๋ จ ์ต์
์
์ฌ์ฉ
throw new OptimisticLockingFailureException("version conflict");
}
order.startShipping();
}
๋ฃจํธ๊ฐ ์๋ ๋ค๋ฅธ ์ํฐํฐ์ ๊ฐ๋ง ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ, (๋ฃจํธ ์ํฐํฐ ์์ฒด์ ๊ฐ์ด ๋ฐ๋๋๊ฒ ์๊ธฐ์) JPA๋ ๋ฃจํธ ์ํฐํฐ์ ๋ฒ์ ๊ฐ์ ์ฆ๊ฐํ์ง ์๋๋ค.
ํ์ง๋ง, ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๊ตฌ์ฑ์์ ์ค ์ผ๋ถ ๊ฐ์ด ๋ฐ๋๋ฉด ๋ ผ๋ฆฌ์ ์ผ๋ก ๋ฐ๋๊ฒ์ด๋ค.
// PTIMISTIC_FORCE_INCREMENT ์ฌ์ฉํด ๊ฐ์ ๋ก ์ฆ๊ฐ ์ฒ๋ฆฌ
// ๋ฃจํธ ์ํฐํฐ๊ฐ ์๋ ๋ค๋ฅธ ์ํฐํฐ๋ ๋ฐธ๋ฅ๊ฐ ๋ณ๊ฒฝ๋๋๋ผ๋ ๋ฒ์ ๊ฐ์ ์ฆ๊ฐ์ํฌ ์ ์๋ค.
@Repository
public class JpaOrderRepository implements OrderRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public Order findByIdOptimisticLockMode(OrderNo id) {
return entityManager.find(
Order.class, id, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
}
//...
}
๋ ์๊ฒฉํ๊ฒ ๋ฐ์ดํฐ ์ถฉ๋์ ๋ง๊ณ ์ถ๋ค๋ฉด, ๋๊ตฐ๊ฐ ์์ ํ๋ฉด์ ๋ณด๊ณ ์์ ๋ ์์ ํ๋ฉด ์์ฒด๋ฅผ ์คํํ์ง ๋ชปํ๋๋ก ํด์ผ ํ๋ค. ํ ํธ๋์ญ์ ๋ฒ์์์๋ง ์ ์ฉ๋๋ ์ ์ ์ ๊ธ ๋ฐฉ์์ด๋, ๋์ค์ ๋ฒ์ ์ถฉ๋์ ํ์ธํ๋ ๋น์ ์ ์ ๊ธ ๋ฐฉ์์ผ๋ก๋ ์ด๋ฅผ ๊ตฌํํ ์ ์๋ค. ์ด๋ ํ์ํ ๊ฒ์ด ์คํ๋ผ์ธ ์ ์ ์ ๊ธ ๋ฐฉ์์ด๋ค.
โ ๊ถ๊ธ- ์ ์ ํ๊ณ ๋น์ทํ๊ฑฐ ์๋๊ฐ โ
๋จ์ผ ํธ๋์ญ์ ์์ ๋์ ๋ณ๊ฒฝ์ ๋ง๋ ์ ์ ์ ๊ธ ๋ฐฉ์๊ณผ ๋ฌ๋ฆฌ ์คํ๋ผ์ธ ์ ์ ์ ๊ธ์ ์ฌ๋ฌ ํธ๋์ญ์ ์ ๊ฑธ์ณ ๋์ ๋ณ๊ฒฝ์ ๋ง๋๋ค.
์ํ์ค ๋ค์ด์ด๊ทธ๋จ
- ์ฌ์ฉ์ A๊ฐ ์์ ํ์ง ์์ ๊ฒฝ์ฐ ๋๋นํด, TimeOut์ ์ค์ ํด์ผ ํ๋ค.
์คํ๋ผ์ธ ์ ์ ์ ๊ธ 4๊ฐ์ง ๊ธฐ๋ฅ
- ์ ์ ์๋
- ์ ๊ธ ํ์ธ
- ์ ๊ธ ํด์
- ๋ฝ ์ ํจ ์๊ฐ ์ฐ์ฅ
public interface LockManager {
LockId tryLock(String type, String id) throws LockException;
void checkLock(LockId lockId) throws LockException;
void releaseLock(LockId lockId) throws LockException;
void extendLockExpiration(LockId lockId, long inc) throws LockException;
}
public class LockId {
private String value;
// ์์ฑ์, getter
}
์ง๋ผ์ ์ปจํผ๋ฐ์ค ์ฌ๋ก
- ํ์ด์ง๋ฅผ ์์ ํ๋ ๋์ ๋ค๋ฅธ ์ฌ๋๋ ์์ ์ด ๊ฐ๋ฅํ๋ค. ๋ค๋ง, Merge ๊ณผ์ ์ ๊ฑฐ์ณ์ค์ผ ํ๋ค.
HTML ๋ทฐ์ ์ปจํธ๋กค๋ฌ
// ๋ฐ์ดํฐ ์์ ํผ์ ๋์์ ์ ๊ทผํ๋ ๊ฒ์ ์ ์ดํ๋ ์ฝ๋์ ์
@RequestMapping("/some/edit/{id}")
public String editForm(@PathVariable("id") Long id, ModelMap model) {
// 1. ์คํ๋ผ์ธ ์ ์ ์ ๊ธ ์๋
LockId lockId = lockManager.tryLock("data", id);
// 2. ๊ธฐ๋ฅ ์คํ
Data data = someDao.select(id);
model.addAttribute("data", data);
// 3. ์ ๊ธ ํด์ ์ ์ฌ์ฉํ LockId๋ฅผ ๋ชจ๋ธ์ ์ถ๊ฐ
model.addAttribute("lockId", lockId);
return "editForm"
}
์ ๊ธ์ ํด์ ํ๋ ์ฝ๋
<form action="/some/edit/${data.id}" method="post">
...
<input type="hidden" name="lid" value="${lockId.value}">
...
</form>
// ------------------------------------------------
@RequestMapping(value = "/some/edit/{id}", method = RequestMethod.POST)
public String edit(@PathVariable("id") Long id,
@ModelAttribute("editReq") EditRequest editReq,
@RequestParam("lid") String lockIdValue) {
editReq.setId(id);
// 1. ์ ๊ธ ์ ์ ํ์ธ
LockId lockId = new LockId(lockIdValue);
lockManager.checkLock(lockId);
// 2. ๊ธฐ๋ฅ ์คํ
someEditService.edit(editReq);
model.addAttribute("data", data);
// 3. ์ ๊ธ ํด์
lockManager.releaseLock(lockId);
return "editSuccess";
}
๊ฐ์ข Lock ์ ํจ์ฑ ๊ฒ์ฌ RULE๋ค ์์
- ์ ๊ธ์ ์ ํจ ์๊ฐ์ด ์ง๋ฌ์ผ๋ฉด ์ด๋ฏธ ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์ ๊ธ์ ์ ์ ํ๋ค.
- ์ ๊ธ์ ์ ์ ํ์ง ์์ ์ฌ์ฉ์๊ฐ ๊ธฐ๋ฅ์ ์คํํ๋ค๋ฉด ๊ธฐ๋ฅ ์คํ์ ๋ง์์ผ ํ๋ค
์ ๊ธ์ ๋ํ ์ ๋ณด๋ฅผ ์ ์ฅํ ํ ์ด๋ธ๊ณผ ์ธ๋ฑ์ค ์์ฑ
์ฃผ์ํค ๋ชฉ์ : ๋์์ ๋์ฌ์ฉ์๊ฐ ํน์ ํ์ ์ ๋ฐ์ดํฐ์ ๋ํ ์ ๊ธ์ ๊ตฌํ๋ ๊ฒ์ ๋ฐฉ์ง
// MySQL ์ฌ์ฉ ์์
create table locks (
'type' varchar(255),
id varchar(255),
lockid varchar(255),
expiration_time datetime, // ์ ๊ธ์ ์ ํจ ์๊ฐ
primary key ('type', id)
) character set utf8;
create unique index locks_idx ON locks (lockid);
์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ๊ธ์ ์์ฒญ์
insert into locks values ๏ผ'Order','1', '์์ฑํ๏ผlockid', '2016-03-28 09:10:00'๏ผ;
LockData ํด๋์ค
public class LockData {
private String type;
private String id;
private String lockid;
private long expirationTime;
public LockData(String type, String id, String lockId, long expirationTime) {
this.type = type;
this.id = id;
this.lockId = lockId;
this.expirationTime= expirationTime;
}
public String getType() {
return type;
}
public String getId() {
return id;
}
public String getLockId() {
return lockId;
}
public long getExpirationTime() {
return expirationTime;
}
// ์ ํจ ์๊ฐ์ด ์ง๋ฌ๋์ง ํ์ธ
public boolean isExpired() {
return expirationTime < System.currentTimeMillis();
}
}
์คํ๋ง JdbcTemplate์ ์ด์ฉํ SpringLockManager์ tryLock() ๊ตฌํ
@Component
public class SpringLockManager implements LockManager {
private int lockTimeout = 5 * 60 * 1000;
private DdbcTemplate jdbcTemplate;
private RowMapper<LockData> lockDataRowMapper = (rs3 rowNum) ->
new LockData(rs.getString(1), rs.getString(2),
rs.getString(3), rs.getTimestamp(4).getTime());
@Transactional
@Override
public LockId tryLock(String type, String id) throws LockException {
checkAlreadyLocked(type, id);
LockId lockId = new LockId(UUID.randomUUID().toString());
locking(type, id, lockId);
return lockId;
}
private void checkAlreadyLocked(String type, String id) {
List<LockData> locks = jdbcTemplate.query(
"select * from locks where type = ? and id = ?",
lockDataRowMapper, type, id);
Optional<LockData> lockData = handleExpiration(locks);
if (lockData.isPresent()) throw new AlreadyLockedException();
}
private Optional<LockData> handleExpiration(List<LockData> locks) {
if (locks.isEmptyO) return Optional.empty();
LockData lockData = locks.get(0);
if (lockData.isExpired()) {
jdbcTemplate.update(
"delete from locks where type = ? and id = ?",
lockData.getType(), lockData.getId());
return Optional.empty();
} else {
return Optional.of(lockData);
}
}
private void locking(String type. String id, LockId lockId) {
try {
int updatedCount = jdbcTemplate.update(
"insert into locks values (?,?,?,?)",
type, id, lockId.getValue(), new Timestamp(getExpirationTime()));
if (updatedCount == 0) throw new LockingFailException();
} catch (DuplicateKeyException e) {
throw new LockingFailException(e);
}
}
private long getExpirationTime() {
return System.currentTimeMillis() + lockTimeout;
}
@Override
public void checkLock(Lockid lockid) throws LockException {
Optional<LockData> lockData = getLockData(lockid);
if (!lockData.isPresent()) throw new NoLockException();
}
private Optional<LockData> getLockData(Lockid lockid) {
List<LockData> locks = jdbcTemplate.query(
"select * from locks where lockid =
lockDataRowMapper, lockid.getValue());
return handleExpiration(locks);
}
@Transactional
@Override
public void extendLockExpiration(Lockid lockId, long inc) throws LockException {
Optional<LockData> lockDataOpt = getLockData(lockid);
LockData lockData =
lockDataOpt.orElseThrow(() -> new NoLockException());
jdbcTemplate.update(
"update locks set expiration_time = ? where type = ? AND id = ?",
new Timestamp(lockData.getTimestamp() + inc),
lockData.getType(), lockData.getId());
}
@Transactional
@Override
public void releaseLock(LockId lockId) throws LockException {
jdbcTemplate.update("delete from locks where lockid = ?", lockId.getValue());
}
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}