AOP
joinpoint : 메소드를 호출하는 시점
advice : joinpoint에서 실행되어야 할 코드
weaving : Joinpoint들을 Advice로 감싸는 작업
kr.or.ddit.aop 패키지 내에 ServiceLoggerAdvice 클래스 생성
@Aspect , @Component , @Slf4j 어노테이션 선언
@Component는 해당 클래스가 스프링 빈으로 생성될 수 있게 해준다.
@RequestMapping은 해당 클래스가 Controller 클래스의 기능을 수행할 수 있게 해준다.
@Controller는 위의 2가지 어노테이션을 모두 포함하고 있다.
**
스프링 부트 3.0(스프링 프레임워크 6.0)부터는 클래스 레벨에 @RequestMapping이 있어도 스프링
컨트롤러로 인식하지 않는다.
오직 @Controller가 있어야 스프링 컨트롤러로 인식 혹은 @RestController도 인식가능
( RequestMappingHandlerMapping에서 @RequestMapping는 인식하지 않고, @Controller만 인식)
package kr.or.ddit.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/*
Aspect(관점) : AOP(Aspect Oriented Programming)의 단위가 되는 횡단 관심사
- 횡단 관심사(Cross-Cutting Concern) : 핵심(core) 비즈니스 로직(삼겹살구워먹기, 빵또아의 아이스크림)과
다소 거리가 있지만, 여러 모듈에서 공통적이고 반복적인 처리를 요구하는 내용(불판닦기, 불판교체, 빵또아의 빵)
- 횡단 관심사 분리(Separation Of Cross-Cutting Concern) : 횡단 관심사에 해당하는
부분(불판닦기, 불판교체, 빵또아의 빵)을 분리해서 한 곳으로 모으는 것을 의미
- Component : @Aspect와 짝궁. component-scan시 "여기 봐주세요"라는 의미
- JoinPoint : 어드바이스가 적용될 수 있는 위치
- Advice(로그출력한다) : 어떤 부가기능(불판닦기)을 언제(삼겹살을 굽기 전(Before)에) 사용할지 정의
* 언제?
- Before : 조인포인트(createPost()) 전에 실행. (삼겹살을 굽기 직전에)
- After : 조인포인트(createPost())에서 처리가 완료된 후 실행(삽겹살을 굽고 먹은 직후 실행)
- Around : 조인포인트(createPost()) 전후에 실행(삽겹살을 굽기 직전과 먹은 직후 실행)
- After Returning : 조인포인트(createPost())가 정상적으로 종료 후 실행
- After Throwing : 조인포인트(createPost())에서 예외 발생 시 실행. 예외가 발생안되면 실행 안함
*/
@Slf4j
@Component
@Aspect
public class ServiceLoggerAdvice {
//로보트에 : AOP대상(로그, 보안, 트랜잭션, 에러)
//포인트컷 표현식. 별쩜쩜별괄호쩜쩜괄호
//excution : 포인트컷(대상(메소드)을 선별하는 것) 지정자
//(* : 임의의 1개의 리턴타입
//.. : 임의의 0개 이상
//kr.or.ddit.*..*(..) : 패키지 밑의 각각의 패키지가 있고
// 그 하위에 모든 파일/패키지
// 각각의 메소드가 있고
// (..) : 모든 파라미터
//결론 : 포인트컷에 포함된 메서드를 대상으로 그 메서드가 실행되기 전에 로그를 출력해보자
//Before어드바이스 : 조인 포인트 전에 실행됨. 예외가 발생하는 경우만 제외하고 항상 실행됨
// ↓↓ : kr.or.ddit.* => kr.or.ddit 패키지 내의 1레벨 수준 ,
// ↓↓ : kr.or.ddit.*..* => kr.or.ddit 패키지 내의 1레벨 수준 패키지(바로 하위패키지) 내의 모든 메소드 ,
// ↓↓ : kr.or.ddit.*..*(..) => 패키지 내의 1레벨 수준 패키지 내의 모든 메소드 , 모든 파라미터
@Before("execution(* kr.or.ddit.*..*(..))")
public void startLog(JoinPoint jp) {
log.info("startLog");
//.getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌. 파라미터 타입은 무엇인지 보여줌
// kr.or.ddit.service.BoardServiceImpl.register(BoardVO)
log.info("startLog : " + jp.getSignature());
//.getArgs() : 전달 된 파라미터 정보를 보여줌
// [BoardVO [boardNo=127,title=개똥이]]
log.info("startLog : " + Arrays.deepToString(jp.getArgs()));
}
}
실행 후 콘솔창에서 aop 컴포넌트 적용 확인
Before 다음엔 After , ServiceLoggerAdvice에 메소드 추가작성
// AfterReturning 어드바이스
// 조인 포인트가 정상적으로 종료한 후에 실행됨. 예외 발생 시 실행 안됨
@AfterReturning("execution(* kr.or.ddit.*..*(..))")
public void logReturning(JoinPoint jp) {
log.info("logReturning");
// .getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌.
// 파라미터 타입은 무엇인지 보여줌
// kr.or.ddit.service.BoardService.register(BoardVO)
log.info("logReturning : " + jp.getSignature());
}
실행 후 콘솔창에서 AOP 컴포넌트 적용 확인
Controller - > Service -> ServiceImpl -> Mapper ->
ServiceLoggerAdvice에 메소드 추가작성
// After Throwing 어드바이스
// 조인 포인트에서 예외 발생 시 실행. 예외가 발생 안 되면 실행 안 됨
@AfterThrowing(pointcut="execution(* kr.or.ddit.*..*(..))", throwing="e")
public void logException(JoinPoint jp , Exception e) {
log.info("logException");
// .getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌.
// 파라미터 타입은 무엇인지 보여줌
// kr.or.ddit.service.BoardService.register(BoardVO)
log.info("logException : " + jp.getSignature());
// 예외 메시지를 보여줌
log.info("logException" + e);
}
// After 어드바이스
// 조인 포인트 완료 후 실행 . 예외 발생이 되더라도 항상 실행 됨
@AfterThrowing("execution(* kr.or.ddit.*..*(..))")
public void endLog(JoinPoint jp) {
log.info("endLog");
//.getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌. 파라미터 타입은 무엇인지 보여줌
// kr.or.ddit.service.BoardServiceImpl.register(BoardVO)
log.info("endLog : " + jp.getSignature());
//.getArgs() : 전달 된 파라미터 정보를 보여줌
// [BoardVO [boardNo=127,title=개똥이]]
log.info("endLog : " + Arrays.deepToString(jp.getArgs()));
}
ServiceLoggerAdvice에 메소드 추가작성
// ProceedingJoinPoint : around 어드바이스에서 사용함
// 횡단관심사 - 포인트컷 대상 core메소드 - 횡단관심사
// 스프링 프레임워크가 컨트롤 하고 있는 비즈니스로직 호출을 가로챔. 책임이 around 어드바이스로 전가됨
// 그래서 비즈니스 메소드에 대한 정보를 around 어드바이스 메소드가 가지고 있어야 하고
// 그 정보를 스프링 컨테이너가 around 어드바이스 메소드로 넘겨주면
// ProceedingJoingPoint 객체로 받아서 around 어드바이스가 컨트롤 시 활용함
@Around("execution(* kr.or.ddit.*..*(..))" )
public Object timeLog(ProceedingJoinPoint pjp) throws Throwable {
// 메소드 실행 직전 시간 체킹
long startTime = System.currentTimeMillis();
//.getArgs() : 전달 된 파라미터 정보를 보여줌
// [BoardVO [boardNo=127,title=개똥이]]
log.info("pjpStart : " + Arrays.toString(pjp.getArgs()));
// 메소드 실행(createPost(ItemVO itemVO)) 실행
Object result = pjp.proceed();
// 메소드 실행 직후 시간 체킹
long endTime = System.currentTimeMillis();
log.info("pjpEnd : " + Arrays.toString(pjp.getArgs()));
// 메소드 실행 직후 시간 - 메소드 실행 직전 시간 = 메소드 실행 시간
log.info("time : " +pjp.getSignature().getName() + " : " + (endTime - startTime));
return result;
}
실행 후 콘솔 창에서 확인
구글차트만들기
https://developers.google.com/chart?hl=ko
Charts | Google for Developers
브라우저 및 휴대기기용 양방향 차트를 추가하기 위한 리소스를 알아보세요.
developers.google.com
kr.or.ddit.chart 하위패키지 생성
controller , mapper , service , service.impl
ChartController 생성
package kr.or.ddit.chart.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
@RequestMapping("/chart")
@Slf4j
@Controller
public class ChartController {
//요청 URI : /chart/chartMain
@GetMapping("/chartMain")
public String chartMain() {
// forwarding
return "chart/chartMain";
}
}
views -> chart 폴더 생성 -> chart 폴더 내 chartMain.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<p><a href="/chart/chart01">구글차트(JSON)</a></p>
<p><a href="/chart/chart01Multi">구글멀티차트(JSON)</a></p>
<p><a href="/chart/chart02">구글차트(오라클DBMS연동)</a></p>
ChartController에 작성
// 요청URI : /chart/chart01
@GetMapping("/chart01")
public String chart01() {
//forwarding
return "chart/chart01";
}
view/chart/chart01.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<script type="text/javascript" src="/resources/js/jquery-3.6.0.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
// 구글 차트 라이브러리를 로딩
google.load("visualization" , "1" , {"packages" : ["corechart"] } );
//불러오는 작업이 완료되어 로딩이 되었다면 drawChart() 함수를 호출하는 콜백이 일어남 drawChart를 실행해라
google.setOnLoadCallback(drawChart);
//콜백함수
function drawChart() {
/*
//아작났어유
//dataType : 응답데이터의 형식
//contentType : 보내는데이터의 형식
//sync : 동기 / async : 비동기
*/
let jsonData = $.ajax({
url : "/resources/json/simpleData.json",
dataType : "json",
async : false
}).responseText;
console.log("jsonData : " + jsonData);
}
</script>
static 폴더 resources내에 json 폴더 생성 -> simpleData.json 파일 생성
simpleData.json파일
{
"cols":[
{"id":"","label":"상품명","pattern":"","type":"string"},
{"id":"","label":"금액","pattern":"","type":"number"}
],
"rows":[
{"c":[{"v":"귤"},{"v":35000}]},
{"c":[{"v":"딸기"},{"v":88000}]},
{"c":[{"v":"레몬"},{"v":16500}]},
{"c":[{"v":"오렌지"},{"v":20000}]},
{"c":[{"v":"키위"},{"v":30000}]},
{"c":[{"v":"포도"},{"v":15000}]}
]
}
파일 열때 open with - json editor로 열면 텍스트 색상 구분가능
http://localhost/resources/json/simpleData.json 로 직접 열어서 아래 페이지 확인
http://localhost/chart/chart01로 이동해서 아래 페이지 확인(f12 개발자도구 console.log확인)
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<script type="text/javascript" src="/resources/js/jquery-3.6.0.js"></script>
<script type="text/javascript"
src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
// 구글 차트 라이브러리를 로딩
google.load("visualization" , "1" , {"packages" : ["corechart"] } );
//불러오는 작업이 완료되어 로딩이 되었다면 drawChart() 함수를 호출하는 콜백이 일어남 drawChart를 실행해라
google.setOnLoadCallback(drawChart);
//콜백함수
function drawChart() {
/*
//아작났어유
//dataType : 응답데이터의 형식
//contentType : 보내는데이터의 형식
//sync : 동기 / async : 비동기
*/
let jsonData = $.ajax({
url : "/resources/json/simpleData.json",
dataType : "json",
async : false
}).responseText;
console.log("jsonData : " , jsonData);
// 구글 차트용 데이터 테이블 생성
let data = new google.visualization.DataTable(jsonData);
// 어떤 차트 모양으로 출력할지를 정해주자 => LineChart
// LineChart , ColumnChart, PieChart
let chart = new google.visualization.LineChart(
document.getElementById("chart_div")
);
// data 데이터를 chart모양으로 출력해보자
chart.draw(data ,
{
title : "차트 예제",
width : 500 ,
height : 400
}
);
}
</script>
<div class="row">
<div class="col-xl-8 col-lg-7">
<!-- Area Chart -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">상품 가격</h6>
</div>
<!-- 구글 차트가 보여질 영역 -->
<div id="chart_div"></div>
</div>
</div>
</div>
http://localhost/chart/chart01 페이지 접속 후 아래 차트 확인
google 멀티차트
ChartController내 아래코드 추가
// 요청URI : /chart/chart01Multi
@GetMapping("/chart01Multi")
public String chart01Multi() {
//forwarding
return "chart/chart01Multi";
}
chart01.jsp 복사해서 chart01Multi.jsp 파일 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<script type="text/javascript" src="/resources/js/jquery-3.6.0.js"></script>
<script type="text/javascript"
src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
// 구글 차트 라이브러리를 로딩
google.load("visualization" , "1" , {"packages" : ["corechart"] } );
//불러오는 작업이 완료되어 로딩이 되었다면 drawChart() 함수를 호출하는 콜백이 일어남 drawChart를 실행해라
google.setOnLoadCallback(drawChart);
google.setOnLoadCallback(drawChart2);
//콜백함수
function drawChart() {
/*
//아작났어유
//dataType : 응답데이터의 형식
//contentType : 보내는데이터의 형식
//sync : 동기 / async : 비동기
*/
let jsonData = $.ajax({
url : "/resources/json/simpleData.json",
dataType : "json",
async : false
}).responseText;
console.log("jsonData : " , jsonData);
// 구글 차트용 데이터 테이블 생성
let data = new google.visualization.DataTable(jsonData);
// 어떤 차트 모양으로 출력할지를 정해주자 => LineChart
// LineChart , ColumnChart, PieChart
let chart = new google.visualization.LineChart(
document.getElementById("chart_div")
);
// data 데이터를 chart모양으로 출력해보자
chart.draw(data ,
{
title : "차트 예제",
width : 500 ,
height : 400
}
);
}
//콜백함수2 drawChart2
function drawChart2() {
/*
//아작났어유
//dataType : 응답데이터의 형식
//contentType : 보내는데이터의 형식
//sync : 동기 / async : 비동기
*/
let jsonData = $.ajax({
url : "/resources/json/simpleData2.json",
dataType : "json",
async : false
}).responseText;
console.log("jsonData : " , jsonData);
// 구글 차트용 데이터 테이블 생성
let data = new google.visualization.DataTable(jsonData);
// 어떤 차트 모양으로 출력할지를 정해주자 => LineChart
// LineChart , ColumnChart, PieChart
let chart = new google.visualization.ColumnChart(
document.getElementById("chart_div2")
);
// data 데이터를 chart모양으로 출력해보자
chart.draw(data ,
{
title : "차트 예제",
width : 500 ,
height : 400
}
);
}
</script>
<div class="row">
<div class="col-xl-8 col-lg-7">
<!-- LineChart -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">상품 가격</h6>
</div>
<!-- 구글 차트가 보여질 영역 -->
<div id="chart_div"></div>
</div>
<!-- Column Chart -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">채소 가격</h6>
</div>
<!-- 구글 차트가 보여질 영역 -->
<div id="chart_div2"></div>
</div>
</div>
</div>
json 폴더내에 simpleData2.json 생성
//simpleData2.json 파일
{
"cols":[
{"id":"","label":"Topping","pattern":"","type":"string"},
{"id":"","label":"Slices","pattern":"","type":"number"}
],
"rows":[
{"c":[{"v":"Mushrooms"},{"v":3}]},
{"c":[{"v":"Onions"},{"v":1}]},
{"c":[{"v":"Olives"},{"v":1}]},
{"c":[{"v":"Zucchini"},{"v":1}]},
{"c":[{"v":"Pepperoni"},{"v":2}]}
]
}
http://localhost/chart/chart01Multi 페이지 접속 후 아래 차트 확인
오라클 sql developer 열기
fruit , vegetable 테이블 생성
create table fruit(
fruit_id varchar2(10),
fruit_nm varchar2(90),
fruit_amt number,
constraint pk_fruit primary key(fruit_id)
);
create table vegetable(
vege_id varchar2(20),
vege_nm varchar2(90),
vege_amt number,
constraint pk_vegetable primary key(vege_id)
);
/*
SET(집합) : 열의 개수가 동일 , 매핑되는 열의 자료형이 동일해야 함.
첫번째 집합 컬럼명으로 컬럼명이 결정됨
union : 합집합 , 중복데이터 1회 출력 , 자동정렬 됨
union all : 합집합 , 중복데이터 모두 출력, 자동정렬 안됨
intersect
minus
*/
select fruit_id , fruit_nm , fruit_amt , 'fruit' GUBUN
from fruit
union
select vege_id , vege_nm , vege_amt , 'vegetable' GUBUN
from vegetable;
테이블 생성 후 데이터 입력
Insert into VEGETABLE (VEGE_ID,VEGE_NM,VEGE_AMT) values ('VEGE01','Mushrooms',3);
Insert into VEGETABLE (VEGE_ID,VEGE_NM,VEGE_AMT) values ('VEGE02','Onions',1);
Insert into VEGETABLE (VEGE_ID,VEGE_NM,VEGE_AMT) values ('VEGE03','Olives',1);
Insert into VEGETABLE (VEGE_ID,VEGE_NM,VEGE_AMT) values ('VEGE04','Zucchini',1);
Insert into VEGETABLE (VEGE_ID,VEGE_NM,VEGE_AMT) values ('VEGE05','Pepperoni',2);
COMMIT;
with t as(
select fruit_id , fruit_nm , fruit_amt , 'fruit' GUBUN
from fruit
union all
select vege_id , vege_nm , vege_amt , 'vegetable'
from vegetable
)
select * from t
where t.gubun = 'fruit';
fruit_SQL.xml생성 후 쿼리문 작성
<?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="kr.or.ddit.chart.mapper.FruitMapper">
<select id="fruitList" parameterType="String" resultType="fruitVO">
with t as(
select fruit_id , fruit_nm , fruit_amt , 'fruit' gubun
from fruit
union all
select vege_id , vege_nm , vege_amt , 'vegetable'
from vegetable
)
select * from t
where t.gubun like '%fruit%'
</select>
</mapper>
root-context.xml 아래와 같이 수정
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="kr.or.ddit.**.mapper" />
</bean>
fruitVO 생성
package kr.or.ddit.chart.vo;
import lombok.Data;
@Data
public class FruitVO {
private String fruitId;
private String fruitNm;
private int fruitAmt;
private String gubun;
}
mybatisAlias.xml에 alias추가
<typeAlias type="kr.or.ddit.chart.vo.FruitVO" alias="fruitVO" />
fruit.xml select 쿼리문 수정
<?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="kr.or.ddit.chart.mapper.FruitMapper">
<select id="fruitList" parameterType="String" resultType="fruitVO">
with t as(
select fruit_id , fruit_nm , fruit_amt , 'fruit' gubun
from fruit
union all
select vege_id , vege_nm , vege_amt , 'vegetable'
from vegetable
)
select * from t
where 1 = 1
<if test="gubun!=null and gubun!=''">
and t.gubun like '%' || #{gubun} || '%'
</if>
</select>
</mapper>
mapper 인터페이스 생성
package kr.or.ddit.chart.mapper;
import java.util.List;
import kr.or.ddit.chart.vo.FruitVO;
public interface FruitMapper {
//<select id="fruitList" parameterType="String" resultType="fruitVO">
public List<FruitVO> fruitList(String gubun);
}
package kr.or.ddit.chart.service;
import java.util.List;
import kr.or.ddit.chart.vo.FruitVO;
public interface FruitService {
public List<FruitVO> fruitList(String gubun);
}
package kr.or.ddit.chart.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.or.ddit.chart.mapper.FruitMapper;
import kr.or.ddit.chart.service.FruitService;
import kr.or.ddit.chart.vo.FruitVO;
@Service
public class FruitServiceImpl implements FruitService {
@Autowired
FruitMapper fruitMapper;
/*
* 오버로딩 : 같은 이름의 메소드를 다르게 사용가능(파라미터 개수 , 타입 , 순서)
* 오버라이드 : 상속받은 부모의 메소드를 자식 메소드에서 재정의
*/
@Override
public List<FruitVO> fruitList(String gubun) {
return this.fruitMapper.fruitList(gubun);
}
}
ChartController에 메소드 추가 작성
// 요청URI : /chart/chart02
@ResponseBody
@GetMapping("/chart02")
public List<FruitVO> chart02() {
List<FruitVO> fruitVOList = this.fruitService.fruitList("fruit");
log.info("fruitVOList : " + fruitVOList);
//forwarding
return fruitVOList;
}
서버 재기동 후 http://localhost/chart/chart02 접속해서 아래 페이지 확인(json데이터 받아오는지)
FruitServiceImpl 구현 클래스 수정
package kr.or.ddit.chart.service.impl;
import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.or.ddit.chart.mapper.FruitMapper;
import kr.or.ddit.chart.service.FruitService;
import kr.or.ddit.chart.vo.FruitVO;
@Service
public class FruitServiceImpl implements FruitService {
@Autowired
FruitMapper fruitMapper;
/*
* 오버로딩 : 같은 이름의 메소드를 다르게 사용가능(파라미터 개수 , 타입 , 순서) 오버라이드 : 상속받은 부모의 메소드를 자식 메소드에서
* 재정의
*/
@Override
public List<FruitVO> fruitList(String gubun) {
/*
* JSONObject 만들기
*
*/
List<FruitVO> fruitVOList = this.fruitMapper.fruitList(gubun);
JSONObject data = new JSONObject();
// fruitVOList -> json 데이터로 변환
// 1. cols 배열에 넣기(상품명 , 금액)
/*
* "cols":[
* {"id":"","label":"상품명","pattern":"","type":"string"},
* {"id":"","label":"금액","pattern":"","type":"number"}
* ],
*/
JSONObject col1 = new JSONObject();
JSONObject col2 = new JSONObject();
JSONArray title = new JSONArray();
col1.put("label" , "상품명");
col1.put("type" , "string");
col2.put("label" , "금액");
col2.put("type" , "number");
title.add(col1);
title.add(col2);
data.put("cols" , title); // 여기까지 cols 완성
}
}