Spring Boot 기반 대학생 아르바이트 매칭 시스템 설계 및 구현

서론

디지털 기술의 급속한 발전과 함께 정보 관리 시스템은 다양한 산업 분야에서 핵심적인 역할을 수행하고 있다. 특히 대학생을 대상으로 한 아르바이트 수요가 증가함에 따라, 기존의 오프라인 방식의 채용 관리 방식은 정보의 비효율성과 처리 속도 저하 등의 문제를 야기하고 있다. 이러한 문제점을 해결하기 위해 본 연구에서는 Spring Boot 기반의 웹 기반 아르바이트 매칭 시스템을 개발하였다. 해당 시스템은 사용자 중심의 인터페이스와 안정적인 백엔드 아키텍처를 결합하여, 아르바이트 등록, 신청, 관리 프로세스를 통합적으로 처리할 수 있도록 설계되었다. 이를 통해 정보 접근성을 향상시키고, 관리자의 운영 효율을 극대화하는 것을 목표로 한다.

개발 환경 구성

  • 프로그래밍 언어: Java (JDK 1.8)
  • 프레임워크: Spring Boot
  • 데이터베이스: MySQL 5.7
  • 서버 환경: Apache Tomcat 7
  • 개발 도구: IntelliJ IDEA / Eclipse
  • 빌드 도구: Maven 3.3.9
  • 데이터베이스 관리: Navicat 11
  • 브라우저: Google Chrome

시스템 배포 후 관리자 페이지는 http://localhost:8080/[context]/admin/dist/index.html 경로로 접속 가능하며, 일반 사용자는 http://localhost:8080/[context]/front/dist/index.html 에서 서비스를 이용할 수 있다. 초기 관리자 계정은 ID: admin, PW: admin 으로 설정되어 있다.

기술 스택 설명

Java 언어 특성

Java는 강력한 객체 지향 프로그래밍(OOP) 언어로, 캡슐화, 상속, 다형성을 핵심 특징으로 한다. 플랫폼 독립성(JVM 기반)과 자동 메모리 관리(Garbage Collector) 덕분에 대규모 시스템 개발에 적합하다. 또한 예외 처리 메커니즘과 멀티스레딩 지원을 통해 안정적인 서버 애플리케이션 구축이 가능하다. 표준 라이브러리가 풍부하여 네트워크 통신, 파일 입출력, 데이터 처리 등 다양한 작업을 효과적으로 수행할 수 있다.

Spring Boot 프레임워크의 장점

전통적인 Spring 프레임워크의 복잡한 XML 설정 문제를 해결하기 위해 등장한 Spring Boot는 ‘Convention over Configuration’ 원칙을 따르며, 내장 서버와 자동 구성 기능을 제공한다. starter 의존성만 추가하면 데이터베이스 연결, 웹 서버 실행, 보안 설정 등이 자동으로 구성되어 개발 생산성이 크게 향상된다. 또한 마이크로서비스 아키텍처와의 호환성이 뛰어나 향후 확장에도 유리하다.

MySQL 데이터베이스 선택 이유

MySQL은 오픈소스 기반의 관계형 데이터베이스로, 뛰어난 성능과 안정성, 낮은 운영 비용으로 널리 사용되고 있다. ACID 특성을 준수하며 트랜잭션 처리가 가능하고, 인덱스 최적화를 통해 빠른 조회 속도를 제공한다. 사용자 권한 관리 및 데이터 암호화 기능을 통해 보안성도 확보할 수 있으며, JDBC 드라이버를 통해 Java 애플리케이션과의 연동이 간편하다.

핵심 기능 구현 코드

파일 업로드 및 다운로드 컨트롤러


@RestController
@RequestMapping("/file")
public class FileUploadController {

    @Autowired
    private ConfigService configService;

    // 파일 업로드 처리
    @PostMapping("/upload")
    public ResponseEntity<Map<String, Object>> uploadFile(
            @RequestParam("file") MultipartFile file,
            @RequestParam(required = false) String category) throws IOException {

        if (file.isEmpty()) {
            return ResponseEntity.badRequest()
                    .body(Map.of("error", "파일이 비어 있습니다."));
        }

        String extension = getFileExtension(file.getOriginalFilename());
        String fileName = System.currentTimeMillis() + "_" + new Random().nextInt(1000) + "." + extension;
        Path uploadPath = Paths.get("src/main/resources/static/upload");

        if (!Files.exists(uploadPath)) {
            Files.createDirectories(uploadPath);
        }

        Path targetPath = uploadPath.resolve(fileName);
        Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);

        // 특정 카테고리에 따른 설정 저장 로직
        if ("profile".equals(category)) {
            updateConfigValue("userProfileImage", fileName);
        }

        String fileUrl = "/upload/" + fileName;
        return ResponseEntity.ok(Map.of("fileName", fileName, "url", fileUrl));
    }

    // 파일 다운로드
    @GetMapping("/download")
    public ResponseEntity<byte[]> downloadFile(@RequestParam String fileName) throws IOException {
        Path filePath = Paths.get("src/main/resources/static/upload").resolve(fileName).normalize();
        byte[] data = Files.readAllBytes(filePath);
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(data);
    }

    private String getFileExtension(String filename) {
        return filename.substring(filename.lastIndexOf(".") + 1);
    }

    private void updateConfigValue(String key, String value) {
        ConfigEntity config = configService.selectOne(new EntityWrapper<ConfigEntity>().eq("name", key));
        if (config == null) {
            config = new ConfigEntity().setName(key).setValue(value);
            configService.insert(config);
        } else {
            config.setValue(value);
            configService.updateById(config);
        }
    }
}
    

게시판 및 커뮤니티 기능


@RestController
@RequestMapping("/post")
public class PostController {

    @Autowired
    private PostService postService;

    // 게시글 목록 조회 (페이지네이션 포함)
    @GetMapping("/list")
    public ResultResponse getPostList(
            @RequestParam Map<String, Object> params,
            HttpServletRequest request) {

        Long userId = (Long) request.getSession().getAttribute("userId");
        String role = (String) request.getSession().getAttribute("role");

        EntityWrapper<PostEntity> wrapper = new EntityWrapper<>();
        if (!"admin".equals(role)) {
            wrapper.eq("user_id", userId);
        }

        // 검색 조건 적용
        MPUtil.applyFilter(wrapper, params, "title", "content");
        MPUtil.applyDateRange(wrapper, params, "create_time");

        PageUtils pageData = postService.queryPage(params, wrapper.orderBy("create_time", false));
        return ResultResponse.ok("조회 성공", pageData);
    }

    // 게시글 상세 조회
    @GetMapping("/detail/{id}")
    public ResultResponse getDetail(@PathVariable Long id) {
        PostEntity post = postService.selectById(id);
        if (post == null) {
            return ResultResponse.error("게시글을 찾을 수 없습니다.");
        }
        return ResultResponse.ok(post);
    }

    // 댓글 계층형 구조 처리
    @GetMapping("/thread/{parentId}")
    public ResultResponse getThreadReplies(@PathVariable Long parentId) {
        List<PostEntity> replies = buildReplyTree(parentId);
        return ResultResponse.ok(replies);
    }

    private List<PostEntity> buildReplyTree(Long parentId) {
        List<PostEntity> children = postService.selectList(
                new EntityWrapper<PostEntity>().eq("parent_id", parentId));
        for (PostEntity child : children) {
            List<PostEntity> subReplies = buildReplyTree(child.getId());
            child.setChildren(subReplies);
        }
        return children;
    }

    // 새로운 게시글 작성
    @PostMapping("/save")
    public ResultResponse savePost(@RequestBody PostEntity post, HttpServletRequest request) {
        post.setId(IdWorker.getId());
        post.setUserId((Long) request.getSession().getAttribute("userId"));
        post.setCreateTime(new Date());
        postService.insert(post);
        return ResultResponse.ok("등록 완료");
    }

    // 삭제 (배치 지원)
    @PostMapping("/delete")
    public ResultResponse deletePosts(@RequestBody Long[] ids) {
        postService.deleteBatchIds(Arrays.asList(ids));
        return ResultResponse.ok("삭제 완료");
    }
}
    

시스템 테스트 전략

시스템 구현 후에는 단위 테스트(JUnit), 통합 테스트(Spring TestContext), 그리고 UI 기반의 종단 간(E2E) 테스트를 수행하였다. 주요 기능별로 입력 유효성 검사, 세션 관리, 권한 체크, 데이터 무결성 등을 검증하였으며, 부하 테스트 도구(JMeter)를 활용하여 동시 접속 상황에서도 안정적인 응답을 유지함을 확인하였다. 결함 관리는 Git 기반 이슈 트래커를 통해 체계적으로 수행되었으며, 요구사항 대비 테스트 커버리지는 92% 이상 달성되었다.

결론 및 기대 효과

본 시스템은 Java 기반의 객체 지향 설계를 통해 모듈화된 구조를 갖추었으며, Spring Boot의 자동 구성 기능을 활용해 개발 시간을 단축하고 유지보수성을 높였다. MySQL을 통한 데이터 일관성 확보와 RESTful API 기반의 프론트엔드 통신은 시스템의 확장성과 안정성을 동시에 만족시킨다. 사용자 입장에서는 직관적인 인터페이스를 통해 아르바이트 정보를 실시간으로 탐색하고 신청할 수 있으며, 관리자는 중앙 집중식 대시보드를 통해 전체 현황을 모니터링할 수 있다. 향후에는 AI 기반의 추천 알고리즘 도입 및 모바일 앱 연동을 통해 서비스를 고도화할 계획이다.

태그: Spring Boot MySQL java RESTful API jpa

6월 13일 23:03에 게시됨