2022.11.25 [FRI]
- Day 80-
수업 주요 목차
- MyBatis(Cont.)-MapperProxy
- jQuery(Cont.)
- 진도 map
🤖Review
Framework의 목적은 최대한 많은 인프라스트럭쳐(컴포넌트, 디자인패턴 등)를 제공해서 개발자들이 비즈니스로직에 집중할 수 있도록 하는 것
🧚♂️ MyBatis는 Java app.과 SQL을 연결(mapping)해주는 역할을 한다.
🔎 Eclipse 실습 내용
1.
- AppConfig
package org.kosta.myproject.config;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // spring 설정 클래스
@ComponentScan("org.kosta.myproject") //component계열 bean생성 관리 및 DI 처리
@MapperScan("org.kosta.myproject.model") //MyBatis Mapper Proxy를 위한 설정 (@Mapper Interface를 스캔해서 구현체 Proxy를 생성)
public class AppConfig {
@Bean //method return value를 bean으로 등록, bean name은 메서드명
public DataSource dataSource() {
BasicDataSource dataSource=new BasicDataSource();
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
dataSource.setUrl("jdbc:oracle:thin:@13.209.97.194:1521:xe");
dataSource.setUsername("scott");
dataSource.setPassword("tiger");
return dataSource;
}
@Bean // 현 method가 return하는 SqlSessionFactory가 Bean 등록 관리, spring container가 DataSource 타입의 bean을 매개변수로 injection한다.
//sqlSession : MyBatis Framework에서 제공하는 data access logic(데이터 연동 메서드)을 제공하는 중요한 객체
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
//db 는 underscore로 합성어를 표현 (account_no) , java 는 camelcase로 합성어를 표현 (accountNo) db의 underscore와 java의 camelcase를 자동 매핑하는 설정
org.apache.ibatis.session.Configuration conf=new org.apache.ibatis.session.Configuration();
conf.setMapUnderscoreToCamelCase(true);
sqlSessionFactoryBean.setConfiguration(conf);
//해당 패키지 하위의 클래스들을 대상으로 클래스명으로 별칭을 부여
sqlSessionFactoryBean.setTypeAliasesPackage("org.kosta.myproject.model");
return sqlSessionFactoryBean.getObject();
}
@Bean //MyBatis를 이용해 db연동할 때 효과적으로 개발할 수 있도록 기능 지원, AOP를 이용해 Transaction처리할 수 있도록 지원
public SqlSessionTemplate sqlSession(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- @MapperScan : Mapper proxy를 위한 설정(@Mapper 어노테이션이 붙은 클래스의 proxy를 생성해준다.)
- 어제 xml 설정에서 db와 java에서의 합성어 표현 방식이 달라 별칭(AS ~)을 sql에 추가해주는 귀찮은 일을 막기 위해 자동으로 매핑해주는 설정을 추가했던 것처럼 java config.에도 추가해줄 수 있다. (Configuration 변수 생성 뒤에 MapUnderscoreToCamelCase(true) 메서드를 이렇게 set해준 뒤 sqlSessionFaxtoryBean에 configuration을 set해준다.)
- Model(MemberVO & MemberMapper.java & MemberMapper.xml)
package org.kosta.myproject.model;
public class MemberVO {
private String id;
private String password;
private String name;
private String address;
public MemberVO() {
super();
}
public MemberVO(String id, String password, String name, String address) {
super();
this.id = id;
this.password = password;
this.name = name;
this.address = address;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "회원정보 [id=" + id + ", password=" + password + ", name=" + name + ", address=" + address + "]";
}
}
package org.kosta.myproject.model;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper //MyBatis Mapper Proxy를 위한 어노테이션, 현 인터페이스의 구현체를 MyBatis Framework에서 자동생성
public interface MemberMapper {
MemberVO findMemberById(String id);
int getTotalMemberCount();
List<MemberVO> findAllMemberList();
int register(MemberVO memberVO);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.kosta.myproject.model.MemberMapper">
<select id="findMemberById" parameterType="string" resultType="MemberVO">
SELECT id, password, name, address FROM spring_member WHERE id=#{value}
</select>
<select id="getTotalMemberCount" resultType="int">
SELECT COUNT(*) FROM spring_member
</select>
<select id="findAllMemberList" resultType="MemberVO">
SELECT id, password, name, address FROM spring_member
</select>
<insert id="register" parameterType="MemberVO">
INSERT INTO spring_member(id, password, name, address) VALUES (#{id},#{password},#{name},#{address})
</insert>
</mapper>
- Test
package test.step1;
import java.util.List;
import org.kosta.myproject.config.AppConfig;
import org.kosta.myproject.model.MemberMapper;
import org.kosta.myproject.model.MemberVO;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestMyBatisMapperProxy {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
MemberMapper memberMapper=(MemberMapper) ctx.getBean("memberMapper"); //
//MemberMapper interface interface를 implements한 Proxy(구현체)가 spring container에 의해 반환된다.
//System.out.println(memberMapper);
//System.out.println(memberMapper.findMemberById("java"));
//회원수 조회
int memberCount=memberMapper.getTotalMemberCount();
System.out.println("총 회원수: "+memberCount);
System.out.println("****************************************************************************");
//회원목록 조회
List<MemberVO> list=memberMapper.findAllMemberList();
for(MemberVO memberVO:list) {
System.out.println(memberVO);
}
System.out.println("****************************************************************************");
//회원등록
MemberVO memberVO=new MemberVO("junit","a","김민재","나폴리");
if(memberMapper.findMemberById(memberVO.getId())==null) {
int result=memberMapper.register(memberVO);
System.out.println("가입한 회원 수: "+result);
} else {
System.out.println("회원 아이디가 중복되어 등록이 불가합니다");
}
ctx.close();
}
}
↓
2022-11-25 20:13:53 DEBUG o.k.m.m.M.getTotalMemberCount - ==> Preparing: SELECT COUNT(*) FROM spring_member
2022-11-25 20:13:53 DEBUG o.k.m.m.M.getTotalMemberCount - ==> Parameters:
2022-11-25 20:13:53 DEBUG o.k.m.m.M.getTotalMemberCount - <== Total: 1
총 회원수: 4
****************************************************************************
2022-11-25 20:13:53 DEBUG o.k.m.m.M.findAllMemberList - ==> Preparing: SELECT id, password, name, address FROM spring_member
2022-11-25 20:13:53 DEBUG o.k.m.m.M.findAllMemberList - ==> Parameters:
2022-11-25 20:13:53 DEBUG o.k.m.m.M.findAllMemberList - <== Total: 4
회원정보 [id=junit, password=a, name=김민재, address=나폴리]
회원정보 [id=mybatis, password=a, name=이강인, address=마요르카]
회원정보 [id=spring, password=a, name=손흥민, address=토트넘]
회원정보 [id=java, password=a, name=아이유, address=오리]
****************************************************************************
2022-11-25 20:13:53 DEBUG o.k.m.m.MemberMapper.findMemberById - ==> Preparing: SELECT id, password, name, address FROM spring_member WHERE id=?
2022-11-25 20:13:53 DEBUG o.k.m.m.MemberMapper.findMemberById - ==> Parameters: junit(String)
2022-11-25 20:13:53 DEBUG o.k.m.m.MemberMapper.findMemberById - <== Total: 1
회원 아이디가 중복되어 등록이 불가합니다
(회원 등록이 성공적으로 되면 "가입한 회원 수: 1"이라고 뜬다.)
- Model2 (AccountVO & AccountMapper.java & AccountMapper.xml)
package org.kosta.myproject.model;
public class AccountVO {
private long accountNo; //column name: account_no
private String name;
private long balance;
public AccountVO() {
super();
}
public AccountVO(String name, long balance) {
super();
this.name = name;
this.balance = balance;
}
public AccountVO(long accountNo, String name, long balance) {
super();
this.accountNo = accountNo;
this.name = name;
this.balance = balance;
}
public long getAccountNo() {
return accountNo;
}
public void setAccountNo(long accountNo) {
this.accountNo = accountNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getBalance() {
return balance;
}
public void setBalance(long balance) {
this.balance = balance;
}
@Override
public String toString() {
return "계좌정보 [accountNo=" + accountNo + ", name=" + name + ", balance=" + balance + "]";
}
}
package org.kosta.myproject.model;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper // MyBatis Mapper Proxy (예: 이 전 예제들의 DAOImpl)를 위한 현 interface를 구현한 클래스 생성
public interface AccountMapper {
AccountVO findAccountByNo(long accountNo);
List<AccountVO> findAllAccountListOrderByNoDesc();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.kosta.myproject.model.AccountMapper">
<select id="findAccountByNo" parameterType="long" resultType="AccountVO">
SELECT account_no,name,balance FROM spring_account WHERE account_no=#{value}
</select>
<select id="findAllAccountListOrderByNoDesc" resultType="AccountVO">
SELECT account_no,name,balance FROM spring_account ORDER BY account_no DESC
</select>
</mapper>
- Test
package test.step2;
import java.util.List;
import org.kosta.myproject.config.AppConfig;
import org.kosta.myproject.model.AccountMapper;
import org.kosta.myproject.model.AccountVO;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestMyBatisMapperProxy2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
AccountMapper accountMapper=(AccountMapper) ctx.getBean("accountMapper"); //DL
// accountMapper class를 implement한 proxy 객체가 container에 의해 전달된다.
System.out.println(accountMapper);
System.out.println("*****************************************************************");
//계좌번호로 계좌정보 조회
long accountNo=1;
AccountVO accountVO=accountMapper.findAccountByNo(accountNo);
System.out.println("검색결과: "+accountVO);
System.out.println("*****************************************************************");
//전체계좌조회
List<AccountVO> list=accountMapper.findAllAccountListOrderByNoDesc();
for(AccountVO atVO:list) {
System.out.println(atVO);
}
System.out.println("*****************************************************************");
ctx.close();
}
}
↓
org.apache.ibatis.binding.MapperProxy@79f227a9
*****************************************************************
2022-11-25 20:16:46 DEBUG o.k.m.m.A.findAccountByNo - ==> Preparing: SELECT account_no,name,balance FROM spring_account WHERE account_no=?
2022-11-25 20:16:47 DEBUG o.k.m.m.A.findAccountByNo - ==> Parameters: 1(Long)
2022-11-25 20:16:47 DEBUG o.k.m.m.A.findAccountByNo - <== Total: 1
검색결과: 계좌정보 [accountNo=1, name=손흥민, balance=100]
*****************************************************************
2022-11-25 20:16:47 DEBUG o.k.m.m.A.findAllAccountListOrderByNoDesc - ==> Preparing: SELECT account_no,name,balance FROM spring_account ORDER BY account_no DESC
2022-11-25 20:16:47 DEBUG o.k.m.m.A.findAllAccountListOrderByNoDesc - ==> Parameters:
2022-11-25 20:16:47 DEBUG o.k.m.m.A.findAllAccountListOrderByNoDesc - <== Total: 5
계좌정보 [accountNo=5, name=황의조, balance=500]
계좌정보 [accountNo=4, name=기성용, balance=400]
계좌정보 [accountNo=3, name=이강인, balance=300]
계좌정보 [accountNo=2, name=정우영, balance=200]
계좌정보 [accountNo=1, name=손흥민, balance=100]
*****************************************************************
2.
- AppConfig (위 예제와 동일)
- Model (ProductVO & ProductMapper.java & ProductMapper.xml)
package org.kosta.myproject.model;
public class ProductVO {
private long productNo;
private String name;
private String maker;
private long price;
public ProductVO() {
super();
}
public ProductVO(long productNo, String name, String maker, long price) {
super();
this.productNo = productNo;
this.name = name;
this.maker = maker;
this.price = price;
}
public long getProductNo() {
return productNo;
}
public void setProductNo(long productNo) {
this.productNo = productNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMaker() {
return maker;
}
public void setMaker(String maker) {
this.maker = maker;
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
@Override
public String toString() {
return "상품정보 [productNo=" + productNo + ", name=" + name + ", maker=" + maker + ", price=" + price + "]";
}
}
package org.kosta.myproject.model;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductMapper {
long getTotalProductCount();
List<ProductVO> findAllProductList();
List<ProductVO> findAllProductListByMakerByMaker(String maker);
List<ProductVO> findAllProductListByMakerByMakerAndPrice(ProductVO productVO);
List<ProductVO> findProductListByLowPriceAndHighPrice(Map<String, Object> map);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.kosta.myproject.model.ProductMapper">
<select id="getTotalProductCount" resultType="long">
SELECT COUNT(*) FROM spring_product
</select>
<sql id="selectProduct">
SELECT product_no,name,maker,price FROM spring_product
</sql>
<select id="findAllProductList" resultType="ProductVO">
<include refid="selectProduct"></include>
ORDER BY product_no DESC
</select>
<select id="findAllProductListByMakerByMaker" parameterType="string" resultType="ProductVO">
<include refid="selectProduct"></include>
WHERE maker=#{value} ORDER BY product_no DESC
</select>
<select id="findAllProductListByMakerByMakerAndPrice" parameterType="ProductVO" resultType="ProductVO">
<include refid="selectProduct"></include>
WHERE maker=#{maker} AND price>#{price} ORDER BY product_no DESC
</select>
<select id="findProductListByLowPriceAndHighPrice" parameterType="map" resultType="ProductVO">
<include refid="selectProduct"></include>
<![CDATA[WHERE price>#{LOWPRICE} AND price<#{HIGHPRICE} AND maker=#{MAKER} ORDER BY price DESC]]>
</select>
</mapper>
- Test 1 (총 상품 수)
package test.step1;
import org.kosta.myproject.config.AppConfig;
import org.kosta.myproject.model.ProductMapper;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestGetTotalProductCount {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
ProductMapper productMapper=(ProductMapper) ctx.getBean("productMapper");
long productCount=productMapper.getTotalProductCount();
System.out.println("총 상품 수: "+productCount);
ctx.close();
}
}
↓
총 상품 수: 5
- Test 2 (전체 상품 조회)
package test.step2;
import java.util.List;
import org.kosta.myproject.config.AppConfig;
import org.kosta.myproject.model.ProductMapper;
import org.kosta.myproject.model.ProductVO;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestFindAllProductList {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
ProductMapper productMapper=(ProductMapper) ctx.getBean("productMapper");
List<ProductVO> list=productMapper.findAllProductList();
for(ProductVO productVO:list) {
System.out.println(productVO);
}
ctx.close();
}
}
↓
상품정보 [productNo=5, name=갤럭시9, maker=삼성, price=120]
상품정보 [productNo=4, name=아이폰2, maker=애플, price=10]
상품정보 [productNo=3, name=아이폰7, maker=애플, price=50]
상품정보 [productNo=2, name=갤럭시8, maker=삼성, price=100]
상품정보 [productNo=1, name=아이폰9, maker=애플, price=150]
- Test 3 (maker가 애플인 상품들이 product_no 내림차순으로 정렬)
package test.step3;
import java.util.List;
import org.kosta.myproject.config.AppConfig;
import org.kosta.myproject.model.ProductMapper;
import org.kosta.myproject.model.ProductVO;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestFindAllProductListByMaker {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
ProductMapper productMapper=(ProductMapper) ctx.getBean("productMapper");
String maker="애플";
List<ProductVO> list=productMapper.findAllProductListByMakerByMaker(maker);
for(ProductVO productVO:list) {
System.out.println(productVO);
}
ctx.close();
}
}
↓
상품정보 [productNo=4, name=아이폰2, maker=애플, price=10]
상품정보 [productNo=3, name=아이폰7, maker=애플, price=50]
상품정보 [productNo=1, name=아이폰9, maker=애플, price=150]
- Test 4 ( maker가 애플이고 가격이 30이상인 상품들이 product_no 내림차순으로 정렬)
package test.step4;
import java.util.List;
import org.kosta.myproject.config.AppConfig;
import org.kosta.myproject.model.ProductMapper;
import org.kosta.myproject.model.ProductVO;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestFindProductListByMakerAndPrice {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
ProductMapper productMapper=(ProductMapper) ctx.getBean("productMapper");
//검색조건이 여러개일 때 MyBatis에서는 Object인 VO 또는 Map으로 처리가 된다.
String maker="애플";
long price=30;
//VO 생성 후 검색 조건을 할당한 후 전달
ProductVO productVO=new ProductVO();
productVO.setMaker(maker);
productVO.setPrice(price);
List<ProductVO> list=productMapper.findAllProductListByMakerByMakerAndPrice(productVO);
for(ProductVO productvo:list) {
System.out.println(productvo);
}
ctx.close();
}
}
↓
상품정보 [productNo=3, name=아이폰7, maker=애플, price=50]
상품정보 [productNo=1, name=아이폰9, maker=애플, price=150]
- Test 5 (삼성의 제품 중 최저가가 30을 초과하고 최고가 130 미만인 가격 상품을 조회)
package test.step5;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.kosta.myproject.config.AppConfig;
import org.kosta.myproject.model.ProductMapper;
import org.kosta.myproject.model.ProductVO;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestFindProductListByLowPriceAndHighPrice {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
ProductMapper productMapper=(ProductMapper) ctx.getBean("productMapper");
Map<String,Object> map=new HashMap<String,Object>();
map.put("LOWPRICE", 30L);
map.put("HIGHPRICE", 130L);
map.put("MAKER", "삼성");
List<ProductVO> list=productMapper.findProductListByLowPriceAndHighPrice(map);
for(ProductVO productvo:list) {
System.out.println(productvo);
}
ctx.close();
}
}
- 검색조건이 여러개일 때 MyBatis에서는 Object인 VO 또는 Map으로 처리가 된다.
- VO로 전달하기에는 부적함, 이럴때는 Map이 적절하다.
- Map<> ~=new HashMap<> (); → Map은 interface로 사용하는 것
↓
상품정보 [productNo=5, name=갤럭시9, maker=삼성, price=120]
상품정보 [productNo=2, name=갤럭시8, maker=삼성, price=100]
3. jQuery
요구사항 class testEvent 링크를 클릭했을 때 id termsChk 동의 체크박스에 체크 상태가 되어야만 다음페이지로 이동하게 한다 체크 상태가 아니면 alert() 으로 "약관에 동의하셔야 다음페이지로 이동할 수 있습니다" 메세지 보여주고 이동시키지 않는다 |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<title>step20-jquery-event</title>
</head>
<body>
<div class="container pt-3">
<input type="checkbox" id="termsChk">동의<br>
<a href="http://daum.net" class="testEvent">다음으로</a>
<script type="text/javascript">
$(function() {
$(".testEvent").click(function(event) {
if($("#termsChk").prop("checked")==false){
alert("약관에 동의하셔야 다음페이지로 이동할 수 있습니다");
event.preventDefault();
}
});
});
</script>
</div>
</body>
</html>
(이것도 가능하다!)
$(".testEvent").click(function() {
if($("#termsChk").prop("checked")==false){
alert("약관에 동의하셔야 다음페이지로 이동할 수 있습니다");
return false;
}
});
↓
![]() |
![]() |
🤮error🤮
강사님 풀이 듣지 않고 혼자서 해보려고 좀 서두르다보니 에러라고 하기엔 조금 어이가 없지만 alert 스펠링을 alrert로 잘못 써서 안됐었다 ㅎ...
//오늘의 질문
- AppConfig에 위치 설정해주는 부분에 대해 다시 설명 부탁드림
- 인터페이스인 Mapper.xml과 Mapper.java의 위치가 다를 경우에만 AppConfig에 위치를 설정
- java와 xml의 이름이 같아야 하는 이유는?
- 나름의 규칙이자 편하려고(이름이 다르면 다르게 설정을 해주어야 하는데 같으면 알아서 자동적으로 해주기 때문에 굳이 다르게 할 필요가 없음)
- interface에서의 method명, 리턴값, 매개변수 인자값과 mapper에서의 id, type들만 같게 설정해주면 되는 것인지?
- 맞습니당
- java에서 xml로 정보를 넘겨줄 때 타입이 각기다른 2가지 이상의 정보를 넘겨줄 때에는 어떻게 넘겨주는지?
- 똑같이 Map을 사용하되 generic은 <String, Object>로 설정해주면 된다.
진짜 너무 재밌다...어떻게 하지...면접때 달달 외울거 생각하면 약간 현기증 나는데 예제 풀고 그러는건 일단 너무 재밌어....
//오늘의 숙제
복습