JavaEE 기반 효율적인 중고차 평가 시스템 구축 실무

중고차 평가 시스템 개요

본 프로젝트는 JavaEE 기술을 기반으로 구축된 웹 애플리케이션으로, 중고차 가치의 과학적이고 정확한 평가를 구현합니다. JDK 1.7, Apache Tomcat 7.0.29 및 Eclipse J2EE 개발 환경을 채택하였으며, 차량 정보 입력, 지능형 평가 알고리즘, 데이터베이스 관리, 사용자 권한 제어, 리포트 통계 및 서드파티 API 연동 등의 핵심 기능을 포함하고 있습니다. 반응형 인터페이스 설계를 지원하여 PC와 모바일 환경 모두에서 활용 가능합니다.

전체 아키텍처 설계

시스템의 데이터 흐름을 먼저 파악해보겠습니다. 사용자가 웹페이지에서 차량 정보를 제출하면 데이터가 어떻게 처리되는지 살펴보세요.

graph TD
    A[클라이언트 브라우저] --> B[Nginx 리버스 프록시]
    B --> C[Tomcat 7.0.29 클러스터]
    C --> D{JavaEE 핵심 서비스}
    D --> E[(MySQL 데이터베이스)]
    D --> F[평가 알고리즘 엔진]
    D --> G[권한 관리 모듈]

이 아키텍처는 전형적인 3-tier 구조를 따르며, 각 계층이 독립적으로 동작하여 유지보수성을 확보합니다.

기술 스택 선택 이유

특정 기업 환경에서는 레거시 시스템과의 호환성이 가장 중요한 요소입니다. 금융, 공공기관, 제조업 분야에서 아직도 많은 시스템이 구형 운영체제와封闭式 네트워크 환경에서 실행되고 있으며, 이러한 조건에서는 안정성이 신기능보다 우선시됩니다.

컴포넌트 선택 이유
JDK 1.7 호환성이 뛰어나며 레거시 시스템에서 광범위하게 사용됨
Tomcat 7.0.29 Servlet 3.0 규격을 지원하고 경량화되어 있음
Eclipse J2EE 전통적인 JavaEE 프로젝트 지원이 뛰어나며 팀 친화적

물론 새로운 프로젝트에서는 Spring Boot와 Docker 조합을 권장하지만, 특정 레거시 환경에서는 "충분함"이 최선입니다.

개발 환경 구축

JDK 설치 과정

Windows 환경에서 JDK 1.7을 설치하는 방법은 다음과 같습니다:

  1. Oracle 공식 사이트에서 jdk-7u80-windows-x64.exe 파일 다운로드
  2. 설치 프로그램 실행 후 C:\Program Files\Java\jdk1.7.0_80 경로에 설치
  3. 환경 변수 JAVA_HOME을 C:\Program Files\Java\jdk1.7.0_80로 설정
  4. PATH 변수에 %JAVA_HOME%\bin 추가
  5. 명령 프롬프트에서 java -version 명령어로 1.7.0_80 버전 확인

Linux 환경에서는 다음 명령어로 설치합니다:

sudo mkdir -p /usr/local/java
sudo tar -zxvf jdk-7u80-linux-x64.tar.gz -C /usr/local/java/
sudo ln -s /usr/local/java/jdk1.7.0_80 /usr/local/java/jdk

/etc/profile 파일에 다음 내용을 추가합니다:

export JAVA_HOME=/usr/local/java/jdk
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

여러 JDK 버전 충돌 방지

하나의 머신에 여러 JDK가 설치된 경우, JAVA_HOME을 명시적으로 지정하여 원하시는 버전을 사용할 수 있습니다:

#!/bin/bash
export JAVA_HOME=/usr/local/java/jdk1.7.0_80
export PATH=$JAVA_HOME/bin:$PATH
exec java -jar myapp.jar "$@"

바이트코드 호환성과 클래스 로더 최적화

JDK 버전 간 호환성을 위해 컴파일 시 소스와 타겟 버전을 명시합니다:

javac -source 1.7 -target 1.7 -bootclasspath $JAVA_HOME/jre/lib/rt.jar MyService.java

동적 클래스 로딩을 구현하면 평가 알고리즘을 자주 업데이트해야 하는 경우에 유용합니다:

private String loadPath;

public AlgorithmClassLoader(String path) {
this.loadPath = path;
}

@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] bytecode = loadBytecode(className);
if (bytecode == null) {
throw new ClassNotFoundException();
}
return defineClass(className, bytecode, 0, bytecode.length);
}

private byte[] loadBytecode(String name) {
String filePath = loadPath + File.separatorChar +
name.replace('.', File.separatorChar) + ".class";
try (InputStream stream = new FileInputStream(filePath);
ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
int data;
while ((data = stream.read()) != -1) {
buffer.write(data);
}
return buffer.toByteArray();
} catch (IOException e) {
return null;
}
}
}

이 구현을 통해 JVM을 재시작하지 않고도 평가 알고리즘을 실시간으로 업데이트할 수 있습니다.

conf/server.xml 파일에서 포트 및 스레드 설정을 조정할 수 있습니다:


파라미터 설명
port 리스닝 포트 번호
connectionTimeout 연결 타임아웃 (밀리초)
URIEncoding GET 요청 인코딩 설정
maxThreads 최대 작업 스레드 수
acceptCount 대기열 최대 크기

bin/setenv.sh 파일에서 JVM 옵션을 설정합니다:

export JAVA_OPTS="-server \
-Xms512m -Xmx1024m \
-XX:PermSize=128m -XX:MaxPermSize=256m \
-XX:+UseConcMarkSweepGC \
-XX:+CMSClassUnloadingEnabled \
-Dfile.encoding=UTF-8"

Eclipse J2EE 버전에서 Dynamic Web Project를 생성하면 다음과 같은 디렉토리 구조가 생성됩니다:

MyCarApp/
├── src/
├── build/
├── WebContent/
│   ├── WEB-INF/
│   │   ├── web.xml
│   │   └── lib/
│   └── index.jsp

pom.xml에 필요한 의존성을 추가합니다:



javax.servlet
servlet-api
3.0.1
provided


mysql
mysql-connector-java
5.1.49


HTML5와 JavaScript를 활용한 반응형 폼을 구성합니다:



번호판:



제조사:

선택
현대
기아
제네시스



생산년도:


평가 요청

번호판 유효성 검사 로직:

function validateLicensePlate(plate) {
const pattern = /^[가-힣]{2}[0-9]{2}[가-힣0-9]{4}$/;
return pattern.test(plate);
}

document.getElementById('carForm').addEventListener('submit', function(event) {
const plate = document.getElementById('licensePlate').value;
if (!validateLicensePlate(plate)) {
alert("유효한 번호판을 입력해주세요!");
event.preventDefault();
}
});

중복 검사를 위한 Ajax 요청:

document.getElementById('licensePlate').addEventListener('blur', function() {
const plate = this.value;
if (validateLicensePlate(plate)) {
fetch('/api/check-duplicate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ licensePlate: plate })
})
.then(response => response.json())
.then(data => {
if (data.exists) {
showWarning("이미 평가된 차량입니다.");
}
});
}
});

전송된 데이터는 Servlet에서 처리됩니다:

public class CarSubmitServlet extends HttpServlet {
private CarService carService = new CarServiceImpl();

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

String licensePlate = request.getParameter("licensePlate");
String manufacturer = request.getParameter("manufacturer");
int manufactureYear = Integer.parseInt(request.getParameter("manufactureYear"));
double drivenDistance = Double.parseDouble(request.getParameter("drivenDistance"));

Car car = new Car();
car.setLicensePlate(licensePlate);
car.setManufacturer(manufacturer);
car.setManufactureYear(manufactureYear);
car.setDrivenDistance(drivenDistance);

boolean saved = carService.save(car);

request.setAttribute("success", saved);
request.getRequestDispatcher("/result.jsp").forward(request, response);
}
}

MySQL에서 핵심 데이터를 관리합니다:

CREATE TABLE cars (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
license_plate VARCHAR(20) UNIQUE NOT NULL,
manufacturer VARCHAR(50),
model VARCHAR(100),
manufacture_year INT,
driven_distance DOUBLE,
accident_history TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(128) NOT NULL,
salt_value VARCHAR(64),
role ENUM('ADMIN', 'USER') DEFAULT 'USER'
);

CREATE TABLE evaluations (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
car_id BIGINT,
evaluator_id BIGINT,
score DECIMAL(5,2),
suggested_price DECIMAL(10,2),
report_path VARCHAR(255),
FOREIGN KEY (car_id) REFERENCES cars(id),
FOREIGN KEY (evaluator_id) REFERENCES users(id)
);

JDBC와 DBCP 연결 풀을 이용한 데이터 접근:

public class CarDAO {
private DataSource connectionPool;

public boolean insert(Car car) throws SQLException {
String sql = "INSERT INTO cars (license_plate, manufacturer, model, manufacture_year, driven_distance) VALUES (?, ?, ?, ?, ?)";
try (Connection conn = connectionPool.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {

stmt.setString(1, car.getLicensePlate());
stmt.setString(2, car.getManufacturer());
stmt.setString(3, car.getModel());
stmt.setInt(4, car.getManufactureYear());
stmt.setDouble(5, car.getDrivenDistance());
return stmt.executeUpdate() > 0;
}
}
}

연결 풀 설정:

BasicDataSource pool = new BasicDataSource();
pool.setUrl("jdbc:mysql://localhost:3306/usedcar_db");
pool.setUsername("root");
pool.setPassword("password");
pool.setInitialSize(5);
pool.setMaxTotal(20);

인증 필터를 통한 접근 제어:

public class AuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession(false);

if (session == null || session.getAttribute("currentUser") == null) {
((HttpServletResponse)response).sendRedirect("/login.jsp");
} else {
chain.doFilter(request, response);
}
}
}

web.xml에 필터를 등록하여 전역 권한 제어를 적용합니다. 비밀번호는 MD5와 솔트 값을 함께 해싱하여 저장합니다.

다차원 점수 가중치 방식을 채택합니다:

평가 항목 가중치 데이터 소스
차령 30% 등록일
주행거리 25% OBD读取
사고이력 20% 서드파티 API
정비이력 15% 정비 기록
지역 시세 10% 실거래 데이터

Java 구현:

public EvaluationResult calculateValue(Car car) {
double ageScore = calculateAgeScore(car.getManufactureYear());
double distanceScore = calculateDistanceScore(car.getDrivenDistance());
double accidentScore = fetchAccidentData(car.getVehicleNumber());
double maintenanceScore = analyzeMaintenance(car.getMaintenanceRecords());
double marketScore = getMarketPrice(car.getModel(), car.getLocation());

double totalScore = ageScore * 0.30 +
distanceScore * 0.25 +
accidentScore * 0.20 +
maintenanceScore * 0.15 +
marketScore * 0.10;

double price = convertScoreToPrice(totalScore, car.getNewCarPrice());

return new EvaluationResult(totalScore, price);
}

REST API로 평가 결과를 제공합니다:

@WebServlet("/api/evaluate")
public class EvaluateServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(result);
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().write(json);
}
}

ECharts를 활용하여 레이더 차트로 각 항목별 점수를 시각화합니다.

JUnit을 활용한 단위 테스트:

@Test
public void testCalculateDepreciation_Standard() {
CarEvaluationService service = new CarEvaluationService();
double result = service.calculateDepreciation(30000000, 5, "standard");
assertEquals(18000000, result, 1000);
}

Postman으로 API 기능을 테스트하고 JaCoCo로覆盖率을 80% 이상 확보합니다.

WAR 파일 패키징:

mvn clean package -DskipTests

Nginx 리버스 프록시 설정:

upstream backend {
server localhost:8080 weight=3;
}

server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
}
}

마무리 및 발전 방향

본 프로젝트는 4개월여의 개발 기간을 거쳐 안정적인 시스템으로 완성되었습니다. 계층형 아키텍처로 유지보수성을 확보하였으며, Swagger를 통해 API 문서를 자동화하여 협업 효율성을 높였습니다.

개선 가능 영역:

  • 수동 배포 프로세스 → Jenkins CI/CD 도입
  • 모노리스 구조 → 마이크로서비스로 분할
  • 규칙 기반 평가 → TensorFlow 기반 AI 예측 모델 도입

향후 로드맵:

단계 기술 방향 기대 효과
1 Spring Boot 마이그레이션 개발 효율성 향상
2 Redis 캐시 도입 응답속도 40%+ 개선
3 모바일 앱 개발 사용자 접점 확대
4 차량联网 데이터 연동 평가 정확도 향상

최신 기술이 아닌 전통적인 JavaEE 기술로도 충분한 확장성과 안정성을 갖춘 엔터프라이즈 시스템을 구축할 수 있음을 입증했습니다. 설계와 구현이 탄탄하다면 어떤 기술 스택이든 신뢰할 수 있는 시스템을 만들 수 있습니다.

태그: javaee Servlet JSP JDBC Tomcat

6월 12일 00:12에 게시됨