WebSquare + Spring 연동 예제 소스 코드 (MyBatis X)
이 예제는 "사용자 정보 조회" 및 "사용자 정보 등록" 기능을 구현합니다.
1. WebSquare 화면 (클라이언트)
userList.xml (화면 정의)
XML
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:w2="http://www.websquare.com/2001/WebSquare"
xmlns:xq="http://www.w3.org/2005/xpath-functions">
<head>
<w2:type>DEFAULT</w2:type>
<w2:buildDate/>
<w2:exampleDataMap/>
<w2:docType>HTML</w2:docType>
<w2:uiInfo>
<w2:w2ui chartid="" ev:onpageload="scwin.onpageload" gcmode="true" weak="false"/>
</w2:uiInfo>
<script type="javascript" lazy="false" src="/ui/user/userList.js"></script>
</head>
<body>
<w2:dataMap baseNode="map" id="dataMap_search">
<w2:udc_info>
<w2:column dataType="text" name="userName"></w2:column>
</w2:udc_info>
</w2:dataMap>
<w2:dataMap baseNode="map" id="dataMap_user">
<w2:udc_info>
<w2:column dataType="text" name="userId"></w2:column>
<w2:column dataType="text" name="userName"></w2:column>
<w2:column dataType="text" name="userEmail"></w2:column>
</w2:udc_info>
</w2:dataMap>
<w2:dataList baseNode="list" id="dataList_userList" saveRemovedData="true">
<w2:udc_info>
<w2:column dataType="text" name="userId"></w2:column>
<w2:column dataType="text" name="userName"></w2:column>
<w2:column dataType="text" name="userEmail"></w2:column>
</w2:udc_info>
</w2:dataList>
<w2:input id="input_searchUserName" style="width: 150px;" ref="dataMap_search.userName"></w2:input>
<w2:button id="btn_search" style="width: 80px;height: 23px;" ev:onClick="scwin.btn_search_onClick" value="조회"></w2:button>
<w2:button id="btn_new" style="width: 80px;height: 23px;" ev:onClick="scwin.btn_new_onClick" value="신규등록"></w2:button>
<w2:grid id="grid_user" scrollByColumn="false" scrollByCell="false"
dataList="dataList_userList" style="width: 400px;height: 200px;">
<w2:caption/>
<w2:header style="">
<w2:row style="">
<w2:column displayMode="label" id="column1" inputType="text" style="height:20px;" value="아이디" width="100"></w2:column>
<w2:column displayMode="label" id="column2" inputType="text" style="height:20px;" value="이름" width="150"></w2:column>
<w2:column displayMode="label" id="column3" inputType="text" style="height:20px;" value="이메일" width="150"></w2:column>
</w2:row>
</w2:header>
<w2:gBody style="">
<w2:row style="">
<w2:column displayMode="label" id="userId" inputType="text" width="100"></w2:column>
<w2:column displayMode="label" id="userName" inputType="text" width="150"></w2:column>
<w2:column displayMode="label" id="userEmail" inputType="text" width="150"></w2:column>
</w2:row>
</w2:gBody>
</w2:grid>
<w2:popup id="popup_userReg" style="width: 300px; height: 200px;">
<w2:anchor winType="window" xpath="html"/>
<w2:contents>
<w2:input id="input_userId_reg" ref="dataMap_user.userId" style="width: 150px;"></w2:input>
<w2:input id="input_userName_reg" ref="dataMap_user.userName" style="width: 150px;"></w2:input>
<w2:input id="input_userEmail_reg" ref="dataMap_user.userEmail" style="width: 150px;"></w2:input>
<w2:button id="btn_save" style="width: 80px;height: 23px;" ev:onClick="scwin.btn_save_onClick" value="저장"></w2:button>
</w2:contents>
</w2:popup>
</body>
</html>
userList.js (JavaScript 로직)
JavaScript
scwin.onpageload = function() {
// 페이지 로드 시 초기 데이터 조회 (선택 사항)
// scwin.searchUserList();
};
// 사용자 조회 버튼 클릭 이벤트
scwin.btn_search_onClick = function() {
scwin.searchUserList();
};
// 사용자 조회 함수 (READ)
scwin.searchUserList = function() {
var searchParam = dataMap_search.getJSON(); // dataMap_search의 데이터를 JSON 형태로 가져옴
WebSquare.request.execute( {
id : "searchUser",
action : "/api/users", // 조회 API URL
method : "GET",
mode : "asynchronous",
mediaType : "application/json",
requestData : searchParam, // 검색 조건 JSON
successCallback : function(res) {
// 서버 응답 (JSON)을 dataList_userList에 바인딩
dataList_userList.setJSON(res.responseJSON.userList); // 서버 응답에서 'userList' 키의 값을 바인딩
WebSquare.util.printStackTrace("사용자 목록 조회 성공: " + JSON.stringify(res.responseJSON));
},
errorCallback : function(res) {
WebSquare.util.printStackTrace("사용자 목록 조회 실패: " + JSON.stringify(res.responseJSON));
alert("사용자 목록 조회 실패했습니다.");
}
} );
};
// 신규등록 버튼 클릭 이벤트
scwin.btn_new_onClick = function() {
dataMap_user.setEmptyAll(); // 등록용 dataMap 초기화
$p.popup("popup_userReg").open(); // 등록 팝업 열기
};
// 사용자 저장 버튼 클릭 이벤트 (CREATE)
scwin.btn_save_onClick = function() {
var userData = dataMap_user.getJSON(); // dataMap_user의 데이터를 JSON 형태로 가져옴
if (!userData.userId || !userData.userName) {
alert("아이디와 이름은 필수 입력 항목입니다.");
return;
}
WebSquare.request.execute( {
id : "saveUser",
action : "/api/users", // 등록 API URL
method : "POST",
mode : "asynchronous",
mediaType : "application/json",
requestData : userData, // 사용자 정보 JSON
successCallback : function(res) {
alert("사용자 정보가 성공적으로 저장되었습니다.");
$p.popup("popup_userReg").close(); // 팝업 닫기
scwin.searchUserList(); // 목록 새로고침
},
errorCallback : function(res) {
WebSquare.util.printStackTrace("사용자 저장 실패: " + JSON.stringify(res.responseJSON));
alert("사용자 저장에 실패했습니다.");
}
} );
};
2. 백엔드 (Spring Boot 기준)
UserDto.java (DTO - Data Transfer Object)
Java
package com.example.websquareexample.dto;
public class UserDto {
private String userId;
private String userName;
private String userEmail;
// Getters and Setters (생성자를 사용해도 됩니다.)
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserEmail() {
return userEmail;
}
public void setUserEmail(String userEmail) {
this.userEmail = userEmail;
}
@Override
public String toString() {
return "UserDto{" +
"userId='" + userId + '\'' +
", userName='" + userName + '\'' +
", userEmail='" + userEmail + '\'' +
'}';
}
}
UserController.java (Controller)
Java
package com.example.websquareexample.controller;
import com.example.websquareexample.dto.UserDto;
import com.example.websquareexample.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController // @Controller + @ResponseBody (자동 JSON 변환)
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
// 사용자 목록 조회 (READ)
@GetMapping("/users")
public ResponseEntity<Map<String, Object>> getUsers(@RequestParam(required = false) String userName) {
System.out.println("[Controller] getUsers 요청. 검색 조건: userName=" + userName);
// 서비스 계층 호출
List<UserDto> userList = userService.getUsers(userName);
// WebSquare에 맞춰 Map 형태로 응답 구성 (userList 키로 List를 담음)
Map<String, Object> response = new HashMap<>();
response.put("userList", userList);
response.put("message", "사용자 목록 조회 성공");
return new ResponseEntity<>(response, HttpStatus.OK);
}
// 사용자 정보 등록 (CREATE)
@PostMapping("/users")
public ResponseEntity<Map<String, String>> createUser(@RequestBody UserDto userDto) {
System.out.println("[Controller] createUser 요청. UserDto: " + userDto.toString());
try {
// 서비스 계층 호출
userService.createUser(userDto);
Map<String, String> response = new HashMap<>();
response.put("message", "사용자 정보 등록 성공");
return new ResponseEntity<>(response, HttpStatus.CREATED); // 201 Created
} catch (Exception e) {
System.err.println("[Controller] 사용자 등록 실패: " + e.getMessage());
Map<String, String> errorResponse = new HashMap<>();
errorResponse.put("message", "사용자 등록 실패: " + e.getMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); // 400 Bad Request
}
}
// UPDATE, DELETE는 유사한 방식으로 구현됩니다.
// @PutMapping("/users/{userId}")
// @DeleteMapping("/users/{userId}")
}
UserService.java (Service)
Java
package com.example.websquareexample.service;
import com.example.websquareexample.dao.UserDao;
import com.example.websquareexample.dto.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserDao userDao; // DAO 의존성 주입
// 사용자 목록 조회 비즈니스 로직 (READ)
public List<UserDto> getUsers(String userName) {
System.out.println("[Service] getUsers 호출. 검색 조건: " + userName);
// 비즈니스 로직: 예를 들어, 검색 조건 가공, 권한 확인 등
// 현재는 간단히 DAO에 그대로 전달
return userDao.selectUsers(userName);
}
// 사용자 정보 등록 비즈니스 로직 (CREATE)
@Transactional // 트랜잭션 관리 (여러 DB 작업이 묶일 때 사용)
public void createUser(UserDto userDto) {
System.out.println("[Service] createUser 호출. 등록할 사용자: " + userDto.toString());
// 비즈니스 로직: 예를 들어, 아이디 중복 확인, 데이터 유효성 검사 등
if (userDao.selectUserById(userDto.getUserId()) != null) {
throw new RuntimeException("이미 존재하는 사용자 ID입니다: " + userDto.getUserId());
}
userDao.insertUser(userDto);
}
// UPDATE, DELETE에 대한 서비스 메서드도 유사하게 구현
}
UserDao.java (DAO - Data Access Object)
MyBatis를 사용하지 않으므로, 여기서는 간단하게 메모리 내(In-Memory) 데이터 저장소를 구현하여 DB 연동을 흉내 냅니다. 실제 DB 연동 시에는 JDBC 템플릿, JPA 등을 사용하여 구현합니다.
Java
package com.example.websquareexample.dao;
import com.example.websquareexample.dto.UserDto;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Repository
public class UserDao {
// 실제 DB 대신 사용할 In-Memory 저장소 (스레드 안전한 Map)
private static final Map<String, UserDto> inMemoryDb = new ConcurrentHashMap<>();
// 초기 데이터 추가 (테스트용)
static {
inMemoryDb.put("user01", createUserDto("user01", "김철수", "kim@example.com"));
inMemoryDb.put("user02", createUserDto("user02", "이영희", "lee@example.com"));
inMemoryDb.put("user03", createUserDto("user03", "박민수", "park@example.com"));
}
private static UserDto createUserDto(String userId, String userName, String userEmail) {
UserDto userDto = new UserDto();
userDto.setUserId(userId);
userDto.setUserName(userName);
userDto.setUserEmail(userEmail);
return userDto;
}
// 사용자 목록 조회 (SELECT)
public List<UserDto> selectUsers(String userName) {
System.out.println("[DAO] selectUsers 호출. 검색 조건: " + userName);
if (userName == null || userName.isEmpty()) {
return new ArrayList<>(inMemoryDb.values()); // 모든 사용자 반환
} else {
// 이름으로 필터링
return inMemoryDb.values().stream()
.filter(user -> user.getUserName().contains(userName))
.collect(Collectors.toList());
}
}
// 특정 사용자 ID로 조회 (SELECT by ID)
public UserDto selectUserById(String userId) {
System.out.println("[DAO] selectUserById 호출. ID: " + userId);
return inMemoryDb.get(userId);
}
// 사용자 정보 삽입 (INSERT)
public void insertUser(UserDto userDto) {
System.out.println("[DAO] insertUser 호출. 삽입할 사용자: " + userDto.toString());
if (inMemoryDb.containsKey(userDto.getUserId())) {
throw new RuntimeException("이미 존재하는 사용자 ID입니다: " + userDto.getUserId());
}
inMemoryDb.put(userDto.getUserId(), userDto);
System.out.println("[DAO] 사용자 삽입 완료. 현재 총 사용자 수: " + inMemoryDb.size());
}
// 사용자 정보 수정 (UPDATE)
public void updateUser(UserDto userDto) {
System.out.println("[DAO] updateUser 호출. 수정할 사용자: " + userDto.toString());
if (inMemoryDb.containsKey(userDto.getUserId())) {
inMemoryDb.put(userDto.getUserId(), userDto); // 기존 데이터 덮어쓰기
System.out.println("[DAO] 사용자 수정 완료.");
} else {
throw new RuntimeException("존재하지 않는 사용자 ID입니다: " + userDto.getUserId());
}
}
// 사용자 정보 삭제 (DELETE)
public void deleteUser(String userId) {
System.out.println("[DAO] deleteUser 호출. 삭제할 ID: " + userId);
if (inMemoryDb.containsKey(userId)) {
inMemoryDb.remove(userId);
System.out.println("[DAO] 사용자 삭제 완료. 현재 총 사용자 수: " + inMemoryDb.size());
} else {
throw new RuntimeException("존재하지 않는 사용자 ID입니다: " + userId);
}
}
}
소스 분석 및 흐름 파악 방법
위 예제 소스를 가지고 직접 IDE에서 프로젝트를 생성하여 실행해보면서 다음 순서로 분석하세요.
- 프로젝트 설정:
- Spring Boot 프로젝트를 생성합니다 (Java 11 이상 권장).
- spring-boot-starter-web 의존성을 pom.xml에 추가합니다.
- 위의 UserDto, UserController, UserService, UserDao 클래스 파일을 적절한 패키지(com.example.websquareexample.dto, .controller, .service, .dao 등)에 생성합니다.
- Spring Boot 애플리케이션을 실행합니다. (main 메서드가 있는 @SpringBootApplication 클래스 실행)
- WebSquare 파일 배치:
- WebSquare 프로젝트 내에 userList.xml과 userList.js 파일을 적절한 경로(예: ui/user/userList.xml, ui/user/userList.js)에 배치하고 WebSquare 서버를 통해 화면을 띄웁니다.
- 흐름 따라가기 (예: 사용자 목록 조회 - READ)
- WebSquare (userList.xml, userList.js):
- 브라우저에서 userList.xml 화면을 엽니다.
- btn_search 버튼의 onClick 이벤트 (scwin.btn_search_onClick)를 확인합니다.
- scwin.btn_search_onClick 함수 내에서 scwin.searchUserList()를 호출하는 것을 확인합니다.
- scwin.searchUserList 함수에서 WebSquare.request.execute() 호출을 찾습니다.
- 여기서 action : "/api/users", method : "GET", requestData : searchParam (dataMap_search의 userName)을 확인합니다.
- successCallback에서 dataList_userList.setJSON(res.responseJSON.userList)를 통해 서버 응답 중 userList라는 키의 값을 dataList_userList에 바인딩하는 것을 확인합니다.
- Spring Backend (UserController.java):
- UserController.java를 열고 @GetMapping("/users") 어노테이션이 붙은 getUsers 메서드를 찾습니다.
- 메서드의 파라미터 @RequestParam(required = false) String userName을 통해 WebSquare에서 보낸 userName이 여기에 매핑됨을 확인합니다.
- userService.getUsers(userName)를 호출하여 서비스 계층으로 제어권을 넘기는 것을 확인합니다.
- 반환된 userList를 Map에 userList라는 키로 담아 ResponseEntity로 반환하는 것을 확인합니다. (이것이 WebSquare successCallback의 res.responseJSON.userList에 해당)
- Spring Backend (UserService.java):
- UserService.java를 열고 getUsers(String userName) 메서드를 찾습니다.
- 여기서 userDao.selectUsers(userName)를 호출하여 DAO 계층으로 제어권을 넘기는 것을 확인합니다.
- Spring Backend (UserDao.java):
- UserDao.java를 열고 selectUsers(String userName) 메서드를 찾습니다.
- 여기서는 inMemoryDb에서 userName에 따라 필터링하여 UserDto 리스트를 반환하는 "데이터 조회" 로직을 확인합니다.
- WebSquare (userList.xml, userList.js):
- 흐름 따라가기 (예: 사용자 등록 - CREATE)
- WebSquare (userList.xml, userList.js):
- btn_new 클릭 -> popup_userReg 열림 -> dataMap_user에 아이디, 이름, 이메일 입력.
- btn_save 클릭 -> scwin.btn_save_onClick 호출.
- dataMap_user.getJSON()으로 입력된 데이터를 userData 변수에 저장.
- WebSquare.request.execute() 호출에서 action : "/api/users", method : "POST", requestData : userData를 확인합니다.
- Spring Backend (UserController.java):
- UserController.java를 열고 @PostMapping("/users") 어노테이션이 붙은 createUser 메서드를 찾습니다.
- 메서드의 파라미터 @RequestBody UserDto userDto를 통해 WebSquare에서 보낸 JSON 데이터가 UserDto 객체로 매핑되는 것을 확인합니다.
- userService.createUser(userDto)를 호출하여 서비스 계층으로 제어권을 넘기는 것을 확인합니다.
- 성공 시 HttpStatus.CREATED (201)를 반환하는 것을 확인합니다.
- Spring Backend (UserService.java):
- UserService.java를 열고 createUser(UserDto userDto) 메서드를 찾습니다.
- @Transactional 어노테이션으로 트랜잭션이 적용됨을 확인합니다.
- userDao.selectUserById()로 ID 중복 확인 후 userDao.insertUser(userDto)를 호출하여 DAO 계층으로 제어권을 넘기는 것을 확인합니다.
- Spring Backend (UserDao.java):
- UserDao.java를 열고 insertUser(UserDto userDto) 메서드를 찾습니다.
- 여기서는 inMemoryDb에 UserDto 객체를 추가하는 "데이터 삽입" 로직을 확인합니다.
- WebSquare (userList.xml, userList.js):
clo.omn 파일 관련
이 예제에서는 clo.omn 파일에 대한 실제 구현은 포함하지 않습니다. 하지만 만약 프로젝트에 해당 파일이 있다면:
- 파일 열기: 해당 파일을 텍스트 에디터로 열어서 내부 구조(XML, JSON 등)와 키워드를 확인합니다.
- 전역 검색: 백엔드 Java 코드(*.java)와 WebSquare JavaScript (*.js)에서 clo.omn 문자열을 전역 검색합니다. 어떤 코드에서 이 파일을 읽고 파싱하는지, 그리고 어떤 목적으로 사용하는지 (Clova, chatbot, OCR 관련 로직)를 찾아내면 파일의 역할을 정확히 파악할 수 있습니다.
이 간단한 예제를 통해 WebSquare와 백엔드 간의 데이터 흐름, 그리고 컨트롤러-서비스-DAO 계층 간의 역할 분담을 명확히 이해하실 수 있을 겁니다. 직접 코드를 실행하고 디버깅해보는 것이 가장 중요합니다!
'Spring( Framework )' 카테고리의 다른 글
| 학생 정보 관리 시스템 구축 예제 (1) | 2025.02.09 |
|---|---|
| Spring MVC Controller, Service, Mapper (MyBatis) 예제 (2) | 2025.02.06 |
| ObjectMapper 매퍼 = new ObjectMapper() (0) | 2025.01.24 |
| 패키지에 impl을 붙이는 이유 (1) | 2025.01.24 |
| REST 방식에서 쿼리스트링 Query string in REST method (1) | 2025.01.23 |