어차피 실행하고 있는 ibsheetTpdb.sql은 한개이고, context-mapper.xml 에서 ibsheetTemplate/*.xml 과 같이 아스타로 모든 파일을 다 열어놨기 때문에 시행은 다 된다.
기존에는 mapper 구조로 되어있긴 한데, 잘되는거 그냥 유지시켜보자.....(나중에 mapper로 다시 해보지 뭐)
xml 부터 변환
1. empMapper.xml -> IbsheetMapper.xml
namespace를 변경해줘야한다.
<!-- 기존 -->
<mapper namespace="egovframework.mapper.let.ibsheetTemplate.empMapper">
<!-- 변경 -->
<mapper namespace="egovframework.mapper.let.ibsheetTemplate.IbsheetMapper">
내용은 기존 내용 가져다 쓰기
<select id="selectEmpList" resultType="egovMap">
SELECT
EMPNO -- 사번
, ENAME -- 이름
, JOB -- 직무
, JOBSTATUS -- 직무 상태
, MGR -- 상사 번호
, HIREDATE -- 입사일
, SAL -- 급여
-- 월별 급여 시뮬레이션
, (SAL) AS JAN
, (SAL * 1.05) AS FEB
, (SAL * 0.95) AS MAR
, (SAL * 1.7) AS APR
, (SAL * 0.75) AS MAY
, COMM -- 커미션
, DEPTNO -- 부서번호
-- 급여 기준으로 텍스트 색상을 다르게 지정
, CASE
WHEN SAL > 4500 THEN '#FF0000'
ELSE '#0000FF'
END AS salTextColor
-- MGR이 없으면 편집 불가(0), 있으면 편집 가능(1)
, CASE
WHEN MGR IS NULL THEN '0'
ELSE '1'
END AS _edit
FROM EMP
WHERE 1 = 1
</select>
SAL 컬럼은 1월엔 급여 그대로, 2월엔 5% 인상, 3월엔 5% 감소 등 월별 급여를 시뮬레이션해서 보여주는 계산 컬럼이다.
색상을 지정하여 가져오면 TextColor 값을 핸들링할 수 있기 때문에 CASE 문에서 색상 부여,
추가로 MGR 여부에 따라 편집 여부도 부여한다.
정상동작 하는지 확인할 것.
2. empVO -> IbsheetVO
기존에는 VO가 없지만 인정성을 위해 부여.
3. EmpDAO-> IbsheetDAO
DAO 이름 수정하고 @Repository 이름 수정.
@Repository("ibsheetDAO")
public class IbsheetDAO extends EgovAbstractMapper {
public List<?> selectEmpList(Map<String, String> map){
return selectList("egovframework.mapper.let.ibsheetTemplate.IbsheetMapper.selectEmpList");
}
}
Color 값 등 어떤 타입이 올지 몰라서 제네릭 와일드카드로 열어두자.
리턴 타입: List<?>
이건 제네릭 와일드카드이며, 어떤 타입이든 허용한다는 의미
타입 안정성이 떨어짐. 사용 시 다운캐스팅이 필요할 수 있음
파라미터: Map<String, String> map
조건을 넣을 수 있음. 예: 이름, 부서번호 등
기존의 List<Map<String, Object>> 는 결과가 Map 형태로 확정되어 있어 사용 시 바로 .get("컬럼명") 가능함.
+ 타입 안정성이 높고 직관적으로 데이터를 다루기 쉬움
우선 그대로 만들어보고 추후에 리턴타입을 변경하자.
4. EmpSearvice -> IbsheetService
package egovframework.let.ibsheetTemplate.service;
import java.util.List;
import java.util.Map;
public interface IbsheetService {
public List<?> selectEmpList(Map<String, String> map) throws Exception;
}
[저장] 이라는 것 자체가 추가/수정/삭제를 상태값에 따라 반영하는 것을 한번에 하는 동작이다.
따라서 각각의 동작을 수행할 수 있는 SQL 문을 추가하자.
<!-- EMP 데이터 저장(추가/수정/삭제) -->
<insert id="insertEmpList" parameterType="map">
<![CDATA[
/** insertEmpList */
INSERT INTO EMP (
EMPNO
, ENAME
, JOB
, MGR
, HIREDATE
, SAL
, COMM
, DEPTNO
)
VALUES (
#{empno}
, #{ename}
, #{job}
, #{mgr}
, #{hiredate}
, #{sal}
, #{comm}
, #{deptno}
)
]]>
</insert>
<update id="updateEmpList" parameterType="map">
<![CDATA[
/** updateEmpList */
UPDATE EMP
SET
DEPTNO = #{deptno}
, COMM = #{comm}
, HIREDATE = #{hiredate}
, MGR = #{mgr}
, SAL = #{sal}
, JOB = #{job}
, ENAME = #{ename}
WHERE 1=1
AND EMPNO = #{empno}
]]>
</update>
<delete id="deleteEmpList" parameterType="map">
<![CDATA[
/** deleteEmpList */
DELETE
FROM EMP
WHERE 1=1
AND EMPNO = #{empno}
]]>
</delete>
2. IbsheetDAO
vo는 그대로니까 건들일 필요 없고, DAO 가서 Mapper를 연결해주자.
저장은 한번에 처리하지만 insert/update/delete는 각각 수행하므로 아래와 같이 따로따로 연결.
public void insertEmp(Map<String, Object> mp){
insert("egovframework.mapper.let.ibsheetTemplate.IbsheetMapper.insertEmpList", mp);
}
public void updateEmp(Map<String, Object> mp){
update("egovframework.mapper.let.ibsheetTemplate.IbsheetMapper.updateEmpList", mp);
}
public void deleteEmp(Map<String, Object> mp){
delete("egovframework.mapper.let.ibsheetTemplate.IbsheetMapper.deleteEmpList", mp);
}
3. IbsheetService
Service 가서 인터페이스 설정.
public void saveEmpList(List<?> li) throws Exception;
4. IbsheetServiceImpl
ServiceImpl 가서 인터페이스 설정.
@SuppressWarnings({ "unchecked", "static-access" })
public void saveEmpList(List<?> li) throws Exception {
for(int i=0;i<li.size();i++){
Map<String,Object> record = (Map<String,Object>)li.get(i);
IBSheetUtil iu = new IBSheetUtil();
iu.mapPrint(record);
//IBSheet 상태 컬럼의 값에 따른 작업 설정
switch((record.get("sStatus")+"").charAt(0)){
case 'I':
ibsheetDAO.insertEmp(record);
break;
case 'U':
ibsheetDAO.updateEmp(record);
break;
case 'D':
ibsheetDAO.deleteEmp(record);
break;
}
}
}
상태값에 따라 추가/수정/삭제 되도록 설정해주기.
5. IbsheetTemplateController
데이터 받으면 json 형태로 변환한 뒤 해당 리스트를 saveEmpList()로 보낸다.
추가로 IO에서 결과값 코드와 Message를 보내면 화면단에 뿌릴 수 있음.
@SuppressWarnings("rawtypes")
@RequestMapping(value = "/empSave.do")
public ModelAndView saveEmp(HttpServletRequest request, HttpServletResponse response) throws Exception {
IBSheetUtil ibUtil = new IBSheetUtil();
List<Map> list = ibUtil.getSheetData8(request.getParameter("Data"));
JSONObject result = new JSONObject();
JSONObject IO = new JSONObject();
try{
ibsheetService.saveEmpList(list);
IO.put("Result", 0);
IO.put("Message", "성공적으로 저장되었습니다.");
result.put("IO", IO);
}catch(Exception ex){
IO.put("Result", -10);
IO.put("Message", "저장 중 오류가 발생했습니다.");
result.put("IO", IO);
}
//JSON
ibUtil.setEtc("mySheet","age", "51");
ModelAndView model = new ModelAndView("jsonView",result);
System.out.println("model : "+ model);
//JSON 반환
return model;
}
근데 어라...왜 안되지.... 콘솔에 오류도 없고 하길래 왜 안되나 봤더니...
조회데이터랑 다르게 저장데이터는 " 와 같이 HTML 엔티티가 포함되어있다.
아무래도 클라이언트에서 json으로 데이터 받을 때 HTML 인코딩 상태로 넘어온 것 같다.
이거 디코딩 해주자.
<!-- pom.xml에 추가 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version> <!-- 버전은 프로젝트에 맞게 조정 -->
</dependency>
SET SCHEMA PUBLIC
SET DATABASE SQL SYNTAX ORA TRUE
--테이블 생성
CREATE MEMORY TABLE PUBLIC.EMP(EMPNO VARCHAR(4) NOT NULL PRIMARY KEY,ENAME VARCHAR(30),JOB VARCHAR(9),JOBSTATUS VARCHAR(9),MGR VARCHAR(4),HIREDATE VARCHAR(8),SAL DOUBLE,COMM DOUBLE,DEPTNO VARCHAR(2), LV INTEGER, ORDERNUM INTEGER)
CREATE MEMORY TABLE PUBLIC.DEPT(DEPTNO VARCHAR(2) NOT NULL PRIMARY KEY,DNAME VARCHAR(14),LOC VARCHAR(13))
SET DATABASE DEFAULT INITIAL SCHEMA PUBLIC
GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.SQL_IDENTIFIER TO PUBLIC
GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.YES_OR_NO TO PUBLIC
GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.TIME_STAMP TO PUBLIC
GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.CARDINAL_NUMBER TO PUBLIC
GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.CHARACTER_DATA TO PUBLIC
--데이터 삽입
INSERT INTO EMP VALUES('3092','JOHN','CLERK','OD','7782','20160601',4300.0E0,100.0E0,'10', 3, 14)
INSERT INTO EMP VALUES('7499','ALLEN','SALESMAN','OD','7698','20130619',1600.0E0,300.0E0,'30', 3, 11)
INSERT INTO EMP VALUES('7521','WARD','SALESMAN','LV','7698','19810222',1250.0E0,500.0E0,'30', 3, 7)
INSERT INTO EMP VALUES('7566','JONES','MANAGER','OD','7839','19810402',2975.0E0,0.0E0,'20', 2, 2)
INSERT INTO EMP VALUES('7654','MARTIN','SALESMAN','OD','7698','19810928',1250.0E0,1400.0E0,'10', 3, 9)
INSERT INTO EMP VALUES('7698','BLAKE','MANAGER','LV','7839','19810501',2850.0E0,0.0E0,'30', 2, 6)
INSERT INTO EMP VALUES('7782','CLARK','MANAGER','OD','7839','19810609',2450.0E0,0.0E0,'10', 2, 12)
INSERT INTO EMP VALUES('7788','SCOTT','ANALYST','OD','7566','19870713',99999.0E0,0.0E0,'10', 3, 4)
INSERT INTO EMP VALUES('7839','KING','PRESIDENT','OD','','19811117',5000.0E0,0.0E0,'10', 1, 1)
INSERT INTO EMP VALUES('7844','TURNER','SALESMAN','OD','7698','19810908',1500.0E0,0.0E0,'30', 3, 8)
INSERT INTO EMP VALUES('7876','ADAMS','CLERK','RS','7788','19870713',1100.0E0,0.0E0,'20', 4, 5)
INSERT INTO EMP VALUES('7900','JAMES','CLERK','SB','7698','19811203',950.0E0,0.0E0,'30', 3, 10)
INSERT INTO EMP VALUES('7902','FORD','ANALYST','SB','7566','19811203',3000.0E0,100.0E0,'20', 3, 3)
INSERT INTO EMP VALUES('7934','MILLER','CLERK','LV','7782','19820123',1300.0E0,200.0E0,'10', 3, 13)
INSERT INTO DEPT VALUES('10','ACCOUNTING','NEWYORK')
INSERT INTO DEPT VALUES('20','RESEARCH','DALLAS')
INSERT INTO DEPT VALUES('30','SALES','CHICAGO')
INSERT INTO DEPT VALUES('40','OPERATIONS','BOSTON')
INSERT INTO DEPT VALUES('50','R&D','NEWYORK')
INSERT INTO DEPT VALUES('60','TECH SUPPORT','DALLAS')
INSERT INTO DEPT VALUES('70','MARKETING','CHICAGO')
INSERT INTO DEPT VALUES('80','DESIGN','BOSTON')
INSERT INTO DEPT VALUES('90','FACTORY1','NEWYORK')
INSERT INTO DEPT VALUES('91','FACTORY2','CHICAGO')
2. MyBatis 매퍼 파일 작성
EMP 테이블 전체 데이터를 조회하는 MyBatis 매퍼(xml) 파일을 만드는 작업.
resources > egovframework > mapper > let > ibsheetTemplate 경로에 empMapper.xml 파일 생성
empMapper.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="egovframework.mapper.let.ibsheetTemplate.empMapper">
<!-- EMP 전체 조회 -->
<select id="selectEmpList" resultType="egovMap">
<![CDATA[
SELECT * FROM EMP
]]>
</select>
</mapper>
데이터는 간단하게 전체를 조회해본다.
namespace는 이 매퍼 파일을 참조할 때 사용할 이름이다.
selectEmpList는 DAO에서 호출할 메서드명이다.
resultType="egovMap"은 eGovFrame에서 자주 사용하는 결과 타입으로, key-value로 데이터를 받을 수 있다.
<![CDATA[ ]]>안에 SQL을 넣는 이유는, XML 특수 문자와 충돌하지 않게 하기 위함이다.
mapper-config.xml 파일에 Type Aliases 설정이 기본적으로 되어있다.
3. Service 인터페이스와 VO 생성
Service 인터페이스와 VO 객체를 만드는 작업.
java > egovframework > let > ibsheetTemplate > service 경로에 EmpService.java 파일 생성 java > egovframework > let > ibsheetTemplate > service 경로에 EmpVO.java 파일 생성
package egovframework.let.ibsheetTemplate.service;
import java.io.Serializable;
public class EmpVO implements Serializable {
private static final long serialVersionUID = 1L;
private String empno;
private String ename;
private String job;
private String jobstatus;
private String mgr;
private String hiredate;
private Double sal;
private Double comm;
private String deptno;
private Integer lv;
private Integer ordernum;
// getter/setter
public String getEmpno() { return empno; }
public void setEmpno(String empno) { this.empno = empno; }
public String getEname() { return ename; }
public void setEname(String ename) { this.ename = ename; }
public String getJob() { return job; }
public void setJob(String job) { this.job = job; }
public String getJobstatus() { return jobstatus; }
public void setJobstatus(String jobstatus) { this.jobstatus = jobstatus; }
public String getMgr() { return mgr; }
public void setMgr(String mgr) { this.mgr = mgr; }
public String getHiredate() { return hiredate; }
public void setHiredate(String hiredate) { this.hiredate = hiredate; }
public Double getSal() { return sal; }
public void setSal(Double sal) { this.sal = sal; }
public Double getComm() { return comm; }
public void setComm(Double comm) { this.comm = comm; }
public String getDeptno() { return deptno; }
public void setDeptno(String deptno) { this.deptno = deptno; }
public Integer getLv() { return lv; }
public void setLv(Integer lv) { this.lv = lv; }
public Integer getOrdernum() { return ordernum; }
public void setOrdernum(Integer ordernum) { this.ordernum = ordernum; }
}
EmpService는 컨트롤러와 DAO를 연결해주는 중간 역할이다.
EmpVO는 EMP 테이블의 데이터를 객체로 담는 용도이지만, 여기서는 egovMap을 사용해도 되기 때문에 선택 사항이다. (추후 확장성을 위해 만들어둠)
4. DAO 인터페이스 생성
DAO 인터페이스를 만드는 작업.
java > egovframework > let > ibsheetTemplate > service > impl경로에 EmpDAO.java 파일 생성
package egovframework.let.ibsheetTemplate.service.impl;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Repository;
import org.egovframe.rte.psl.dataaccess.EgovAbstractMapper;
@Repository("empDAO")
public class EmpDAO extends EgovAbstractMapper {
public List<Map<String, Object>> selectEmpList() {
return selectList("egovframework.mapper.let.ibsheetTemplate.empMapper.selectEmpList");
}
}
EgovAbstractMapper는 MyBatis의 SqlSession을 상속해서 selectList, selectOne 등을 바로 사용할 수 있도록 도와준다.
EgovAbstractMapper는 egovframework.rte.psl.dataaccess 모듈에 포함되어 있으므로, Maven 또는 Gradle에 아래 의존성을 추가해야함.
egov 4점대부터는 egovframework-> org.egovframe이렇게 바뀌었으니 잘 보기!
<dependency>
<groupId>org.egovframe.rte</groupId>
<artifactId>org.egovframe.rte.psl.dataaccess</artifactId>
<version>4.2.0</version> <!-- 사용 중인 버전 확인 후 적절히 설정 -->
</dependency>
selectList("네임스페이스.아이디") 형식으로 매퍼의 SQL을 호출한다.
empMapper.xml에서 namespace="egovframework.mapper.let.ibsheetTemplate.empMapper"로 지정했기 때문에 해당 네임스페이스를 그대로 사용한다.
이 두개가 같아야하고 아래 select문 id까지 맞게 찾아가는 거임
5. Service 구현 클래스 생성
DAO를 실제로 호출할 Service 구현체를 만드는 작업.
java > egovframework > let > ibsheetTemplate > service > impl경로에 EmpServiceImpl.java 파일 생성
DB 연결 설정 (DataSource), MyBatis Mapper 파일 위치 지정, SQL 스크립트 초기 실행
<jdbc:embedded-database> 부분에 아래 내용 추가.
HSQLDB 같은 내장 DB를 사용할 경우 초기화 SQL을 실행하는 부분이다.
<!-- hsql -->
<jdbc:embedded-database id="dataSource-hsql" type="HSQL">
<jdbc:script location= "classpath:/db/shtdb.sql"/>
<jdbc:script location="classpath:/db/ibsheetTpdb.sql"/> <!-- 추가된 SQL 파일 -->
</jdbc:embedded-database>
이 설정은 HSQLDB가 초기 실행 시 ibsheetTpdb.sql 파일을 읽어서 EMP 테이블 등 필요한 테이블을 생성한다.
메모리 모드일 경우 매번 새로 실행되므로 필수!
2) context-mapper.xml
MyBatis 관련 설정, Mapper XML 파일 등록, SQL 실행 환경 구성
<property name="mapperLocations"> 아래 mapper 경로 추가.
<!-- Mybatis setup for Mybatis Database Layer -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:/egovframework/mapper/config/mapper-config.xml" />
<property name="mapperLocations">
<list>
<value>classpath:/egovframework/mapper/let/**/*_${Globals.DbType}.xml</value>
<value>classpath:/egovframework/mapper/let/ibsheetTemplate/*.xml</value> <!-- xml 파일 경로 추가 -->
</list>
</property>
</bean>
직접 XML 파일 경로를 명시해서 매퍼 파일을 등록한다.
3) egov-com-servlet.xml
Spring MVC 설정, 뷰(View) 관련 설정, 컨트롤러 컴포넌트 스캔 등
Spring MVC에서 JSON 데이터를 반환할 수 있도록 구성한다.
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class java.util.ArrayList] with preset Content-Type 'null']
현재 이런 오류가 나는데,
컨트롤러에서 List<Map<String, Object>> 형태의 데이터를 반환하고 있으며,
@ResponseBody와 produces = "application/json" 도 사용하고 있으므로, 정상적으로 JSON 형태로 응답이 나가야 함.
이 에러는 Spring MVC에서 JSON 변환에 필요한 HttpMessageConverter를 찾을 수 없어서 발생하는 문제이다.
즉, ArrayList<Map<String, Object>>를 JSON으로 변환할 수 있는 변환기가 누락되어 있다는 뜻.
pom.xml 에서 jackson-databind 이 있는지 확인하거나,
spring 설정파일인 dispacher-servlet.xml(여기서는 egov-com-servlet.xml) 에서 mvc:annotation-driven 태그가 있는지 확인.
수동설정과 자동설정이 함께 있음
GPT에게 물어보니 아래와 같이 대답했다.
당신의 dispatcher-servlet.xml에 다음 두 가지 설정이 동시에 존재합니다:
⚠️ 이 수동 설정 때문에 annotation-driven이 자동으로 등록하는 MappingJackson2HttpMessageConverter가 무시되고 있습니다.
Jackson 변환기를 수동 설정에 추가하거나, 아래처럼 주석처리하는 방법이 있다.
난 메시지 컨버터를 추가해줬다.
<!-- 화면처리용 JSP 파일명의 prefix, suffix 처리에 대한 mvc 설정 -->
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" p:order="1"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="webBindingInitializer">
<bean class="egovframework.com.cmm.web.EgovBindingInitializer"/>
</property>
<property name="messageConverters">
<list>
<!-- JSON 변환을 위한 메시지 컨버터 추가 -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</list>
</property>
</bean>
+++ ModelAndView를 쓰는 경우 아래처럼 사용
<!-- json 관련 설정 @@@ -->
<!-- View 이름이 Bean 이름과 같을 때 그 Bean을 뷰로 사용함 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="0">
</bean>
<!-- 실제 JSON 출력을 담당하는 View 객체 -->
<bean id="jsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" p:contentType="application/json;charset=UTF-8">
</bean>
<!-- json 관련 설정 끝-->
viewResolver : 컨트롤러에서 반환하는 뷰 이름을 실제 JSP 경로로 매핑 jsonView: @ResponseBody 없이도 JSON 응답을 보낼 수 있게 하는 뷰 설정
위 Controller랑은 관련이 없긴한데.... 아래와 같이 ModelAndView를 쓰는 경우에는 아래처럼 jsonView를 지정해야한다.
egovFrame 4.2 에서 기본 템플릿인 simple homepage에 기존 샘플을 붙이기로 했다.
여기서 제공하는 구조를 갖다 쓰기 위함....
기존에 쓰던 샘플도 HSQL 을 사용해서 DB연결을 했고 샘플 전용 SQL 파일을 넣어 테이블을 생성해서 활용했다.
이번에도 해당 테이블을 활용해보도록 하자.
1. 구조파악
resources 아래 프로젝트 구조
1️⃣ shtdb.sql (DB 스크립트 파일)
HSQLDB를 사용할 때, 초기 테이블 생성 및 샘플 데이터 삽입을 위한 SQL 스크립트 파일.
HSQLDB는 Java 기반의 오픈 소스 관계형 데이터베이스(RDBMS)로, 로컬에서 개발할 때 자바 내장 DB를 사용해서 간단한 SQL문 실행과 테스트를 진행할 수 있다.
특징으로는 서버를 껐다가 켜면 데이터가 초기화된다.
shtdb.sql 같은 SQL 파일이 HSQLDB에서 데이터를 초기화하기 위해 사용되는 것.
따라서 내부에 보면
CREATE TABLE ... (테이블 생성)
INSERT INTO ... (초기 데이터 삽입)
ALTER TABLE ... (제약 조건 추가)
이런 내용들이 들어가 있음.
2️⃣ globals.properties (전역 설정 파일 - DB 연결 등)
eGovFrame에서 사용하는 전역 설정값을 저장하는 파일이다.
DB 연결 정보, 파일 경로, 공통 변수 등을 정의한다.
#-----------------------------------------------------------------------
#
# globals.properties : 시스템
#
#-----------------------------------------------------------------------
# 1. key = value 구조입니다.
# 2. key값은 공백문자를 포함불가, value값은 공백문자를 가능
# 3. key값으로 한글을 사용불가, value값은 한글사용이 가능
# 4. 줄을 바꿀 필요가 있으면 '\'를 라인의 끝에 추가(만약 '\'문자를 사용해야 하는 경우는 '\\'를 사용)
# 5. Windows에서의 디렉토리 표시 : '\\' or '/' ('\' 사용하면 안됨)
# 6. Unix에서의 디렉토리 표시 : '/'
# 7. 주석문 처리는 #사용
# 8. value값 뒤에 스페이스가 존재하는 경우 서블릿에서 참조할때는 에러발생할 수 있으므로 trim()하거나 마지막 공백없이 properties 값을 설정할것
#-----------------------------------------------------------------------
# 운영서버 타입(WINDOWS, UNIX)
Globals.OsType = WINDOWS
# G4C 연결용 IP (localhost)
Globals.LocalIp = 127.0.0.1
# DB서버 타입(mysql,oracle,altibase,tibero) - datasource 및 sqlMap 파일 지정에 사용됨
Globals.DbType = hsql
Globals.UserName=sa
Globals.Password=
# mysql
#Globals.DriverClassName=net.sf.log4jdbc.DriverSpy
#Globals.Url=jdbc:log4jdbc:mysql://127.0.0.1:3306/sht
#oracle
#Globals.DriverClassName=oracle.jdbc.driver.OracleDriver
#Globals.Url=jdbc:oracle:thin:@127.0.0.1:1521:egovfrm
#Altibase
#Globals.DriverClassName=Altibase.jdbc.driver.AltibaseDriver
#Globals.Url=jdbc:Altibase://127.0.0.1:1721/egovfrm?encoding=UTF-8
#Tibero
#Globals.DriverClassName=com.tmax.tibero.jdbc.TbDriver
#Globals.Url=jdbc:tibero:thin:@127.0.0.1:1821:egovfrm
#cubrid
#Globals.DriverClassName=cubrid.jdbc.driver.CUBRIDDriver
#Globals.Url=jdbc:cubrid:127.0.0.1:33000:sht:::?charset=utf-8
#Hsql - local hssql 사용시에 적용
Globals.DriverClassName=net.sf.log4jdbc.DriverSpy
Globals.Url=jdbc:log4jdbc:hsqldb:hsql://127.0.0.1/sampledb
# MainPage Setting
Globals.MainPage = /cmm/main/mainPage.do
# 파일 확장자 화이트리스트(허용목록) : 파일 확장자를 (.)과 함께 연이어서 사용하며 (.)로 시작한다.
Globals.fileUpload.Extensions.Images = .gif.jpg.jpeg.png
Globals.fileUpload.Extensions = .gif.jpg.jpeg.png.xls.xlsx
# File ID 암호화 키
# 주의 : 반드시 기본값 "egovframe"을 다른것으로 변경하여 사용하시기 바랍니다.
Globals.File.algorithmKey = egovframe
설명이 잘 나와있음.
기본값으로 내장 DB인 hsql로 설정되어 있고, 만약 다른 DB정보를 연결하기 원한다면 아래 #으로 주석처리 되어있는 것들에 주석을 풀어서 DB 연결을 해주면 된다.
MainPage Setting : 애플리케이션이 실행될 때 기본으로 열리는 페이지가 /cmm/main/mainPage.do 로 되어있음
Globals.fileUpload.Extensions.Images → 이미지 업로드 허용 확장자: .gif, .jpg, .jpeg, .png
Globals.fileUpload.Extensions → 일반 파일 업로드 허용 확장자: .gif, .jpg, .jpeg, .png, .xls, .xlsx
서버에서 특정 확장자의 파일만 업로드할 수 있도록 화이트리스트(허용 목록) 설정
Globals.File.algorithmKey = egovframe : 파일 ID를 암호화할 때 사용하는 기본 키 값(보안상 변경 필요)
3️⃣ mapper-config.xml (MyBatis 설정 파일)
MyBatis의 매퍼(Mapper) 설정 파일.
SQL 쿼리를 XML 기반으로 정의하고, Java 객체와 매핑하는 역할을 한다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--Mybatis 설정 -->
<settings>
<!-- 전통적인 데이터베이스 컬럼명 형태인 A_COLUMN을 CamelCase형태의 자바 프로퍼티명 형태인 aColumn으로 자동으로 매핑하도록 함 -->
<setting name="mapUnderscoreToCamelCase" value="true"></setting>
<!-- 파라미터에 Null 값이 있을 경우 에러 처리 -->
<setting name="jdbcTypeForNull" value="VARCHAR"></setting>
</settings>
<!-- Type Aliases 설정-->
<typeAliases>
<typeAlias alias="egovMap" type="org.egovframe.rte.psl.dataaccess.util.EgovMap" />
<typeAlias alias="FileVO" type="egovframework.com.cmm.service.FileVO" />
<typeAlias alias="ComDefaultCodeVO" type="egovframework.com.cmm.ComDefaultCodeVO" />
<typeAlias alias="comDefaultVO" type="egovframework.com.cmm.ComDefaultVO" />
</typeAliases>
<mappers>
<!-- 이런식으로 넣는거임 -->
<!-- <mapper resource="egovframework/mapper/sample/Sample_SQL.xml"/> -->
</mappers>
</configuration>
마이바티스 관련해서 두가지 큰 설정이 있는데, Type Alias와 mapper 설정이다.
[Type Aliases(타입 별칭) 설정]
MyBatis에서 객체 타입을 짧게 사용할 수 있도록 별칭(Alias)을 정의함.
예를 들어, <resultType>에서 org.egovframe.rte.psl.dataaccess.util.EgovMap을 쓰는 대신 egovMap으로 사용할 수 있음.
<select id="getFileList" resultType="FileVO">
SELECT * FROM FILE_TABLE;
</select>
🔹 mapper란?
"Java 코드에서 DB 쿼리를 실행할 수 있게 도와주는 도우미"
MyBatis에서 SQL 쿼리를 XML 파일이나 인터페이스에 정의하고, 이를 Java 객체와 연결하여 SQL 실행을 자동화해주는 역할을 하는 것.
터미널로 hsqlbb.jar 있는 경로 들어가서 위 명령어를 실행하면 sampledb.script, sampledb.properties 같은 파일이 생성됨.
이후 jdbc:hsqldb:hsql://localhost/sampledb로 접속하면 정상 동작.
pom.xml에 이렇게 들어있는데, Maven이 자동으로 target/lib 또는 .m2/repository에 저장한다.
<dependency>
<groupId>org.hsqldb</groupId> <!-- HSQLDB 라이브러리 그룹 ID -->
<artifactId>hsqldb</artifactId> <!-- 라이브러리 이름 -->
<version>2.7.2</version> <!-- 사용하는 HSQLDB 버전 (최신 버전) -->
<classifier>jdk8</classifier> <!-- JDK 8 전용 버전 사용 -->
</dependency>
어쨌든 이렇게 하면 db 관련 설정파일이 생성된다.
db.properties (데이터베이스 설정 파일)
db.script (HSQLDB용 SQL 초기화 스크립트)
하지만 난 기존꺼 그냥 붙여넣었고.... 그냥 임베디드 모드(메모리모드)를 사용할 예정이다.
시트는 조회성으로 따로 페이지 이동해서 저장하는 등의 기능을 구현하기엔 번거롭다. 따라서 최대한 기존 게시판과 동일하게 만들기 위해 EditDialog를 사용해서 게시글 추가 페이지처럼 사용한다. 목록 상에서는 제목만 보이지만 실제로는 내용 및 첨부파일까지 가져온다. 클릭 이벤트에서 EditDialog를 띄우고 내용을 추가할 수 있으며, 첨부파일도 등록할 수 있다. Insert, Update 페이지를 각각 만드는 것이 아닌, [저장]버튼 하나로 게시글을 한번에 저장한다. 단, 게시글은 1개씩만 수정될 수 있도록 한다.
추가적으로 날짜형식을 맞춰준다던지, 작성자를 가져온다던지, 게시글 추가, 저장 버튼을 만든다던지 등
/**
* IBSheet8 저장로직
*
* @param boardVO
* @param model
* @return
* @throws Exception
*/
@RequestMapping(value = "/cop/bbs/sheetfileSave.do", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
@ResponseBody
public String filesaveIBSheet(@RequestParam Map<String,String> map,
HttpServletRequest request,
final MultipartHttpServletRequest multiRequest,
@ModelAttribute("searchVO") BoardVO boardVO,
@ModelAttribute("bdMstr") BoardMaster bdMstr, @ModelAttribute("board") Board board,
BindingResult bindingResult, ModelMap model) throws Exception {
LoginVO user = (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser();
Map<String, Object> saveResult = new HashMap<>();
Map<String, Object> IO = new HashMap<>();
try {
// ---------------------- 시트 데이터 추출 -------------------------
String formData = map.get("Data");
String decodedData = HtmlUtils.htmlUnescape(formData); //form 데이터 디코딩
ObjectMapper objectMapper = new ObjectMapper(); // JSON 문자열을 파싱
Map<String, Object> jsonMap = objectMapper.readValue(decodedData, Map.class); // JSON을 Map으로 변환
// 'data' 배열에서 첫 번째 항목을 가져와 Map으로 변환
Map<String, Object> firstItem = ((List<Map<String, Object>>) jsonMap.get("data")).get(0);
// 각 항목을 Map<String, String>으로 변환
Map<String, String> resultMap = new HashMap<>();
for (Map.Entry<String, Object> entry : firstItem.entrySet()) {
resultMap.put(entry.getKey(), entry.getValue().toString());
}
// 시트데이터 결과 출력
System.out.println("저장할 데이터 : " + jsonMap);
// ---------------------- 기본 데이터 세팅 -------------------------
board.setLastUpdusrId(user.getUniqId());
board.setNtcrNm(""); // dummy 오류 수정 (익명이 아닌 경우 validator 처리를 위해 dummy로 지정됨)
board.setPassword(""); // dummy 오류 수정 (익명이 아닌 경우 validator 처리를 위해 dummy로 지정됨)
board.setNttCn(resultMap.get("nttCn"));
board.setNttSj(resultMap.get("nttSj"));
board.setNtcrId((String)map.get("ntcrId"));
board.setNtceBgnde(board.getNtceBgnde());
board.setNtceEndde(board.getNtceEndde());
board.setBbsId(board.getBbsId());
//nttid 이 long 타입이기 때문에 형변환
int nttIdx = Integer.parseInt(resultMap.get("nttIdx"));
if (nttIdx != 0) {
long longNumber = (long) nttIdx;
board.setNttId(longNumber);
} else {
// 기본값 설정 (예: 0)
board.setNttId(0);
}
// ---------------------- 파일 추출 -------------------------
/* 시트에 추가된 파일은 저장시 서버로 전송되며, 파일이름과 저장경로를 응답하면 파일 다운로드 가능
* 파일이 있는 경우, 신규파일인지, 파일수정인지 여부에 따라 파일명 및 파일경로 업데이트
*/
//ibsheet의 file 이름은 [행ID + $ + file컬럼명]
String fileName = resultMap.get("id") + "$lblistFile";
// 파일을 받기 위한 MultipartFile 객체 생성
MultipartFile file = multiRequest.getFile(fileName);
FileVO fvo = new FileVO();
// file ID 유무
String atchFileId_new = ""; //신규 파일이면 ID 생성
String atchFileId = boardVO.getAtchFileId(); //기존파일 ID
String atchFileId_old = resultMap.get("atchFileId"); //기존파일 ID(추가x)
String fileName_old = resultMap.get("streFileNm"); //기존 파일 이름(추가x)
// 현재 프로젝트의 루트 경로 가져오기
String webappPath = servletContext.getRealPath("/files");
System.out.println("파일 저장 경로 설정 webappPath : " + webappPath);
// 파일이 존재할 때
if (file != null && !file.isEmpty()) {
//1. 기존 파일 ID가 없음(신규 저장)
if ("".equals(atchFileId)) {
// 새로운 첨부 파일 ID 생성 및 파일 저장
List<FileVO> result = fileUtil.parseFileInf(
Collections.singletonMap("fileName", file), // 파일을 Map 형태로 전달
"BBS_", // 파일 prefix
0, // 파일 순번 (기본값)
atchFileId, // 첨부 파일 ID
webappPath // 파일 저장 경로
);
if (!result.isEmpty()) {
FileVO fileVO = result.get(0);
//파일 정보 추가
atchFileId_new = fileMngService.insertFileInf(fileVO);
// Board 객체에 새 첨부 파일 ID 설정
board.setAtchFileId(atchFileId_new);
}
//2. 기존 파일이 있는 상태에서 신규파일로 변경
} else {
// 기존 첨부 파일 ID에 파일 추가
fvo.setAtchFileId(atchFileId);
int cnt = fileMngService.getMaxFileSN(fvo);
List<FileVO> _result = fileUtil.parseFileInf(
Collections.singletonMap("fileName", file), // 파일을 Map 형태로 전달
"BBS_", // 파일 prefix
cnt, // 파일 순번
atchFileId, // 첨부 파일 ID
webappPath // 파일 저장 경로
);
//파일 정보 업데이트
fileMngService.updateFileInfs(_result);
}
} else { // 파일이 없음
System.out.println("파일이 없습니다.");
if(atchFileId_old != null && (resultMap.get("isLblistFile")).equals("true") ) { //기존에 파일이 있었으면 파일명 유지
board.setAtchFileId(atchFileId_old); //기존에 있던 파일id 유지
board.setStreFileNm(fileName_old); //기존에 있던 파일이름 유지
}
}
// ---------------------- 상태값에 따라 저장 -------------------------
if("Added".equals(resultMap.get("STATUS"))) {
System.out.println("################# 추가 #################");
board.setNttNo(4L); // 게시물 번호 long
board.setFrstRegisterId(user.getUniqId()); //여기에 알아서 최초등록자아이디
bbsMngService.insertBoardArticle(board);
}else if("Changed".equals(resultMap.get("STATUS"))) {
System.out.println("################# 수정 #################");
bbsMngService.updateBoardArticle(board);
}else {
System.out.println("################# 삭제 #################");
board.setLastUpdusrId(user.getUniqId());
bbsMngService.deleteBoardArticle(board);
}
// ---------------------- 응답 -------------------------
IO.put("Result", "0");
IO.put("Message", "저장되었습니다.");
//파일이 있는 경우 파일 응답 추가
if (file != null && !file.isEmpty()) {
List<Map<String, String>> fileArr = new ArrayList<>();
Map<String, String> fileData1 = new HashMap<>();
String originalFilename = file.getOriginalFilename(); //파일이름
fileData1.put("file", (String) originalFilename);
fileData1.put("filePath", webappPath + '/');
fileData1.put("id", (String) resultMap.get("id"));
fileArr.add(fileData1);
// IO 객체에 파일데이터 추가
IO.put("data", fileArr);
}
saveResult.put("IO", IO);
}catch(Exception ex){
IO.put("Result", "-10");
IO.put("Message", "저장 중 오류가 발생했습니다.");
saveResult.put("IO", IO);
} finally {}
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = objectMapper.writeValueAsString(saveResult);
return jsonString;
}
그럼 이렇게 뜨는데
원래 게시글 목록에는 첨부파일 이름도 확인할 수가 없다.
내용은 그냥 동일 테이블에서 불러오면 되는데 첨부파일은 vo 부터 추가해줘야함.
따라서 공지사항 리스트에서 첨부파일도 받을 수 있도록 Board.java를 수정해줘야함.
2) Board.java 수정
▼ Board.java 에 file name을 받을 수 있도록 변수 및 getter Setter 추가
/**
* 게시물 첨부파일 명
*/
private String streFileNm = "";
...
/**
* streFileNm attribute를 리턴한다.
*
* @return the streFileNm
*/
public String getStreFileNm() {
return streFileNm;
}
/**
* streFileNm attribute 값을 설정한다.
*
* @param streFileNm
* the streFileNm to set
*/
public void setStreFileNm(String streFileNm) {
this.streFileNm = streFileNm;
}
3) EgovBoard_SQL_hsql.xml 수정
여기까지 하면 저장은 잘된다. 문제는 [내용]과 [첨부파일]이 제대로 조회되지 않음.
이유는 데이터를 조회하는 selectBoardArticleList 의 SELECT 문에서 [내용]과 [파일명]을 불러오지 않기 때문.(원래 안불러옴)
따라서 EgovBoard_SQL_hsql.xml 에서 공지사항 목록을 조회하는 부분을 약간 수정해서 [내용]과 [파일명] 이 나올 수 있도록 한다.
▼ EgovBoard_SQL_hsql.xml 에 a.NTT_CN, f.STRE_FILE_NM 추가