반응형
SMALL

empSearch.do로 데이터 조회 끝났으면 이제 파일명 맞춰주고 정리하면 된다.

 

empDAO -> ibsheetDAO

empSearvice -> ibsheetService

empSearviceImpl -> ibsheetServiceImpl

empVO -> ibsheetVO

empMapper.xml -> ibsheetMapper.xml

EmpController.java -> IbsheetTemplateController.java

 

어차피 실행하고 있는 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;
}

 

 

 

5. EmpSearviceImpl -> IbsheetServiceImpl

package egovframework.let.ibsheetTemplate.service.impl;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;

import egovframework.let.ibsheetTemplate.service.IbsheetService;

@Service("ibsheetService")
public class IbsheetServiceImpl implements IbsheetService {

    @Resource(name = "ibsheetDAO")
    private IbsheetDAO ibsheetDAO;

    @Override
    public List<?> selectEmpList(Map<String, String> map) throws Exception {
        return ibsheetDAO.selectEmpList(map);
    }

}

 

DAO 이름, 상속하는 service 이름 등 수정

 

 6. EmpController.java -> IbsheetTemplateController.java

@Service("ibsheetService") 에 설정했던 이름을 @Resource로 받는다.

/empSearch2.do로 호출하면 데이터 조회됨.

    @Resource(name = "ibsheetService")
    private IbsheetService ibsheetService;
	
	@RequestMapping(value = "/empSearch2.do")
	public ModelAndView selectEmp(@RequestParam Map<String,String> map, HttpSession hs) throws Exception {

		System.out.println("조회 버튼 클릭 : " );
		IBSheetUtil ibUtil = new IBSheetUtil();
		//데이터 조회
		List<?> data = ibsheetService.selectEmpList(map);
		

		System.out.println("data : " + data);
		//JSON
		ibUtil.setData(data);
		
		ModelAndView model = new ModelAndView("jsonView",ibUtil.getSearchJSON());

		//JSON 반환 
		return model; 
	}

 

이름 잘 바꿔서 조회 완료

 

Color 값 그대로 들어와서 조회 잘 됨ㅇㅇ

물론 Formula가 아니라서 값 수정 시 색상 변경되는건 따로 js에서 설정해줘야함.

 

 

여기까지 조회 끝났고, 저장까지만 해보자.


저장은 '/empSave.do' 호출하면 되도록 하자.

 

1.IbsheetMapper.xml

[저장] 이라는 것 자체가 추가/수정/삭제를 상태값에 따라 반영하는 것을 한번에 하는 동작이다.

따라서 각각의 동작을 수행할 수 있는 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;
	}

 

근데 어라...왜 안되지.... 콘솔에 오류도 없고 하길래 왜 안되나 봤더니...

 

 

조회데이터랑 다르게 저장데이터는 &quot; 와 같이 HTML 엔티티가 포함되어있다.

아무래도 클라이언트에서 json으로 데이터 받을 때 HTML 인코딩 상태로 넘어온 것 같다.

 

이거 디코딩 해주자.

 

<!-- pom.xml에 추가 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version> <!-- 버전은 프로젝트에 맞게 조정 -->
</dependency>

 

이미 commons-lang3는 pom.xml에 있었고...

		String sheetData = request.getParameter("Data");
		String decoded = StringEscapeUtils.unescapeHtml4(sheetData); //HTML 엔티티 디코딩

 

이런식으로 디코딩된 데이터를 넘겨주면 된다.

 

정상저장되고
IO도 잘 넘어오고
페이지 새로고침해도 잘 반영되어 있음

 

여기까지 저장 끝!

 

CRUD를 완료했다.

반응형
LIST
반응형
SMALL

이것저것 붙이다가 다 때려치우고 그냥 처음부터 간단히 Emp DB 연결한거 정리.

기본의 기본의 기본부터 해본다.


목표는 "/empSearch.do" 로 호출하면 화면에 데이터가 조회되는 것!

HSQLDB를 임베디드 모드(메모리모드)로 실행할 것이고, MyBatis 사용.

 

 

1. 연결할 sql 파일 넣기

resources > db > 에 연결하려는 sql 파일 넣기

shtdb.sql이랑 동일경로에 넣음

 

내용은 아래와 같다. 간단히 EMP 테이블과 DEPT 테이블을 넣자.

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 특수 문자와 충돌하지 않게 하기 위함이다.

egovMap은 어디에 설정되어있냐면,

	<!-- 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>

 

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.util.List;
import java.util.Map;

public interface EmpService {
    List<Map<String, Object>> selectEmpList() throws Exception;
}
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 파일 생성

service 패키지 안 구조

package egovframework.let.ibsheetTemplate.service.impl;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import egovframework.let.ibsheetTemplate.service.EmpService;

@Service("empService")
public class EmpServiceImpl implements EmpService {

    @Resource(name = "empDAO")
    private EmpDAO empDAO;

    @Override
    public List<Map<String, Object>> selectEmpList() throws Exception {
        return empDAO.selectEmpList();
    }

}

 

 

  • @Service("empService")는 Spring이 이 클래스를 서비스 계층으로 인식하게 해준다.
  • @Resource(name = "empDAO")로 DAO를 주입받는다.
  • Service 인터페이스에 정의한 selectEmpList를 구현하고, 내부적으로 DAO의 같은 이름 메서드를 호출한다.

 

 

6. Controller 클래스 생성

Controller 클래스를 만들어서,

empSearch.do라는 URL로 요청이 들어오면 EMP 테이블의 모든 데이터를 조회해서 반환하는 기능을 구현.

java > egovframework > let > ibsheetTemplate > web 경로에 EmpController.java 파일 생성

 

package egovframework.let.ibsheetTemplate.web;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import egovframework.let.ibsheetTemplate.service.EmpService;

@Controller
public class EmpController {

    @Resource(name = "empService")
    private EmpService empService;

    @RequestMapping(value = "/empSearch.do")
    @ResponseBody
    public List<Map<String, Object>> selectEmpList(ModelMap model) throws Exception {
        return empService.selectEmpList();
    }
}

 

 

7. 설정파일 설정

몇가지 추가설정을 해줘야 한다.

 

1) context-datasource.xml

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에 다음 두 가지 설정이 동시에 존재합니다:
  1. ✅ <mvc:annotation-driven/>
  2. ⛔ RequestMappingHandlerAdapter를 수동으로 등록

        <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
            <property name="webBindingInitializer">
                <bean class="egovframework.com.cmm.web.EgovBindingInitializer"/>
            </property>
        </bean>
⚠️ 이 수동 설정 때문에 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를 지정해야한다.

 

아래는 예시!

@RequestMapping("/empList.do")
public ModelAndView selectEmpList(@RequestParam Map<String, String> paramMap) throws Exception {
    List<?> empList = empService.selectEmpList(paramMap);
    ModelAndView mav = new ModelAndView("jsonView");
    mav.addObject("empList", empList);
    return mav;
}

 

  • 컨트롤러에서 ModelAndView("jsonView")를 리턴하면,
  • Spring은 "jsonView"라는 이름의 Bean을 찾고,
  • 그 Bean은 MappingJackson2JsonView니까,
  • 내부적으로 Java 객체를 JSON으로 변환해서 응답함.
  • 따라서 클라이언트(브라우저 등)에는 JSON이 출력.

 

 

4) pom.xml

jsonView를 쓰려면 jackson 라이브러리가 필수임

		 <!-- jsonView 관련 -->      
        <dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-core</artifactId>
		    <version>2.9.8</version>
		</dependency>
        <dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		    <version>2.9.8</version>
		</dependency>
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-annotations</artifactId>
		    <version>2.9.8</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-core-asl -->
		<dependency>
		    <groupId>org.codehaus.jackson</groupId>
		    <artifactId>jackson-core-asl</artifactId>
		    <version>1.9.13</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-mapper-asl -->
		<dependency>
		    <groupId>org.codehaus.jackson</groupId>
		    <artifactId>jackson-mapper-asl</artifactId>
		    <version>1.9.13</version>
		</dependency>

 

 


8. 결과

데이터가 화면에 잘 출력된다.

네트워크 탭에서도 보면 200으로 잘 가져오고, json 형태로 이쁘게 가져옴.

 

 

 

반응형
LIST
반응형
SMALL

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 실행을 자동화해주는 역할을 하는 것.
  • SampleMapper_SQL.xml 파일을 예시로 들면
<mapper namespace="sampleMapper">
    <select id="selectSampleList" resultType="egovMap">
        SELECT * FROM SAMPLE
    </select>
</mapper>

이런식으로 생겼고,  namespace에 있는 Mapper의 고유 이름을 통해 찾아갈 수 있다.

 

 


[<mappers> 태그 설정]

  • MyBatis에서 SQL Mapper XML 파일을 로딩하기 위한 설정.
  • 중앙 설정 파일(mapper-config.xml) 에 설정하는 것은 MyBatis의 전통적인 설정 방식으로, 모든 매퍼를 일괄 등록할 수 있다.
  • mapper-config.xml은 SqlSessionFactoryBeanconfigLocation 속성을 통해 참조된다.
  • 개별 매퍼를 수동으로 지정해야 해서, 경로가 많거나 패턴이 다양한 경우 관리가 번거로울 수 있다.
    <mappers>
		<!-- 이런식으로 넣는거임 -->
        <!-- <mapper resource="egovframework/mapper/sample/Sample_SQL.xml"/>  -->
    </mappers>

 

 

▼ context-datasource.xml 에서 설정하는 방법(권장)

더보기

MyBatis에서 SQL 매핑파일(XML)을 등록하는 방법으로 목적은 동일하지만, 사용되는 위치가 다른 곳이 있다.

 

이방법이 유지보수에 더 적합하다고 한다.

context-mapper.xml

context-datasource.xml은 Spring Framework + MyBatis + eGovFrame 환경에서 데이터베이스 연결과 관련된 설정을 담당하는 핵심 파일이다.

이 파일이 제대로 설정되어 있어야 MyBatis 쿼리를 실행할 수 있고, DB 연결이 성공적으로 이루어짐.

 

여기서 sqlSession(SqlSessionFactoryBean)은 MyBatis가 SQL을 실행하기 위한 핵심 객체를 만들어줌.

	<!-- 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>
                <!-- 아래와 같이 xml 파일 경로 추가 -->
				<value>classpath:/egovframework/mapper/let/ibsheetTemplate/*.xml</value>
			</list>
		</property>
	</bean>

 

이것도 이런식으로 넣어주면 된다.

mapperLocations를 찾아서 아래 list에 실행하기 원하는 sql 정보가 담긴 xml 파일을 등록해줄 수 있음.

특히, 와일드카드(*)를 사용할 수 있어, 폴더 내부의 여러개의 매퍼 파일을 한번에 등록할 수 있다.

 

 

 

mapper-config.xml 에서 Mapper를 설정하나, context-datasource.xml 에서 설정하나,

정상적으로 설정되면 <mapper namespace="..."> 형태로 된 매퍼 파일들이 MyBatis에 등록된다.

항목 <mappers> (mapper-config.xml 내) mapperLocations (SqlSessionFactoryBean 내)
사용 위치 mapper-config.xml 내부 context-datasource.xml 등에서 SqlSessionFactoryBean 내
와일드카드 지원 ❌ 불가능 ✅ 가능 (*, **)
등록 방식 매퍼 파일 하나하나 명시 전체 폴더 또는 패턴 지정 가능
유지보수성 낮음 (경로 추가 시 직접 수정 필요) 높음 (패턴으로 자동 매핑 가능)
eGovFrame 권장 방식 보통 병행하거나, mapperLocations 우선 사용

 

 

 

기존 시스템의 ibatis 구조

기존 예제는 ibatis를 썼기 때문에 egovframework> sqlmap > config > hsql(각 DB별 폴더) 와 같은 구조로 되어있을꺼다.

하지만 이번 샘플은 mybatis로 사용할 것이기 때문에 구조가 다를 수 있음.

현재 mybatis는 egovframework> mapper > config > mapper-config.xml 에서 전체 관리한다.

 

 

 

 

 

 

4️⃣ let > ... > xxx_SQL_hsql.xml (HSQLDB용 SQL 매퍼 파일)

eGovFrame에서 HSQLDB 전용 SQL 쿼리를 담은 MyBatis 매퍼 파일.

폴더 구조에 따라서 분류된 각각의 역할에 맞는 SQL문이 들어있다.

화면에 보이는 EgovFile_SQL_hsql.xml 같은 파일은 File 기능과 관련된 SQL문을 모아놨을거다.

다른 EgovFile_SQL_*.xml 파일들은 각각 다른 DB (MySQL, Oracle 등)에 맞춰 작성된 것.

<?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="FileManageDAO">


	<resultMap id="fileList" type="egovframework.com.cmm.service.FileVO">
		<result property="atchFileId" column="ATCH_FILE_ID"/>
		<result property="fileCn" column="FILE_CN"/>
		<result property="fileExtsn" column="FILE_EXTSN"/>
		<result property="fileMg" column="FILE_SIZE"/>
		<result property="fileSn" column="FILE_SN"/>
		<result property="fileStreCours" column="FILE_STRE_COURS"/>
		<result property="orignlFileNm" column="ORIGNL_FILE_NM"/>
		<result property="streFileNm" column="STRE_FILE_NM"/>
		<result property="creatDt" column="CREAT_DT"/>			
	</resultMap>
    

 	<select id="FileManageDAO.selectFileList" parameterType="FileVO" resultMap="fileList">
 		
			SELECT 
				a.ATCH_FILE_ID, b.FILE_CN, b.FILE_SN, b.FILE_STRE_COURS, b.STRE_FILE_NM,
				b.FILE_EXTSN, b.ORIGNL_FILE_NM, b.FILE_SIZE, a.CREAT_DT
			FROM
				LETTNFILE a, LETTNFILEDETAIL b
			WHERE
				a.ATCH_FILE_ID = #{atchFileId}
			AND 
				a.ATCH_FILE_ID = b.ATCH_FILE_ID
			AND 
				a.USE_AT = 'Y'				
			ORDER BY b.FILE_SN	
 		
 	</select>
    
    ...

 

그러니까 내가 뭔가를 DB의 내용을 수정하는 SQL 문을 추가한다면 여기에 폴더를 추가해서 분류해놔야한다.

 

 

 

 

 


2. SQL 파일 붙여넣고 연결하기

resources > db > shtdb.sql 부터 확인해보자.

 

Simple Homepage 실행 시 기본적으로 생성되는 DB SQL인 shtdb.sql의 내용은 아래와 같다.

CREATE MEMORY TABLE LETTCCMMNCLCODE(CL_CODE CHAR(3) NOT NULL PRIMARY KEY,CL_CODE_NM VARCHAR(60),CL_CODE_DC VARCHAR(200),USE_AT CHAR(1),FRST_REGIST_PNTTM TIMESTAMP,FRST_REGISTER_ID VARCHAR(20),LAST_UPDT_PNTTM TIMESTAMP,LAST_UPDUSR_ID VARCHAR(20))
CREATE MEMORY TABLE LETTCCMMNCODE(CODE_ID VARCHAR(6) NOT NULL PRIMARY KEY,CODE_ID_NM VARCHAR(60),CODE_ID_DC VARCHAR(200),USE_AT CHAR(1),CL_CODE CHAR(3),FRST_REGIST_PNTTM TIMESTAMP,FRST_REGISTER_ID VARCHAR(20),LAST_UPDT_PNTTM TIMESTAMP,LAST_UPDUSR_ID VARCHAR(20),CONSTRAINT SYS_FK_86 FOREIGN KEY(CL_CODE) REFERENCES LETTCCMMNCLCODE(CL_CODE))
CREATE MEMORY TABLE LETTCCMMNDETAILCODE(CODE_ID VARCHAR(6) NOT NULL,CODE VARCHAR(15) NOT NULL,CODE_NM VARCHAR(60),CODE_DC VARCHAR(200),USE_AT CHAR(1),FRST_REGIST_PNTTM TIMESTAMP,FRST_REGISTER_ID VARCHAR(20),LAST_UPDT_PNTTM TIMESTAMP,LAST_UPDUSR_ID VARCHAR(20),PRIMARY KEY(CODE_ID,CODE),CONSTRAINT SYS_FK_89 FOREIGN KEY(CODE_ID) REFERENCES LETTCCMMNCODE(CODE_ID))
CREATE MEMORY TABLE LETTHEMPLYRINFOCHANGEDTLS(EMPLYR_ID VARCHAR(20) NOT NULL,CHANGE_DE CHAR(20) NOT NULL,ORGNZT_ID CHAR(20),GROUP_ID CHAR(20),EMPL_NO VARCHAR(20) NOT NULL,SEXDSTN_CODE CHAR(1),BRTHDY CHAR(20),FXNUM VARCHAR(20),HOUSE_ADRES VARCHAR(100) NOT NULL,HOUSE_END_TELNO VARCHAR(4),AREA_NO VARCHAR(4),DETAIL_ADRES VARCHAR(100) NOT NULL,ZIP VARCHAR(6) NOT NULL,OFFM_TELNO VARCHAR(20),MBTLNUM VARCHAR(20) NOT NULL,EMAIL_ADRES VARCHAR(50),HOUSE_MIDDLE_TELNO VARCHAR(4),PSTINST_CODE CHAR(8),EMPLYR_STTUS_CODE VARCHAR(15) NOT NULL,ESNTL_ID CHAR(20),PRIMARY KEY(EMPLYR_ID,CHANGE_DE))
CREATE MEMORY TABLE LETTNAUTHORGROUPINFO(GROUP_ID CHAR(20) NOT NULL PRIMARY KEY,GROUP_NM VARCHAR(60) NOT NULL,GROUP_CREAT_DE CHAR(20) NOT NULL,GROUP_DC VARCHAR(100))
CREATE MEMORY TABLE LETTNAUTHORINFO(AUTHOR_CODE VARCHAR(30) NOT NULL PRIMARY KEY,AUTHOR_NM VARCHAR(60) NOT NULL,AUTHOR_DC VARCHAR(200),AUTHOR_CREAT_DE CHAR(20) NOT NULL)
CREATE MEMORY TABLE LETTNBBS(NTT_ID NUMERIC(20) NOT NULL,BBS_ID CHAR(20) NOT NULL,NTT_NO NUMERIC(20),NTT_SJ VARCHAR(2000),NTT_CN LONGVARCHAR,ANSWER_AT CHAR(1),PARNTSCTT_NO NUMERIC(10),ANSWER_LC INTEGER,SORT_ORDR NUMERIC(8),RDCNT NUMERIC(10),USE_AT CHAR(1) NOT NULL,NTCE_BGNDE CHAR(20),NTCE_ENDDE CHAR(20),NTCR_ID VARCHAR(20),NTCR_NM VARCHAR(20),PASSWORD VARCHAR(200),ATCH_FILE_ID CHAR(20),FRST_REGIST_PNTTM TIMESTAMP NOT NULL,FRST_REGISTER_ID VARCHAR(20) NOT NULL,LAST_UPDT_PNTTM TIMESTAMP,LAST_UPDUSR_ID VARCHAR(20),PRIMARY KEY(NTT_ID,BBS_ID))
CREATE MEMORY TABLE LETTNBBSMASTER(BBS_ID CHAR(20) NOT NULL PRIMARY KEY,BBS_NM VARCHAR(255) NOT NULL,BBS_INTRCN VARCHAR(2400),BBS_TY_CODE CHAR(6) NOT NULL,BBS_ATTRB_CODE CHAR(6) NOT NULL,REPLY_POSBL_AT CHAR(1),FILE_ATCH_POSBL_AT CHAR(1) NOT NULL,ATCH_POSBL_FILE_NUMBER NUMERIC(2) NOT NULL,ATCH_POSBL_FILE_SIZE NUMERIC(8),USE_AT CHAR(1) NOT NULL,TMPLAT_ID CHAR(20),FRST_REGISTER_ID VARCHAR(20) NOT NULL,FRST_REGIST_PNTTM TIMESTAMP NOT NULL,LAST_UPDUSR_ID VARCHAR(20),LAST_UPDT_PNTTM TIMESTAMP)
CREATE MEMORY TABLE LETTNBBSMASTEROPTN(BBS_ID CHAR(20) DEFAULT '' NOT NULL PRIMARY KEY,ANSWER_AT CHAR(1) DEFAULT '' NOT NULL,STSFDG_AT CHAR(1) DEFAULT '' NOT NULL,FRST_REGIST_PNTTM TIMESTAMP DEFAULT '1970-01-01 00:00:00.0' NOT NULL,LAST_UPDT_PNTTM TIMESTAMP,FRST_REGISTER_ID VARCHAR(20) DEFAULT '' NOT NULL,LAST_UPDUSR_ID VARCHAR(20))
CREATE MEMORY TABLE LETTNBBSUSE(BBS_ID CHAR(20) NOT NULL,TRGET_ID CHAR(20) NOT NULL,USE_AT CHAR(1) NOT NULL,REGIST_SE_CODE CHAR(6),FRST_REGIST_PNTTM TIMESTAMP,FRST_REGISTER_ID VARCHAR(20) NOT NULL,LAST_UPDT_PNTTM TIMESTAMP,LAST_UPDUSR_ID VARCHAR(20),PRIMARY KEY(BBS_ID,TRGET_ID),CONSTRAINT SYS_FK_98 FOREIGN KEY(BBS_ID) REFERENCES LETTNBBSMASTER(BBS_ID))
CREATE MEMORY TABLE LETTNEMPLYRINFO(EMPLYR_ID VARCHAR(20) NOT NULL PRIMARY KEY,ORGNZT_ID CHAR(20),USER_NM VARCHAR(60) NOT NULL,PASSWORD VARCHAR(200) NOT NULL,EMPL_NO VARCHAR(20),IHIDNUM VARCHAR(13),SEXDSTN_CODE CHAR(1),BRTHDY CHAR(20),FXNUM VARCHAR(20),HOUSE_ADRES VARCHAR(100) NOT NULL,PASSWORD_HINT VARCHAR(100) NOT NULL,PASSWORD_CNSR VARCHAR(100) NOT NULL,HOUSE_END_TELNO VARCHAR(4) NOT NULL,AREA_NO VARCHAR(4) NOT NULL,DETAIL_ADRES VARCHAR(100),ZIP VARCHAR(6) NOT NULL,OFFM_TELNO VARCHAR(20),MBTLNUM VARCHAR(20) NOT NULL,EMAIL_ADRES VARCHAR(50),OFCPS_NM VARCHAR(60),HOUSE_MIDDLE_TELNO VARCHAR(4) NOT NULL,GROUP_ID CHAR(20),PSTINST_CODE CHAR(8),EMPLYR_STTUS_CODE VARCHAR(15) NOT NULL,ESNTL_ID CHAR(20) NOT NULL,CRTFC_DN_VALUE VARCHAR(20),SBSCRB_DE TIMESTAMP,CONSTRAINT SYS_FK_101 FOREIGN KEY(GROUP_ID) REFERENCES LETTNAUTHORGROUPINFO(GROUP_ID) ON DELETE CASCADE)
CREATE MEMORY TABLE LETTNEMPLYRSCRTYESTBS(SCRTY_DTRMN_TRGET_ID VARCHAR(20) NOT NULL PRIMARY KEY,MBER_TY_CODE VARCHAR(15),AUTHOR_CODE VARCHAR(30) NOT NULL,CONSTRAINT SYS_FK_104 FOREIGN KEY(SCRTY_DTRMN_TRGET_ID) REFERENCES LETTNEMPLYRINFO(EMPLYR_ID),CONSTRAINT SYS_FK_113 FOREIGN KEY(AUTHOR_CODE) REFERENCES LETTNAUTHORINFO(AUTHOR_CODE))
CREATE MEMORY TABLE LETTNENTRPRSMBER(ENTRPRS_MBER_ID VARCHAR(20) NOT NULL PRIMARY KEY,ENTRPRS_SE_CODE CHAR(15),BIZRNO VARCHAR(10),JURIRNO VARCHAR(13),CMPNY_NM VARCHAR(60) NOT NULL,CXFC VARCHAR(50),ZIP VARCHAR(6) NOT NULL,ADRES VARCHAR(100) NOT NULL,ENTRPRS_MIDDLE_TELNO VARCHAR(4) NOT NULL,FXNUM VARCHAR(20),INDUTY_CODE CHAR(15),APPLCNT_NM VARCHAR(50) NOT NULL,APPLCNT_IHIDNUM VARCHAR(13),SBSCRB_DE TIMESTAMP,ENTRPRS_MBER_STTUS VARCHAR(15),ENTRPRS_MBER_PASSWORD VARCHAR(200),ENTRPRS_MBER_PASSWORD_HINT VARCHAR(100) NOT NULL,ENTRPRS_MBER_PASSWORD_CNSR VARCHAR(100) NOT NULL,GROUP_ID CHAR(20),DETAIL_ADRES VARCHAR(100),ENTRPRS_END_TELNO VARCHAR(4) NOT NULL,AREA_NO VARCHAR(4) NOT NULL,APPLCNT_EMAIL_ADRES VARCHAR(50) NOT NULL,ESNTL_ID CHAR(20) NOT NULL,CONSTRAINT SYS_FK_116 FOREIGN KEY(GROUP_ID) REFERENCES LETTNAUTHORGROUPINFO(GROUP_ID) ON DELETE CASCADE)
CREATE MEMORY TABLE LETTNFILE(ATCH_FILE_ID CHAR(20) NOT NULL PRIMARY KEY,CREAT_DT TIMESTAMP NOT NULL,USE_AT CHAR(1))
CREATE MEMORY TABLE LETTNFILEDETAIL(ATCH_FILE_ID CHAR(20) NOT NULL,FILE_SN NUMERIC(10) NOT NULL,FILE_STRE_COURS VARCHAR(2000) NOT NULL,STRE_FILE_NM VARCHAR(255) NOT NULL,ORIGNL_FILE_NM VARCHAR(255),FILE_EXTSN VARCHAR(20) NOT NULL,FILE_CN LONGVARCHAR,FILE_SIZE NUMERIC(8),PRIMARY KEY(ATCH_FILE_ID,FILE_SN),CONSTRAINT SYS_FK_119 FOREIGN KEY(ATCH_FILE_ID) REFERENCES LETTNFILE(ATCH_FILE_ID))
CREATE MEMORY TABLE LETTNGNRLMBER(MBER_ID VARCHAR(20) NOT NULL PRIMARY KEY,PASSWORD VARCHAR(200) NOT NULL,PASSWORD_HINT VARCHAR(100),PASSWORD_CNSR VARCHAR(100),IHIDNUM VARCHAR(13),MBER_NM VARCHAR(50) NOT NULL,ZIP VARCHAR(6) NOT NULL,ADRES VARCHAR(100) NOT NULL,AREA_NO VARCHAR(4) NOT NULL,MBER_STTUS VARCHAR(15),DETAIL_ADRES VARCHAR(100),END_TELNO VARCHAR(4) NOT NULL,MBTLNUM VARCHAR(20) NOT NULL,GROUP_ID CHAR(20),MBER_FXNUM VARCHAR(20),MBER_EMAIL_ADRES VARCHAR(50),MIDDLE_TELNO VARCHAR(4) NOT NULL,SBSCRB_DE TIMESTAMP,SEXDSTN_CODE CHAR(1),ESNTL_ID CHAR(20) NOT NULL,CONSTRAINT SYS_FK_122 FOREIGN KEY(GROUP_ID) REFERENCES LETTNAUTHORGROUPINFO(GROUP_ID) ON DELETE CASCADE)
CREATE MEMORY TABLE LETTNORGNZTINFO(ORGNZT_ID CHAR(20) NOT NULL PRIMARY KEY,ORGNZT_NM VARCHAR(20) NOT NULL,ORGNZT_DC VARCHAR(100))
CREATE MEMORY TABLE LETTNSCHDULINFO(SCHDUL_ID CHAR(20) NOT NULL PRIMARY KEY,SCHDUL_SE CHAR(1),SCHDUL_DEPT_ID VARCHAR(20),SCHDUL_KND_CODE VARCHAR(20),SCHDUL_BEGINDE TIMESTAMP,SCHDUL_ENDDE TIMESTAMP,SCHDUL_NM VARCHAR(255),SCHDUL_CN VARCHAR(2500),SCHDUL_PLACE VARCHAR(255),SCHDUL_IPCR_CODE CHAR(1),SCHDUL_CHARGER_ID VARCHAR(20),ATCH_FILE_ID CHAR(20),FRST_REGIST_PNTTM TIMESTAMP,FRST_REGISTER_ID VARCHAR(20),LAST_UPDT_PNTTM TIMESTAMP,LAST_UPDUSR_ID VARCHAR(20),REPTIT_SE_CODE CHAR(3))
CREATE MEMORY TABLE LETTNTMPLATINFO(TMPLAT_ID CHAR(20) NOT NULL PRIMARY KEY,TMPLAT_NM VARCHAR(255),TMPLAT_COURS VARCHAR(2000),USE_AT CHAR(1),TMPLAT_SE_CODE CHAR(6),FRST_REGISTER_ID VARCHAR(20),FRST_REGIST_PNTTM TIMESTAMP,LAST_UPDUSR_ID VARCHAR(20),LAST_UPDT_PNTTM TIMESTAMP)
CREATE MEMORY TABLE IDS(TABLE_NAME VARCHAR(20) DEFAULT '' NOT NULL PRIMARY KEY,NEXT_ID NUMERIC(30) DEFAULT 0 NOT NULL)
ALTER TABLE LETTHEMPLYRINFOCHANGEDTLS ADD CONSTRAINT SYS_FK_92 FOREIGN KEY(EMPLYR_ID) REFERENCES LETTNEMPLYRINFO(EMPLYR_ID)
ALTER TABLE LETTNBBS ADD CONSTRAINT SYS_FK_95 FOREIGN KEY(BBS_ID) REFERENCES LETTNBBSMASTER(BBS_ID)
ALTER TABLE LETTNEMPLYRSCRTYESTBS ADD CONSTRAINT SYS_FK_107 FOREIGN KEY(SCRTY_DTRMN_TRGET_ID) REFERENCES LETTNENTRPRSMBER(ENTRPRS_MBER_ID)
ALTER TABLE LETTNEMPLYRSCRTYESTBS ADD CONSTRAINT SYS_FK_110 FOREIGN KEY(SCRTY_DTRMN_TRGET_ID) REFERENCES LETTNGNRLMBER(MBER_ID)
SET WRITE_DELAY 20
SET SCHEMA PUBLIC
INSERT INTO LETTCCMMNCLCODE VALUES('LET','전자정부 프레임워크 경량환경 템플릿','전자정부 프레임워크 경량환경 템플릿','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNCODE VALUES('COM001','등록구분','게시판, 커뮤니티, 동호회 등록구분코드','Y','LET','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNCODE VALUES('COM003','업무구분','업무구분코드','Y','LET','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNCODE VALUES('COM004','게시판유형','게시판유형구분코드','Y','LET','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNCODE VALUES('COM005','템플릿유형','템플릿유형구분코드','Y','LET','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNCODE VALUES('COM009','게시판속성','게시판 속성','Y','LET','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNCODE VALUES('COM019','일정중요도','일정중요도 낮음/보통/높음 상태구분','Y','LET','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNCODE VALUES('COM030','일정구분','일정구분 코드','Y','LET','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNCODE VALUES('COM031','반복구분','일정 반복구분 코드','Y','LET','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM001','REGC01','단일 게시판 이용등록','단일 게시판 이용등록','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM001','REGC07','게시판사용자등록','게시판사용자등록','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM003','BBS','게시판','게시판','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM004','BBST01','일반게시판','일반게시판','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM004','BBST02','익명게시판','익명게시판','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM004','BBST03','공지게시판','공지게시판','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM004','BBST04','방명록','방명록','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM005','TMPT01','게시판템플릿','게시판템플릿','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM009','BBSA01','유효게시판','유효게시판','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM009','BBSA02','갤러리','갤러리','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM009','BBSA03','일반게시판','일반게시판','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM019','A','높음','높음','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM019','B','보통','보통','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM019','C','낮음','낮음','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM030','1','회의','회의','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM030','2','세미나','세미나','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM030','3','강의','강의','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM030','4','교육','교육','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM030','5','기타','기타','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM031','1','당일','당일','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM031','2','반복','반복','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM031','3','연속','연속','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTCCMMNDETAILCODE VALUES('COM031','4','요일반복','요일반복','Y','2011-08-31 00:00:00.000000000','SYSTEM','2011-08-31 00:00:00.000000000','SYSTEM')
INSERT INTO LETTNORGNZTINFO VALUES ('ORGNZT_0000000000000','관리자부서','관리자부서')
INSERT INTO LETTNAUTHORGROUPINFO VALUES('GROUP_00000000000000','기본 그룹입니다','2011-08-31','기본 그룹')
INSERT INTO LETTNBBSMASTER VALUES('BBSMSTR_AAAAAAAAAAAA','공지사항','공지사항게시판','BBST03','BBSA03','Y','Y',2,5242880,'Y','TMPLAT_BOARD_DEFAULT','USRCNFRM_00000000000','2011-08-31 12:00:00.000000000','USRCNFRM_00000000000','2011-08-31 12:00:00.000000000')
INSERT INTO LETTNBBSMASTER VALUES('BBSMSTR_BBBBBBBBBBBB','갤러리','갤러리게시판','BBST01','BBSA02','Y','Y',2,5242880,'Y','TMPLAT_BOARD_DEFAULT','USRCNFRM_00000000000','2011-08-31 12:00:00.000000000','USRCNFRM_00000000000','2011-08-31 12:00:00.000000000')
INSERT INTO LETTNBBSUSE VALUES('BBSMSTR_AAAAAAAAAAAA','SYSTEM_DEFAULT_BOARD','Y','REGC01','2011-08-31 12:00:00.000000000','USRCNFRM_00000000000','2011-08-31 12:00:00.000000000','USRCNFRM_00000000000')
INSERT INTO LETTNBBSUSE VALUES('BBSMSTR_BBBBBBBBBBBB','SYSTEM_DEFAULT_BOARD','Y','REGC01','2011-08-31 12:00:00.000000000','USRCNFRM_00000000000','2011-08-31 12:00:00.000000000','USRCNFRM_00000000000')
INSERT INTO LETTNEMPLYRINFO VALUES('admin','ORGNZT_0000000000000','관리자','JfQ7FIatlaE5jj7rPYO8QBABX8yb7bNbQy4AKY1QIfc=','','','F','','','관리자 주소','','','','','','','','','','','','GROUP_00000000000000','00000000','P','USRCNFRM_00000000000','','2011-08-31 00:00:00.000000000')
INSERT INTO LETTNTMPLATINFO VALUES('TMPLAT_BOARD_DEFAULT','게시판 기본템플릿','/css/egovframework/cop/bbs/egovbbsTemplate.css','Y','TMPT01','SYSTEM','2011-08-31 00:00:00.000000000',NULL,NULL)
INSERT INTO IDS VALUES('BBS_ID',1)
INSERT INTO IDS VALUES('FILE_ID',1)
INSERT INTO IDS VALUES('SAMPLE',1)
INSERT INTO IDS VALUES('SCHDUL_ID',1)
INSERT INTO IDS VALUES('TMPLAT_ID',1)

 

전체 DB 초기화용 HSQLDB용 DDL과 일부 데이터 삽입 구문이 포함되어 있고, 테이블 구성은 아래와 같다.

 

✅ 주요 테이블 구성

공통 코드

  • LETTCCMMNCLCODE: 공통분류코드
  • LETTCCMMNCODE: 공통코드
  • LETTCCMMNDETAILCODE: 공통상세코드

게시판 관련

  • LETTNBBSMASTER: 게시판 마스터 (게시판 설정)
  • LETTNBBSMASTEROPTN: 게시판 옵션
  • LETTNBBSUSE: 게시판 사용정보 (조직별)
  • LETTNBBS: 게시글 테이블

사용자 관련

  • LETTNEMPLYRINFO: 일반 사용자(직원) 정보
  • LETTNEMPLYRSCRTYESTBS: 사용자-권한 매핑 테이블 (보안 설정)
  • LETTNAUTHORGROUPINFO: 권한 그룹 정보
  • LETTNAUTHORINFO: 권한 정보
  • LETTHEMPLYRINFOCHANGEDTLS: 사용자 변경 이력

회원 유형별 테이블

  • LETTNENTRPRSMBER: 기업 회원
  • LETTNGNRLMBER: 일반 회원

기타 테이블

  • LETTNFILE, LETTNFILEDETAIL: 파일 첨부 관련
  • LETTNSCHDULINFO: 일정 정보
  • LETTNTMPLATINFO: 템플릿 정보
  • LETTNORGNZTINFO: 조직 정보
  • IDS: ID 생성기 (NEXT_ID 저장용)

 

✅ 주요 제약 조건 및 외래키

  • 대부분의 테이블은 GROUP_ID, AUTHOR_CODE, BBS_ID 등을 통해 서로 연결됨
  • ON DELETE CASCADE가 있는 경우 하위 레코드 자동 삭제됨 (예: GROUP_ID FK)
  • 일부 테이블은 여러 테이블에서 FOREIGN KEY로 참조됨 (예: SCRTY_DTRMN_TRGET_ID는 다중 참조)

 

✅ 테이블 관계도 요약 (일부)

[LETTNBBS] ──▶ [LETTNBBSMASTER]
                │
                └─▶ [LETTNBBSMASTEROPTN]
                └─▶ [LETTNBBSUSE]

[LETTNEMPLYRINFO] ──▶ [LETTNAUTHORGROUPINFO]
     │                     ▲
     └─▶ [LETTNEMPLYRSCRTYESTBS] ──▶ [LETTNAUTHORINFO]
                                 └─▶ [LETTNENTRPRSMBER]
                                 └─▶ [LETTNGNRLMBER]

[LETTCCMMNCODE] ──▶ [LETTCCMMNCLCODE]
[LETTCCMMNDETAILCODE] ──▶ [LETTCCMMNCODE]

 

 

shtdb.sql은 냅두고 동일한 경로에 샘플 DB를 넣어준다.

 

.sql.mv.db 로 끝나는 파일도 같이 있는데, 로컬에서 작성한 샘플 데이터, 설정 정보 등을 그대로 다른 환경에서도 사용하고 싶을 때 같이 옮겨줘야 한다.

 

  • .mv.db    ->   "Multi-Version DB" 형식의 물리적 데이터 파일
  • .sql.trace.db   -> 트랜잭션 로그 파일로, 비정상 종료(예: 전원 꺼짐, 강제 종료 등) 시 복구를 도와줌.

요렇게 경로에 전부 옮겨준다.

 

이외에 또 다른 점을 보자면 mappers  아래에 template의 SQL 파일이 따로 있는데, 하위 폴더 만들어서 넣어준다.

폴더 만들어서 넣어줌

 

여기엔 아래와 같이 CRUD에 필요한 구체적인 SQL문이 들어있음.

	<select id="selectDept" resultType="egovMap" parameterType="java.util.HashMap">
		<![CDATA[
			/** selectDept */
			SELECT * FROM DEPT WHERE 1=1
		]]>
        
        <if test="loc != null ">
			AND LOC LIKE '%'||#{loc}||'%'
		</if>
		<if test="deptno != null ">
			AND DEPTNO = #{deptno}
		</if>
	</select>

 

이외에 기존 시스템과 다른 몇가지 파일들이 있는데,

기타 설정파일들

 

global.properties 보면 JDBC URL에 아래와 같이 써있다.

Globals.Url=jdbc:log4jdbc:hsqldb:hsql://127.0.0.1/sampledb

HSQLDB의 JDBC URL 패턴에 따라 모드가 다르다.

 

보면 지금 기본적으로 서버모드로 되어있음.

모드 JDBC URL 형식 설명
메모리 모드 jdbc:hsqldb:mem:mydb 메모리에서 실행됨. 프로그램 종료 시 데이터 사라짐.
파일 모드 jdbc:hsqldb:file:/path/to/db 파일 기반 DB. 종료 후에도 데이터 유지됨.
서버 모드 jdbc:hsqldb:hsql://host/dbname 네트워크 서버 방식. 여러 클라이언트가 접속 가능.

 

📌 서버 모드(Server Mode)란?

  • HSQLDB를 서버 프로세스로 실행하고, 클라이언트가 네트워크를 통해 접속하는 방식.
  • 데이터가 파일에 저장되므로 종료 후에도 유지됨.
  • 동시에 여러 애플리케이션이 같은 DB에 접근 가능함.
  • 보통 독립적인 HSQLDB 서버를 실행한 후 클라이언트가 접속하는 방식.

 

📌 서버 모드에서 실행 방법

  1. HSQLDB 서버를 실행해야 함.
java -cp hsqldb.jar org.hsqldb.server.Server --database.0 file:sampledb --dbname.0 sampledb

 

  1. 터미널로 hsqlbb.jar 있는 경로 들어가서 위 명령어를 실행하면 sampledb.script, sampledb.properties 같은 파일이 생성됨.
  2. 이후 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 초기화 스크립트)

하지만 난 기존꺼 그냥 붙여넣었고.... 그냥 임베디드 모드(메모리모드)를 사용할 예정이다.

 

여기까지 resource 부분은 다 봤고 이 다음에는 java 파일을 설정해보자.

반응형
LIST
반응형
SMALL

egov 4.2 버전

JDK 1.8

Tomcat 8.0

HSQL 사용


1. 프로젝트 생성

eGovFrame Template Project 생성

 

group_id는 동일하게 넣고

 

프로젝트가 만들어지면 Server를 연결합니다.

 

 

/sht_webapp 으로 실행됨.

 

그럼 이렇게 홈페이지 생성 완료.

 


2. 수정할 부분 확인

여기서 공지사항과 갤러리 부분만 시트로 변경해줄 것이다.

 

기본화면이고

 

등록버튼을 누르면

 

이렇게 등록화면으로 가서 게시글 저장 후

 

다시 조회화면으로 돌아온다.

 

 

기존 로직은 조회페이지 / 상세페이지 / 등록페이지 / 수정페이지 등등 나뉘어져 있으나,

우리는 단순 조회성 table만 제공하므로 페이지 이동은 빼고, 그냥 조회페이지에서 CRUD 전체를 진행하게 한다.

 

+++ 추가로 조회수는 사용하지 않음. 상세페이지를 들어가는게 아니라 count 불가함.

 


3. 제품 넣기

1) 제품 파일 넣기

프로젝트 구조는 아래처럼 되어있다.

 

ibsheet는 javascript 기반의 제품으로 js 부분에 제품을 넣어 사용하는 페이지에 import 해서 사용하면 된다.

js 폴더가 있으니 알기 쉽게 이쪽에 넣어주자.

붙여넣기

이동되면 이런경로로 되어있음.

 

 

2) 공지사항 페이지 확인

 

공지사항 페이지로 들어가서 주소를 보면 

 

http://localhost:8080/sht_webapp/cop/bbs/selectBoardList.do?bbsId=BBSMSTR_AAAAAAAAAAAA&menuNo=43

 

이런식으로 되어있는데, 

 

따라가보니 EgovBBSManageController.java 발견.

 

return 쪽을 보니

 

 

cop/bbs/EgovNoticeList 로 이동시킴을 알 수 있음.

이게 페이지 jsp 이다.

 

화면 여기있구요.

 

내용을 보니 알림마당 안에 있는 [공지사항], [사이트갤러리]가 이쪽을 바라보는 듯 하다.

 

일단 이 페이지에 시트를 띄워본다.

 

div 추가

<script type="text/javascript" src="<c:url value='/js/EgovBBSMng.js' />" ></script>


<%-- 시트 관련 js 추가 --%>
<link type="text/css" rel="stylesheet" href="<c:url value='/js/ibsheet8/css/default/main.css'/>"/>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibsheet.js'/>"></script> <%-- 코어 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/locale/ko.js'/>"></script> <%-- 메세지 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-common.js'/>"></script> <%-- 시트 공통 설정 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-dialog.js'/>"></script> <%-- 시트에서 띄우는 다이얼로그 관련 함수 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-excel.js'/>"></script> <%-- 서버롤 호출하여 다운로드/업로드시 필요 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibleaders.js'/>"></script> <%-- 라이선스  파일 --%>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>


...


    <!-- //검색 필드 박스 끝 -->
    <!-- 
    <div id="page_info"><div id="page_info_align"></div></div>                    
     -->

     <!-- ibsheet 표시 -->
    <div id="search_field">
        <div id="sheetDiv" style="width: 100%; height: 500px;"></div>
    </div>               



    <!-- table add start -->

 

 

js 부분은 아래와 같이 작성

더보기

 

<body onload="initSheet()"> 로 해서 페이지 로딩과 동시에 생성되도록 설정

<script type="text/javascript">

function initSheet(){
	var options = {
		Cfg: {
			SearchMode:2
		},
		
		Def: {
			Row: {
			}
		},
		LeftCols:[
			{Header: "번호", 		Type: "Int", 	Name: "SEQ", 		Width: 50,	 CanEdit:0,Visible:0},
		],
		Cols: [
			{Header: "제목",		Type: "Text",	Name: "nttSj",		Width: 200,		Align: "Left",RelWidth:1,},
			{Header: "작성자",		Type: "Text",	Name: "frstRegisterNm",	Width: 80,	Align: "Center", 	Visible:1, CanEdit:0},
			{Header: "작성일",		Type: "Date",	Name: "frstRegisterPnttm",	Width: 100,		Align: "Center", 
					Format:"yyyy-MM-dd",DataFormat:"yyyy-MM-dd",EditFormat:"yyyyMMdd", CanEdit:0
			},
			{Header: "조회수",		Type: "Int", 		Name: "inqireCo",	Width: 100,	Align: "Center",	Visible:1, CanEdit:0},		
		],
		Events: {}

	};
	
	//시트 초기화
	IBSheet.create({
		id: 'sheet', // 생성할 시트의 id
		el: 'sheetDiv', // 시트를 생성할 Dom 객체 및 id
		options: options, // 생성될 시트의 속성
	});

}


</script>

...

<body onload="initSheet()">

 

 

그럼 아래와 같이 표시된다.

 

 

여기까지 생성 완료


4. 조회

조회를 해보자.

 

EgovBBSManageController.java에서 공지사항 페이지로 오는 selectBoardList.do 쪽을 보면

 

 

Map<String, Object> map = bbsMngService.selectBoardArticles(boardVO, vo.getBbsAttrbCode());

 

bbsMngService는 게시판 관리 기능을 담당하는 서비스 클래스이다.

이 클래스는 게시글 목록을 조회하는 selectBoardArticles 메서드를 제공하며,

해당 메서드는 조회 결과를 Map<String, Object> 형태로 반환한다.

반환된 Map 객체에는 게시글 목록을 담은 resultList와 총 게시글 수를 나타내는 resultCnt가 포함된다.

 

이 데이터를 처리하기 위해 컨트롤러에서는 bbsMngService.selectBoardArticles 메서드를 호출하고,

결과로 얻어진 Map 객체에서 resultList와 resultCnt를 추출한다.

추출된 데이터를 모델에 담아 뷰로 전달하기 위해 model.addAttribute 메서드를 사용하여 각각 resultList와 resultCnt를 화면에 전달한다.

 

즉, 게시글 목록과 총 게시글 수를 화면에서 받을 수 있는 것.

 

 

페이지 네비게이션도 있는 화면이니 SearchMode:4를 사용해서 페이징 모드로 조회를 하자.

기존 로직은 냅두되, selectBoardArticles 로 데이터를 불러오는것만 동일하게 하기.

 

 

1) 컨트롤러에 조회로직 추가

▼ EgovBBSManageController.java 에 ibsheet 전용 paging 조회용 selectLargeSheetData 추가

더보기

 

페이징 조회를 하기 위해서는 ibpage에 담긴 현재페이지 수에 맞게 데이터를 보내줘야한다.

 

    /**
     * IBSheet8 paging 조회
     *
     * @param boardVO
     * @param model
     * @return
     * @throws Exception
     */
	@RequestMapping(value = "/cop/bbs/searchPaging.do")
	@ResponseBody
	public String selectLargeSheetData(@RequestParam Map<String,String> map,
							@ModelAttribute("searchVO") BoardVO searchVO,
							ModelMap model) throws Exception {

		//서버페이징 필수 조건
		int rowidx = Integer.parseInt((String)map.get("ibpage"));  //현재가 몇번째 페이지
		int onepagerow = Integer.parseInt((String)map.get("OnePageRow")); //한번에 몇건씩 가져올 것인지
		String searchCnd = (String)map.get("searchCnd"); //검색조건
		String searchWrd = (String)map.get("searchWrd"); //검색어
		
		/***********************  이 부분이 제일 중요 ***************************************/
		int startrow,endrow;
		startrow = rowidx==1?0:((rowidx-1) * onepagerow);
		endrow = startrow + (onepagerow-1);
		/***********************  이 부분이 제일 중요 ***************************************/

		//정보를 보낸다
		searchVO.setFirstIndex(startrow); //첫인덱스
		searchVO.setLastIndex(endrow); //마지막인덱스
		searchVO.setRecordCountPerPage(onepagerow); //페이지당 레코드 개수
		searchVO.setPageIndex(rowidx); //페이지인덱스
		searchVO.setSearchCnd(searchCnd); //조회조건
		searchVO.setSearchWrd(searchWrd); //검색어
		searchVO.setBbsId(searchVO.getBbsId()); // bbsId

		Map<String, Object> map2 = bbsMngService.selectBoardArticles(searchVO,null);
		int totCnt = Integer.parseInt((String)map2.get("resultCnt"));

	
		Map<String, Object> sheetData = new HashMap<>();
		sheetData.put("Total", totCnt); // 반드시 필요
		sheetData.put("data", map2.get("resultList"));
		System.out.println("데이터 조회 : "+map2.get("resultList"));
		
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.getFactory().configure(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature(), true); //한글설정
		String jsonString = objectMapper.writeValueAsString(sheetData);

		return jsonString;
	}

 

import com.fasterxml.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

 

 이것들이 추가되어야하는데 pom.xml 에서 jackson을 추가해줘야 함....

하는 김에 common-lang도 추가하자.

 

 

 

2) pom.xml 추가

Controller에 쓰는거 미리 추가하자.

		<!-- ibsheet용 jackson 추가 -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.15.2</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.15.2</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>2.15.2</version>
		</dependency>
		<!-- ibsheet용 commons-lang 추가 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.12.0</version>
		</dependency>

 

pom.xml 에 위 구문 추가해주고 Maven > Update Project 하는거 잊지말기!

 

 

3) js에 조회로직 추가

 

		Events: {
			onRenderFirstFinish: function(param) {
				search();
			},
		}

	};
    
    ....
    
    
    //외부함수로 조회로직 추가
    function search(){
	// 폼 요소 선택
	const form = document.forms["frm"];
	// FormData 생성 후 URLSearchParams로 변환
	const SearchParam = new URLSearchParams(new FormData(form)).toString();

	
	sheet.doSearchPaging({
		url: "searchPaging.do",
		param:SearchParam,
		cPage: 1,
		sync: 0,
		method:"POST",
		pageLengthParam:"OnePageRow",
		callback:function (rtn) {
		    var rtnData = JSON.parse(rtn.data); 

		   
		  }
	});
}

 

기존 조회로직에 맞게 Form 요소를 전체 던져주자.

기본적으로 내장 DB인 HSQL로 돌고있으니 데이터가 없음.

등록버튼 눌러서 게시글 하나 추가 후 페이지 새로고침 해보면 시트에도 잘 조회됨을 볼 수 있다.

조회데이터 : [egovframework.let.cop.bbs.service.BoardVO@3adbf1bd[bbsAttrbCode=,bbsNm=,bbsTyCode=,fileAtchPosblAt=,firstIndex=1,frstRegisterNm=관리자,isExpired=N,lastIndex=1,lastUpdusrNm=,pageIndex=1,pageSize=10,pageUnit=10,parntsReplyLc=,parntsSortOrdr=,plusCount=false,posblAtchFileNumber=0,recordCountPerPage=10,replyPosblAt=,rowNo=0,searchBgnDe=,searchCnd=,searchEndDe=,searchUseYn=,searchWrd=,sortOrdr=0,subPageIndex=,atchFileId=,bbsId=BBSMSTR_AAAAAAAAAAAA,frstRegisterId=USRCNFRM_00000000000,frstRegisterPnttm=2025-2-18,inqireCo=0,lastUpdusrId=,lastUpdusrPnttm=,ntceBgnde=10000101            ,ntceBgndeView=,ntceEndde=99991231            ,ntceEnddeView=,ntcrId=,ntcrNm=,nttCn=,nttId=1,nttNo=0,nttSj=제목입니다,parnts=0,password=,replyAt=N,replyLc=0,sortOrdr=0,useAt=Y]]

서버 콘솔에도 sysout로 데이터를 확인할 수 있음.

 

여기까지 기본조회 완료.

 

[페이지 디자인 및 구현 방법]

시트는 조회성으로 따로 페이지 이동해서 저장하는 등의 기능을 구현하기엔 번거롭다.

따라서 최대한 기존 게시판과 동일하게 만들기 위해 EditDialog를 사용해서 게시글 추가 페이지처럼 사용한다.
목록 상에서는 제목만 보이지만 실제로는 내용 및 첨부파일까지 가져온다.
클릭 이벤트에서 EditDialog를 띄우고 내용을 추가할 수 있으며, 첨부파일도 등록할 수 있다.
Insert, Update 페이지를 각각 만드는 것이 아닌, [저장]버튼 하나로 게시글을 한번에 저장한다.
단, 게시글은 1개씩만 수정될 수 있도록 한다.

 

 

 


추가적으로 날짜형식을 맞춰준다던지, 작성자를 가져온다던지, 게시글 추가, 저장 버튼을 만든다던지 등

jsp 수정이 필요하다.

 

아래 전체 소스 붙여넣기

 

▼ 아래에 EgovNoticeList.jsp 전체 소스

더보기

시트 표시를 위한 js 설정 + 버튼 모양을 위한 .styled-button 스타일 추가 + 기존테이블 주석처리

 

<%--
  Class Name : EgovNoticeList.jsp
  Description : 게시물 목록화면
  Modification Information
 
      수정일         수정자                   수정내용
    -------    --------    ---------------------------
     2009.03.19   이삼섭          최초 생성
     2011.08.31   JJY       경량환경 버전 생성
 
    author   : 공통서비스 개발팀 이삼섭
    since    : 2009.03.19  
--%>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page import="egovframework.com.cmm.service.EgovProperties" %>    
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<c:set var="ImgUrl" value="/images/egovframework/cop/bbs/"/>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
<meta http-equiv="content-language" content="ko">
<link href="<c:url value='/'/>css/common.css" rel="stylesheet" type="text/css" >
<c:if test="${anonymous == 'true'}"><c:set var="prefix" value="/anonymous"/></c:if>
<script type="text/javascript" src="<c:url value='/js/EgovBBSMng.js' />" ></script>


<%-- 시트 관련 js --%>
<link type="text/css" rel="stylesheet" href="<c:url value='/js/ibsheet8/css/default/main.css'/>"/>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibsheet.js'/>"></script> <%-- 코어 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/locale/ko.js'/>"></script> <%-- 메세지 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-common.js'/>"></script> <%-- 시트 공통 설정 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-dialog.js'/>"></script> <%-- 시트에서 띄우는 다이얼로그 관련 함수 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-excel.js'/>"></script> <%-- 서버롤 호출하여 다운로드/업로드시 필요 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibleaders.js'/>"></script> <%-- 라이선스  파일 --%>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>



<c:choose>
<c:when test="${preview == 'true'}">
<script type="text/javascript">
<!--
    function press(event) {
    }

    function fn_egov_addNotice() {
    }
    
    function fn_egov_select_noticeList(pageNo) {
    }
    
    function fn_egov_inqire_notice(nttId, bbsId) {      
    }
//-->
</script>
</c:when>
<c:otherwise>
<script type="text/javascript">
<!--
    function press(event) {
        if (event.keyCode==13) {
            fn_egov_select_noticeList('1');
        }
    }

    function fn_egov_addNotice() {
        document.frm.action = "<c:url value='/cop/bbs${prefix}/addBoardArticle.do'/>";
        document.frm.submit();
    }
    
    function fn_egov_select_noticeList(pageNo) {
        document.frm.pageIndex.value = pageNo;
        document.frm.action = "<c:url value='/cop/bbs${prefix}/selectBoardList.do'/>";
        document.frm.submit();  
    }
    
    function fn_egov_inqire_notice(nttId, bbsId) {
        document.subForm.nttId.value = nttId;
        document.subForm.bbsId.value = bbsId;
        document.subForm.action = "<c:url value='/cop/bbs${prefix}/selectBoardArticle.do'/>";
        document.subForm.submit();          
    }
//-->

/*********
 시트 초기화
 **********/
function initSheet(){
	var options = {
		Cfg: {
			SearchMode:4,
			ColorState: 7, // 편집할 수 없는 셀의 배경색을 표현하지 않음
			InfoRowConfig: {
				Visible: true,
				Space: "Bottom",
				"Layout": ['Paging2',"<button onclick='addArticle()' class='styled-button'>게시글 추가</button> " + 
				 	"<button onclick='saveArticle()' class='styled-button''>저장</button>"],
			},
			Export:{
				FilePath:'../../files/'
			},
			
			PageLength: 5,  // 한페이지당 표시할 게시글 수
		},
		
		Def: {
			Row: {
				CanFormula: true,
				CalcOrder: "sStatus,isLblistFile"
				
			}
		},
		LeftCols:[
			{Header: "번호", 		Type: "Int", 	Name: "SEQ", 				Width: 50,	 	CanEdit:0,	Visible:0},
			{Header: "번호",		Type: "Int",	Name: "nttIdx",				Width: 50,	 	CanEdit:0, 	Align: "Center", Visible:1},
			{Header: "상태",						Name: "sStatus",			Width: 60,   	Extend: IB_Preset.STATUS, CanEdit:0},
		],
		Cols: [
			{Header: "제목",		Type: "Text",	Name: "nttSj",				Width: 200,		Align: "Left",	RelWidth:1},
			{Header: "첨부파일",	Type: "File",	Name: "lblistFile",			Width: 100,		Align: "Center"},
			{Header: "내용",		Type: "Lines",	Name: "nttCn",				Width: 100,		Align: "Left",	Visible:0},
			{Header: "작성자",		Type: "Text",	Name: "frstRegisterNm",		Width: 80,		Align: "Center", 	Visible:1, DefaultValue:getUsername(), CanEdit:0},
			{Header: "작성일",		Type: "Date",	Name: "frstRegisterPnttm",	Width: 100,		Align: "Center", 
					Format:"yyyy-MM-dd",DataFormat:"yyyy-MM-dd",EditFormat:"yyyyMMdd", DefaultValue:today(), CanEdit:0
			},
		//	{Header: "조회수",		Type: "Int", 		Name: "inqireCo",	Width: 100,	Align: "Center",	Visible:0, CanEdit:0}, //조회수 사용 안함
			{Header: "첨부파일 포함유무",	Type: "Bool",	Name: "isLblistFile",	Width: 100,		Align: "Center", Visible:0,
				Formula:function(fr){
					if(fr.Row['lblistFile'] == ''){
						return false;
					}else{
						return true;
					}
				}
			},
			{Header: "게시물 삭제",						Name: "sDelete",	Width: 60,	Extend: IB_Preset.DelCheck, Visible:0},
			{Header: "파일 id",		Type: "Text",	Name: "atchFileId",	Width: 100,						Visible:0},
			{"Header": "파일명", 		Type: "Text",	Name: "streFileNm",	Width: 100,	Align: "Center", 	Visible: 0},
			
				
		],
		Events: {
			onRenderFirstFinish: function(param) {
				//데이터 조회
				search();
			},
			
			onBeforeDataLoad:function(evtParam){
				// 조회 결과 데이터
				var DATA = evtParam.data;
				// 조회된 데이터 일부를 수정한다.
			    for(var i = 0; i < DATA.length; i++){
			        var row = DATA[i];
			        //frstRegisterPnttm 값이 비어있지 않는 경우, 날짜가 한자리수 일 때 0을 붙여준다.
			        if(!isNull(row["frstRegisterPnttm"])){
			            row["frstRegisterPnttm"] = formatDate(row["frstRegisterPnttm"]);
			        }

					//nttId 값을 넣어준다.
			        row["nttIdx"] =row["nttId"];
			        
					//삭제된 행의 데이터를 지워준다.
			    	if( row["nttSj"] == "이 글은 작성자에 의해서 삭제되었습니다." ){
			    	   row["Del"] = 1;
			    	   row["nttSjTextStyle"] = 9;
			    	   row["nttSjCanEdit"] = 0;

					   //컬럼 비우기
			    	   row["lblistFileDisabled"] = 1;
			    	   row["frstRegisterNm"] = '';
			    	   row["frstRegisterPnttm"] = '';
			    	   row["atchFileId"] = '';
			    	   row["streFileNm"] = '';
			    	   row["nttCn"] = '';
			    	   
			      	}
			       
				   //파일명을 첨부파일 컬럼에 넣는다.
		            if(row["streFileNm"]) row["lblistFile"] = row["streFileNm"];
			        
			    }
			},
			onSetFile: function (evtParam) {
				if(evtParam.files[0]){
				    console.log("첨부파일명 : ", evtParam.files[0].name);
					//파일명을 저장하는 더미에 이름을 추가한다.
					evtParam.row["streFileNm"] = evtParam.files[0].name;
				}
			},
			onAfterClick: function(param) {
				//게시글은 무조건 한개만 수정되도록 한다.
				
				// 상태값 확인
				var isModified = sheet.getRowsByStatus("Added,Changed,Deleted");
				
				if(!isNull(param.row.Del)){
					return;
				}
				
    		    if(param.col == "nttSj" && isModified.length < 1 ){ //변경된게 없음
    		     	sheet.showEditDialog({row:param.row,excludeHideCol:1});
    		    }else if(param.col == "nttSj" && isModified.length == 1 && param.row.sStatus != "R"){ //변경된게 있고 그걸 클릭한 경우
      		      	sheet.showEditDialog({row:param.row,excludeHideCol:1});
    		    }else if((param.col == "nttSj" || param.col == "lblistFile") && isModified.length == 1 && param.row.sStatus == "R"){ //변경된게 있고 신규를 클릭한 경우
					sheet.setAttribute(param.row, "lblistFile", "Disabled", 1);
    		        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 편집이 가능합니다.</span>",time: 1000});

    		    }
			},
			onDblClick:function(evtParam){
    		    if(evtParam.col == "nttSj"){
					return true;//더블클릭을 막음
    		    }
			},
	   		 onAfterSave : function(evtParam) {
	   			 //저장 후 메시지 출력
				 sheet.showMessageTime({
				        message: "<span style='color:black'>저장되었습니다.</span>",
				        func:function(args){
				        	search(); //재조회
				        }
				 })
			 }
		}
	};
	
	//시트 초기화
	IBSheet.create({
		id: 'sheet', // 생성할 시트의 id
		el: 'sheetDiv', // 시트를 생성할 Dom 객체 및 id
		options: options, // 생성될 시트의 속성
	});

}


/**************
 편집 다이얼로그 수정
 **************/
IBSheet.onBeforeCreate = function (obj) {
	//파일 타입 컬럼은 EditDialog에 뜨지 않게 한다.(EditDialog에서 파일첨부 불가)
	if(obj.id == "editSheet_sheet"){
		var editDialogCols = obj.options["Body"][0];
		for(i=0;i<editDialogCols.length;i++){ 
			if(editDialogCols[i]["TargetType"] == "File"){
				editDialogCols[i]["Visible"] = 0;
				
			}else if(editDialogCols[i]["TargetType"] == "Lines" 
					|| editDialogCols[i]["TargetType"] == "Bool"){
				editDialogCols[i]["Visible"] = 1;
			}
				
		}
	}
    return obj;
}



/*****
 조회
 *****/
function search(){
	// 폼 요소 선택
	const form = document.forms["frm"];
	// FormData 생성 후 URLSearchParams로 변환
	const SearchParam = new URLSearchParams(new FormData(form)).toString();

	
	sheet.doSearchPaging({
		url: "searchPaging.do",
		param:SearchParam,
		cPage: 1,
		sync: 0,
		method:"POST",
		pageLengthParam:"OnePageRow",
		callback:function (rtn) {
		    var rtnData = JSON.parse(rtn.data); 

		   
		  }
	});
}


/********************
 한자릿수 날짜 앞에 0을 넣는다.
 ********************/
function formatDate(dateString) {
    // 문자열을 "-"를 기준으로 분리
    let parts = dateString.split("-");

    // parts[1]이 한 자리 숫자인 경우 (월)
    if (parts[1].length === 1) {
        parts[1] = "0" + parts[1];
    }

    // parts[2]이 한 자리 숫자인 경우 (일)
    if (parts[2].length === 1) {
        parts[2] = "0" + parts[2];
    }

    // "YYYY-MM-DD" 형식으로 다시 합치기
    return parts.join("-");
}


/*************
 공백 여부 판단한다.
 *************/
function isNull(value) {
	
	if(value == null || value == undefined || value == ""){
		return true;	
	}else{
		return false;
	}
	
}

/**************
 오늘 날짜를 가져온다.
 **************/
function today(){
	return IBSheet.dateToString(new Date(),'yyyy-MM-dd');
}


/********************
 상단에서 접속한 아이디를가져온다.
 ********************/
function getUsername() {
    const loginElement = document.getElementById("header_loginname");
    const fullName = loginElement.textContent;
    // "님"을 제외한 아이디 부분만 추출
    const username = fullName.trim().replace("님", "");
    
    return username;
}


/*********
 게시글 추가
 **********/
function addArticle(){

	//로그인한 아이디가 없을 경우 alert 발생
	if(isNull(getUsername())){
		alert("로그인 후 등록 가능합니다.");
		return;
	}

	
	//행추가 시 EditDialog 열림
	if(!sheet.hasChangedData()){
		sheet.addRow({"next":sheet.getFirstRow(),init:{nttIdx : sheet.getTotalRowCount()+1}} );
		sheet.showEditDialog({row:sheet.getFocusedRow(),excludeHideCol:1});
	}else{

        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 등록이 가능합니다.</span>",time: 1000});
	}
}


/**********
 상태값 저장
 **********/
function saveArticle(){
	//로그인한 아이디가 없을 경우 alert 발생
	if(isNull(getUsername())){
		alert("로그인 후 등록 가능합니다.");
		return;
	}
	
	popup(); //alert발생
}

/******************
 상태값에 따른 alert 발생
 ******************/
function popup() {
	
	//상태값에 따라 Text 변경
	var article = sheet.getSaveJson().data;
	var statusNm = "";
	var statusBtn = "저장";

	if(article.length == 0){
        sheet.showMessageTime({message: "<span style='color:black'>변경사항이 없습니다.</span>",time: 1000});	
        return;
	}
	
	if(article[0].sStatus == "I"){
		statusNm = "추가";
	}else if(article[0].sStatus == "U"){
		statusNm = "수정";
	}else if(article[0].sStatus == "D"){
		statusNm = "삭제";
		statusBtn = "삭제";
	}
	
	
	//팝업 띄우기
	if(article.length > 1){ //편집 게시글 한개이상
        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 편집이 가능합니다.</span>",time: 1000});
	
	}else if(article.length == 1){ //게시글을 한개씩만 저장한다.

		sheet.showMessageTime({
			message: "<span style='color:black'>해당 게시글을 " + statusNm + "하시겠습니까?</span>",
			time: 0,
			buttons: [statusBtn, "취소"],
			func: function (args) {
				//저장 시
				if (args==1) {
					
					var formData = sheet.getSaveJson({saveMode:2, formData: true}); //시트의 데이터와 file를 추출


					// 'frm' 폼 데이터 추가
					const form = document.forms["frm"];
					for (var k = 0; k < form.elements.length; k++) {
						var frmInput = form.elements[k];
						if (frmInput.name) {
							formData.append(frmInput.name, frmInput.value);
						}
					}

					
					//데이터 저장
					$.ajax({
						url: "/sht_webapp/cop/bbs/sheetfileSave.do",
							data: formData,
							method: "POST",
							enctype: 'multipart/form-data',
							contentType: false,
							processData: false,
							cache: false,
							success:function(data){
								var result = data.IO.Result;
								var fileData = data.IO.data;
								//결과를 시트에 반영한다.
								sheet.applySaveResult(result, null, null, fileData);
								
							},
							error: function(jqXHR, textStatus, errorThrown) {
								// 오류 발생 시 처리할 코드
								console.error("Error: " + textStatus, errorThrown);
							}

					});
				} else if (args==2) {
					//취소에 대한 처리
				}
			}
		});
	}
}

</script>
</c:otherwise>
</c:choose>
<title><c:out value="${brdMstrVO.bbsNm}"/> 목록</title>

<style type="text/css">
    h1 {font-size:12px;}
    caption {visibility:hidden; font-size:0; height:0; margin:0; padding:0; line-height:0;}
    
    /* ibsheet InfoRow의 버튼 디자인 */
	.styled-button{
		background-color: #3d5575; /* 버튼 배경색 */
		color: white; /* 텍스트 색상 */
		border: none; /* 테두리 제거 */
		padding: 6px 15px; /* 내부 여백 */
		font-size: 12px; /* 글씨 크기 */
		font-weight: bold; /* 글씨 굵게 */
		border-radius: 5px; /* 둥근 모서리 */
		cursor: pointer; /* 마우스 포인터 */
		transition: background-color 0.3s ease, transform 0.2s ease; /* 호버 효과 애니메이션 */
		float: right; /* 오른쪽 정렬 */
		margin-left: 10px; /* 버튼 간격 */
	}
</style>


</head>
<body onload="initSheet()">
<noscript class="noScriptTitle">자바스크립트를 지원하지 않는 브라우저에서는 일부 기능을 사용하실 수 없습니다.</noscript>
<!-- 전체 레이어 시작 -->
<div id="wrap">
    <!-- header 시작 -->
    <div id="header_mainsize"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncHeader.jsp"/></div>
    <div id="topnavi"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncTopnav.jsp"/></div>        
    <!-- //header 끝 --> 
    <!-- container 시작 -->
    <div id="container">
        <!-- 좌측메뉴 시작 -->
        <div id="leftmenu"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncLeftmenu.jsp"/></div>
        <!-- //좌측메뉴 끝 -->
            <!-- 현재위치 네비게이션 시작 -->
            <div id="content">
                <div id="cur_loc">
                    <div id="cur_loc_align">
                        <ul>
                            <li>HOME</li>
                            <li>&gt;</li>
                            <li>알림마당</li>
                            <li>&gt;</li>
                            <li><strong>${brdMstrVO.bbsNm}</strong></li>
                        </ul>
                    </div>
                </div>
                <!-- 검색 필드 박스 시작 -->
                <div id="search_field">
                    <div id="search_field_loc"><h2><strong><c:out value='${brdMstrVO.bbsNm}'/></strong></h2></div>
					<form name="frm" action ="<c:url value='/cop/bbs${prefix}/selectBoardList.do'/>" method="post">
						<input type="hidden" name="bbsId" value="<c:out value='${boardVO.bbsId}'/>" />
						<input type="hidden" name="nttId"  value="0" />
						<input type="hidden" name="bbsTyCode" value="<c:out value='${brdMstrVO.bbsTyCode}'/>" />
						<input type="hidden" name="bbsAttrbCode" value="<c:out value='${brdMstrVO.bbsAttrbCode}'/>" />
						<input type="hidden" name="authFlag" value="<c:out value='${brdMstrVO.authFlag}'/>" />
						<input name="pageIndex" type="hidden" value="<c:out value='${searchVO.pageIndex}'/>"/>
						<input name="ntcrId" type="hidden" value=""/>
						<input name="ntceBgnde" type="hidden" value=""/>
						<input name="ntceEndde" type="hidden" value=""/>
                        <input type="submit" value="실행" onclick="fn_egov_select_noticeList('1'); return false;" id="invisible" class="invisible" />
                        
                        <fieldset><legend>조건정보 영역</legend>
                        <div class="sf_start">
                            <ul id="search_first_ul">
                                <li>
								    <select name="searchCnd" class="select" title="검색조건 선택">
								           <option value="0" <c:if test="${searchVO.searchCnd == '0'}">selected="selected"</c:if> >제목</option>
								           <option value="1" <c:if test="${searchVO.searchCnd == '1'}">selected="selected"</c:if> >내용</option>             
								           <option value="2" <c:if test="${searchVO.searchCnd == '2'}">selected="selected"</c:if> >작성자</option>            
                                    </select>
                                </li>
                                <li>
                                    <input name="searchWrd" type="text" size="35" value='<c:out value="${searchVO.searchWrd}"/>' maxlength="35" onkeypress="press(event);" title="검색어 입력"> 
                                </li>
                                <li>
                                    <div class="buttons" style="position:absolute;left:870px;top:182px;">
                                        <a href="#LINK" onclick="javascript:fn_egov_select_noticeList('1'); return false;"><img src="<c:url value='/images/img_search.gif' />" alt="search" />조회 </a>
                                        <% if(null != session.getAttribute("LoginVO")){ %>
                                        <c:if test="${brdMstrVO.authFlag == 'Y'}">
                                           <!-- <a href="<c:url value='/cop/bbs${prefix}/addBoardArticle.do'/>?bbsId=<c:out value="${boardVO.bbsId}"/>">등록</a> -->
                                        </c:if>
                                        <%} %>
                                    </div>                              
                                </li>      
                            </ul>
                        </div>
                        </fieldset>
                    </form>
                </div>
                <!-- //검색 필드 박스 끝 -->
                <!-- 
                <div id="page_info"><div id="page_info_align"></div></div>                    
                 -->
                 
					<div id="search_field">
						<div id="sheetDiv" style="width: 100%; height: 500px;"></div>
					</div>
                 
                <!-- table add start -->
                <!--<div class="default_tablestyle">
                    <table summary="번호, 제목, 게시시작일, 게시종료일, 작성자, 작성일, 조회수   입니다" cellpadding="0" cellspacing="0">
                    <caption>게시물 목록</caption>
                    <colgroup>
                    <col width="10%">
                    <col>  
                    <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
	                    <col width="10%">
	                    <col width="10%">
				    </c:if>
				    <c:if test="${anonymous != 'true'}">
                        <col width="10%">
                    </c:if>
                    <col width="15%">
                    <col width="8%">
                    </colgroup>
                    <thead>
                    <tr>
                        <th scope="col" class="f_field" nowrap="nowrap">번호</th>
                        <th scope="col" nowrap="nowrap">제목</th>
                        <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
	                        <th scope="col" nowrap="nowrap">게시시작일</th>
	                        <th scope="col" nowrap="nowrap">게시종료일</th>
	                    </c:if>
	                    <c:if test="${anonymous != 'true'}">
	                        <th scope="col" nowrap="nowrap">작성자</th>
	                    </c:if>
                        <th scope="col" nowrap="nowrap">작성일</th>
                        <th scope="col" nowrap="nowrap">조회수</th>
                    </tr>
                    </thead>
                    <tbody>                 

                    <c:forEach var="result" items="${resultList}" varStatus="status">
                                                   
                      <tr>
				        <td><b><c:out value="${paginationInfo.totalRecordCount+1 - ((searchVO.pageIndex-1) * searchVO.pageSize + status.count)}"/></b></td>            
				        <td align="left">
				            <form name="subForm" method="post" action="<c:url value='/cop/bbs${prefix}/selectBoardArticle.do'/>">
				            <c:if test="${result.replyLc!=0}">
				                <c:forEach begin="0" end="${result.replyLc}" step="1">
				                    &nbsp;
				                </c:forEach>
				                <img src="<c:url value='/images/reply_arrow.gif'/>" alt="reply arrow"/>
				            </c:if>
				            <c:choose>
				                <c:when test="${result.isExpired=='Y' || result.useAt == 'N'}">
				                    <c:out value="${result.nttSj}" />
				                </c:when>
				                <c:otherwise>
				                        <input type="hidden" name="bbsId" value="<c:out value='${result.bbsId}'/>" />
				                        <input type="hidden" name="nttId"  value="<c:out value="${result.nttId}"/>" />
				                        <input type="hidden" name="bbsTyCode" value="<c:out value='${brdMstrVO.bbsTyCode}'/>" />
				                        <input type="hidden" name="bbsAttrbCode" value="<c:out value='${brdMstrVO.bbsAttrbCode}'/>" />
				                        <input type="hidden" name="authFlag" value="<c:out value='${brdMstrVO.authFlag}'/>" />
				                        <input name="pageIndex" type="hidden" value="<c:out value='${searchVO.pageIndex}'/>"/>
				                        

										<input type="hidden" name="parnts" value="<c:out value='${result.parnts}'/>" >
										<input type="hidden" name="sortOrdr" value="<c:out value='${result.sortOrdr}'/>" >
										<input type="hidden" name="replyLc" value="<c:out value='${result.replyLc}'/>" >
				                        
				                        
				                        <span class="link"><input type="submit" style="width:320px;border:solid 0px black;text-align:left;" value="<c:out value="${result.nttSj}"/>" ></span>
				                </c:otherwise>
				            </c:choose>
				            </form>
				        </td>
				        <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
				            <td ><c:out value="${result.ntceBgnde}"/></td>
				            <td ><c:out value="${result.ntceEndde}"/></td>
				        </c:if>
				        <c:if test="${anonymous != 'true'}">
				            <td ><c:out value="${result.frstRegisterNm}"/></td>
				        </c:if>
				        <td ><c:out value="${result.frstRegisterPnttm}"/></td>
				        <td ><c:out value="${result.inqireCo}"/></td>
				      </tr>
				     </c:forEach>     
				     <c:if test="${fn:length(resultList) == 0}">
				      <tr>
				        <c:choose>
				            <c:when test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
				                <td colspan="7" ><spring:message code="common.nodata.msg" /></td>
				            </c:when>
				            <c:otherwise>
				                <c:choose>
				                    <c:when test="${anonymous == 'true'}">
				                        <td colspan="4" ><spring:message code="common.nodata.msg" /></td>
				                    </c:when>
				                    <c:otherwise>
				                        <td colspan="5" ><spring:message code="common.nodata.msg" /></td>
				                    </c:otherwise>
				                </c:choose>     
				            </c:otherwise>
				        </c:choose>       
				          </tr>      
				     </c:if>  
                    </tbody>
                    </table>
                </div>--> 
				
                <!-- 페이지 네비게이션 시작
                <div id="paging_div">
                    <ul class="paging_align">
                        <ui:pagination paginationInfo="${paginationInfo}" type="image" jsFunction="fn_egov_select_noticeList" />    
                    </ul>
                </div> -->
                <!-- //페이지 네비게이션 끝 -->  
            </div>
            <!-- //content 끝 -->    
        </div>
        <!-- //container 끝 -->
        <!-- footer 시작 -->
        <div id="footer"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncFooter.jsp"/></div>
        <!-- //footer 끝 -->
    </div>
    <!-- //전체 레이어 끝 -->
 </body>
</html>

 

 

 


 

 

이렇게 나오면 jsp 설정은 끝!

 


4. 저장

화면에 [게시글 추가], [저장] 버튼을 만들고 제목 클릭 시 EditDialog를 띄우게 만들었다.

ibsheet는 상태값이라는게 있어서 추가/수정/삭제 와 같이 상태값이 변화한 항목들을 뽑아 DB에 반영할 수 있다.

기존에는 추가 / 수정 / 삭제가 다 로직이 따로 있는데, 이걸 그냥 하나로 묶어버리려고 함.

 


1) 컨트롤러에 저장로직 추가

▼ EgovBBSManageController.java 에 ibsheet 전용 저장로직인 filesaveIBSheet 추가

더보기

 

상단에 ServletContext 는 수기로 추가해주자.

 

@Autowired
private ServletContext servletContext;

 

저장로직 추가(각 내용 주석 확인)

  /**
  * 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 추가

(상단에 resultMap도 추가할 것)

더보기

(MyBatis 기반임)

 

resultMap에서 NTT_CN, STRE_FILE_NM 컬럼을 추가하고,

SELECT 쪽에서

 

a.NTT_CN, f.STRE_FILE_NM 

 

LEFT OUTER JOIN
LETTNFILEDETAIL f
ON a.ATCH_FILE_ID = f.ATCH_FILE_ID

 

만 추가한 것.

File 쪽은 LETTNFILEDETAIL 테이블에서 관리하기 때문에 JOIN 해줬다.

	<resultMap id="boardList" class="egovframework.let.cop.bbs.service.BoardVO">
		<result property="bbsId" column="BBS_ID" columnIndex="1"/>
		<result property="nttId" column="NTT_ID" columnIndex="2"/>
		<result property="nttSj" column="NTT_SJ" columnIndex="3"/>
		<result property="frstRegisterId" column="FRST_REGISTER_ID" columnIndex="4"/>
		<result property="frstRegisterNm" column="FRST_REGISTER_NM" columnIndex="5"/>
		<result property="frstRegisterPnttm" column="FRST_REGIST_PNTTM" columnIndex="6"/>
		<result property="inqireCo" column="RDCNT" columnIndex="7"/>
		<result property="parnts" column="PARNTSCTT_NO" columnIndex="8"/>
		<result property="replyAt" column="ANSWER_AT" columnIndex="9"/>		
		<result property="replyLc" column="ANSWER_LC" columnIndex="10"/>
		<result property="useAt" column="USE_AT" columnIndex="11"/>
		<result property="atchFileId" column="ATCH_FILE_ID" columnIndex="12"/>
		<result property="ntceBgnde" column="NTCE_BGNDE" columnIndex="13"/>
		<result property="ntceEndde" column="NTCE_ENDDE" columnIndex="14"/>
		<result property="nttCn" column="NTT_CN" columnIndex="15"/>
		<result property="streFileNm" column="STRE_FILE_NM" columnIndex="16"/>
	</resultMap>


....

    <select id="selectBoardArticleList" parameterType="egovframework.let.cop.bbs.service.BoardVO" resultMap="boardList">
		
			SELECT
				a.NTT_ID, a.NTT_SJ, a.FRST_REGISTER_ID, IFNULL(b.USER_NM, a.NTCR_NM) as FRST_REGISTER_NM,
				YEAR(a.FRST_REGIST_PNTTM)||'-'||MONTH(a.FRST_REGIST_PNTTM)||'-'||DAYOFMONTH(a.FRST_REGIST_PNTTM) as FRST_REGIST_PNTTM,
				a.RDCNT, a.PARNTSCTT_NO, a.ANSWER_AT, a.ANSWER_LC, a.USE_AT, a.ATCH_FILE_ID,
				a.BBS_ID, a.NTCE_BGNDE, a.NTCE_ENDDE,
				
				a.NTT_CN,
        		f.STRE_FILE_NM  
			FROM
				LETTNBBS a
			LEFT OUTER JOIN 
				LETTNEMPLYRINFO b
			ON a.FRST_REGISTER_ID = b.ESNTL_ID 

			LEFT OUTER JOIN
				LETTNFILEDETAIL f
			ON a.ATCH_FILE_ID = f.ATCH_FILE_ID


			WHERE
				a.BBS_ID = #{bbsId}
		
			<if test="searchCnd == 0">AND
					a.NTT_SJ LIKE '%' || #{searchWrd} || '%' 		
			</if>
			<if test="searchCnd == 1">AND
					a.NTT_CN LIKE  '%' || #{searchWrd} || '%' 		
			</if>
			<if test="searchCnd == 2">AND
					b.USER_NM LIKE '%' || #{searchWrd} || '%' 		
			</if>
					
			ORDER BY a.SORT_ORDR DESC, NTT_NO ASC
			LIMIT #{recordCountPerPage} OFFSET #{firstIndex}
						
	</select>

 

SELECT 문만 수정해주면 된다.

 

이렇게 되면 조회할 때 게시글 내용과 파일까지 함께 넘어온다.

 


4. 파일 설정

 

아까 게시글 첨부파일 명을 board에서 처리하기 위해 Board.java에서 FIle이름을 받을 수 있는 인자를 추가해줬었다.

    /**
     * 게시물 첨부파일 명
     */
    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;
	}

 

 

파일명만 문제가 아니라, 해당 파일 경로와 파일명을 페이지로 리턴해줘야하기 때문에

첨부파일 목록에서 파일 저장 경로와 이름을 수정해줘야 한다.

 

1) EgovFileMngUtil.java 수정


첨부파일에 대한 목록 정보를 취득하는  parseFileInf를 아래와 같이 수정한다.

    /**
     * 첨부파일에 대한 목록 정보를 취득한다.
     *
     * @param files
     * @return
     * @throws Exception
     */
    public List<FileVO> parseFileInf ~~~~

 

EgovFileMngUtil.java 에서 아래부분 수정

더보기

일부 파일 경로 및 폴더설정을 따라간다.

    /**
     * 첨부파일에 대한 목록 정보를 취득한다.
     *
     * @param files
     * @return
     * @throws Exception
     */
    public List<FileVO> parseFileInf(Map<String, MultipartFile> files, String KeyStr, int fileKeyParam, String atchFileId, String storePath) throws Exception {
	int fileKey = fileKeyParam;

	String storePathString = "";
	String atchFileIdString = "";

	if ("".equals(storePath) || storePath == null) {
	    storePathString = propertyService.getString("Globals.fileStorePath");
	} else {
	    //storePathString = propertyService.getString(storePath);
		storePathString = storePath; //IBSheet) Cfg.FilePath에 설정한 경로 그대로 설정
	}

	if ("".equals(atchFileId) || atchFileId == null) {
	    atchFileIdString = idgenService.getNextStringId();
	} else {
	    atchFileIdString = atchFileId;
	}

	//File saveFolder = new File(storePathString);
	File saveFolder = new File(EgovWebUtil.filePathBlackList(storePathString)); //IBSheet) 폴더 설정

	if (!saveFolder.exists() || saveFolder.isFile()) {
	    saveFolder.mkdirs();
	}

	Iterator<Entry<String, MultipartFile>> itr = files.entrySet().iterator();
	MultipartFile file;
	String filePath = "";
	List<FileVO> result  = new ArrayList<FileVO>();
	FileVO fvo;

	while (itr.hasNext()) {
	    Entry<String, MultipartFile> entry = itr.next();

	    file = entry.getValue();
	    String orginFileName = file.getOriginalFilename();

	    //--------------------------------------
	    // 원 파일명이 없는 경우 처리
	    // (첨부가 되지 않은 input file type)
	    //--------------------------------------
	    if ("".equals(orginFileName)) {
		continue;
	    }
	    ////------------------------------------

	    int index = orginFileName.lastIndexOf(".");
	    //String fileName = orginFileName.substring(0, index);
	    String fileExt = orginFileName.substring(index + 1);
	    String newName = KeyStr + EgovStringUtil.getTimeStamp() + fileKey;
	    long _size = file.getSize();

	    if (!"".equals(orginFileName)) {
		//filePath = storePathString + File.separator + newName;
		//file.transferTo(new File(filePath));
		filePath = storePathString + File.separator + orginFileName; //IBSheet)기존이름으로 설정
		file.transferTo(new File(EgovWebUtil.filePathBlackList(filePath))); //IBSheet)파일 path 설정
	    }
	    fvo = new FileVO();
	    fvo.setFileExtsn(fileExt);
	    fvo.setFileStreCours(storePathString);
	    fvo.setFileMg(Long.toString(_size));
	    fvo.setOrignlFileNm(orginFileName);
	    //fvo.setStreFileNm(newName);
	    fvo.setStreFileNm(orginFileName); //IBSheet) 파일 이름을 [파일이름.확장자] 형식으로 그대로 전달
	    
	    fvo.setAtchFileId(atchFileIdString);
	    fvo.setFileSn(String.valueOf(fileKey));

	    //writeFile(file, newName, storePathString);
	    result.add(fvo);

	    fileKey++;
	}

	return result;
    }

 

 

이렇게 하면 설정 끝.

 


5. 정리

정리하자면 수정한 파일 목록은 아래와 같다.(제품은 추가한 것)

 

 

동작확인

 

로그인하고 시트 모양 인

 

 

게시글 추가

 

 

첨부파일 등록 후 클릭하면 다운로드 됨

 

 

수정

 

 

삭제

반응형
LIST

'Project > eGov 샘플 프로젝트' 카테고리의 다른 글

부트스트랩(Bootstrap) 모달 띄우기  (0) 2025.02.26
반응형
SMALL

+ Recent posts

반응형
LIST