Back-end Develop

JDBC, MyBatis, Hibernate 비교하기: 실습과 환경 설정

gozer0 2024. 7. 18. 01:04

얼마 전에 Database와 연결하는거

실습해본다고

 

JDBC, MyBatis, Hibernate

 

모두 사용해본적이 있다.

그런데 JDBC 외에는

MyBatis와 Hibernate의 차이를 명확히 구분하기 어려웠다.

세가지 환경을 각각 설정하고 만들어 비교해보려고 한다.

 

JDBC (Java Database Connectivity)

JDBC는 Java 애플리케이션과 데이터베이스 간의 연결을 제공하는 Java의 표준 API

SQL 쿼리를 직접 작성하고 데이터베이스와의 연결을 수동으로 관리해야 한다.

 

특징:

  • 직접 SQL 작성: 쿼리를 직접 작성하고 실행
  • 직접 관리: 커넥션, 쿼리, 결과 처리를 직접 관리
  • 구현 복잡성: 복잡한 쿼리와 트랜잭션을 관리하기 어려움

장점:

  • 세밀한 제어: SQL 쿼리와 데이터베이스 연결을 직접 제어할 수 있다.
  • 성능: ORM 프레임워크를 사용하지 않기 때문에 성능이 좋을 수 있다.

단점:

  • 코드 반복: 반복적인 코드가 많아지며, 유지보수가 어려워질 수 있음
  • *보일러플레이트 코드: 많은 양의 보일러플레이트 코드가 필요하다.

**보일러플레이트(Boilerplate)**는 소프트웨어 개발에서 흔히 사용되는 용어로, 코드의 반복적인 부분을 의미함.

 

 

MyBatis

MyBatis는 SQL 쿼리를 XML 파일에 정의하고, 자바 객체와 SQL 매핑을 지원하는 퍼시스턴스 프레임워크.

SQL을 직접 작성하면서도 매핑을 통해 객체 지향적인 데이터 처리를 할 수 있다.

 

특징:

  • SQL 매핑: XML 파일 또는 어노테이션을 통해 SQL 쿼리를 정의하고, Java 객체와 매핑
  • 제어와 유연성: SQL을 직접 작성할 수 있어 복잡한 쿼리와 성능 튜닝에 유리
  • 동적 SQL: 조건에 따라 SQL 쿼리를 동적으로 생성할 수 있다.

장점:

  • SQL 제어: SQL 쿼리를 직접 작성할 수 있어 쿼리 성능을 최적화할 수 있다.
  • 매핑: SQL과 객체 매핑이 명확하게 정의되어 있어 유지보수가 용이하다.

단점:

  • SQL 코드 분산: SQL 코드가 XML 파일 또는 어노테이션에 분산되어 있어 관리가 어려울 수 있다.
  • 보일러플레이트 코드: 반복적인 매핑 코드가 필요하다.

 

Hibernate

Hibernate는 Java 객체를 관계형 데이터베이스에 매핑하는 ORM (Object-Relational Mapping) 프레임워크

데이터베이스와의 상호작용을 객체 지향적으로 처리하며, SQL 쿼리 작성이 필요 없거나 최소화된다.

 

특징:

  • 자동 매핑: Java 객체와 데이터베이스 테이블 간의 매핑을 자동으로 처리한다.
  • HQL (Hibernate Query Language): 객체 지향 쿼리 언어를 사용하여 데이터베이스 작업을 수행.
  • 캐싱: 1차 캐시와 2차 캐시를 지원하여 성능을 향상시킬 수 있다.

장점:

  • 객체 지향: 객체 지향 프로그래밍 패러다임을 사용하여 데이터베이스 작업을 간소화한다.
  • 자동화: 많은 작업을 자동으로 처리하여 개발 생산성을 높인다.
  • 캐싱: 성능 향상을 위한 캐싱 기능을 제공한다.

단점:

  • 학습 곡선: ORM의 개념과 Hibernate의 설정을 이해하는 데 시간이 걸릴 수 있다.
  • 성능: 자동 매핑과 캐싱으로 인한 성능 저하가 있을 수 있다.

 

 

 

 

 

이클립스에 Maven 프로젝트를 생성하여 이와 같이 구조를 구성했다.

RDBMS는 H2를 사용하였다.

CREATE TABLE s_emp(
id NUMBER(7) CONSTRAINT s_emp_id_nn NOT NULL,
name VARCHAR2(25) CONSTRAINT s_emp_name_nn NOT NULL,
start_date DATE,		
title VARCHAR2(25),				
dept_name VARCHAR2(25),	
salary NUMBER(11, 2),
CONSTRAINT s_emp_id_pk PRIMARY KEY (id)
);

INSERT INTO s_emp VALUES (1, '김가영', '2000-07-08', '개발자', '소프트웨어부서', 5000);
SELECT * FROM s_emp;

 

 

 

 

JDBC 실습

package com.rubypaper.persistence.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class EmployeeDAO {
	private Connection conn = null;
	private PreparedStatement stmt = null;
	private ResultSet rs = null;
	
	private String INSERT_EMP = 
		"insert into s_emp(id, name, start_date, title, dept_name, salary) " + "values((select nvl(max(id), 0) + 1 from s_emp), ?, ?, ?, ?, ?)";
	private String LIST_EMP = 
		"select id, name, start_date, title, dept_name, salary " + "from s_emp order by name";
	
	public void insertEmployee(EmployeeVO vo) {
		System.out.println("===> JDBC 기반으로 직원 등록 기능 처리");
		try {
			conn = getConnection();	
			
			stmt = conn.prepareStatement(INSERT_EMP);
			stmt.setString(1, vo.getName());
			stmt.setTimestamp(2, vo.getStartDate());
			stmt.setString(3, vo.getTitle());
			stmt.setString(4, vo.getDeptName());
			stmt.setDouble(5, vo.getSalary());
			stmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			close(stmt, conn);
		}
	}

	// 회원 목록 조회
	public List<EmployeeVO> getEmployeeList() {
		System.out.println("===> JDBC 기반으로 직원 목록 조회 기능 처리");
		List<EmployeeVO> employeeList = new ArrayList<EmployeeVO>();
		try {
			conn = getConnection();	
			stmt = conn.prepareStatement(LIST_EMP);
			rs = stmt.executeQuery();
			while(rs.next()){
				EmployeeVO employee = new EmployeeVO();
				employee.setId(rs.getLong("ID"));
				employee.setName(rs.getString("NAME"));
				employee.setStartDate(rs.getTimestamp("START_DATE"));
				employee.setTitle(rs.getString("TITLE"));
				employee.setDeptName(rs.getString("DEPT_NAME"));
				employee.setSalary(rs.getDouble("SALARY"));
				employeeList.add(employee);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			close(rs, stmt, conn);
		}
		return employeeList;
	}
		
	// 커넥션 획득
	public Connection getConnection() {
		try {
			Class.forName("org.h2.Driver");	
			String url = "jdbc:h2:tcp://localhost/~/test";
			conn = DriverManager.getConnection(url, "sa", "");	
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	}
		
	// 커넥션 종료
	public void close(ResultSet rs, Statement stmt, Connection conn) {
		try {
			if(rs != null)
				rs.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			rs = null;
		}
		
		try {
			if(stmt != null)
				stmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			stmt = null;
		}
		
		try {
			if(conn != null && !conn.isClosed())
				conn.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			conn = null;
		}
	}
	
	// 커넥션 종료
	public void close(Statement stmt, Connection conn) {
		try {
			if(stmt != null)
				stmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			stmt = null;
		}
		
		try {
			if(conn != null && !conn.isClosed())
				conn.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			conn = null;
		}
	}
}
package com.rubypaper.persistence.jdbc;

import java.sql.Timestamp;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class EmployeeVO {
	private Long id; 
	private String name; 
	private Timestamp startDate;
	private String title; 
	private String deptName; 
	private Double salary;
}

 

먼저, JDBC로 client를 실행해보면,
Hibernate처럼 자동으로 테이블을 만들어주지 않으니

테이블을 만들지 않고 실행하면 에러가 난다.

 

 

 

h2에서 table을 create해주고 실행시키면

성공적으로 값이 들어간다.

 

 

MyBatis 실습

package com.rubypaper.persistence.mybatis;

import java.io.IOException;
import java.io.Reader;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class EmployeeDAO {
	private SqlSession mybatis;
	
	public EmployeeDAO() {
		try {
			Reader reader = Resources.getResourceAsReader(
			"com/rubypaper/persistence/mybatis/sql-map-config.xml");
			SqlSessionFactory sessionFactory = 
				new SqlSessionFactoryBuilder().build(reader);
			mybatis = sessionFactory.openSession();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void insertEmployee(EmployeeVO vo) {
		System.out.println("===> MyBatis 기반으로 직원 등록 기능 처리");
		mybatis.insert("EmployeeDAO.insertEmployee", vo);
		mybatis.commit();
	}

	public List<EmployeeVO> getEmployeeList() {
		System.out.println("===> MyBatis 기반으로 직원 목록 조회 기능 처리");
		return mybatis.selectList("EmployeeDAO.getEmployeeList");
	}
}

 

package com.rubypaper.persistence.mybatis;

import java.sql.Timestamp;

import lombok.Data;

@Data
public class EmployeeVO {
	private Long id;
	private String name; 
	private Timestamp startDate; 
	private String title; 
	private String deptName; 
	private Double salary; 
}

 

여기서의 VO파일에는 Lombok 라이브러리를 추가 설치하여

@Data 어노테이션을 써서

@Getter, @Setter 어노테이션을 생략시켰다.

 

 

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

	<resultMap type="employee" id="employeeResult">
		<result property="id" column="ID"/>
		<result property="name" column="NAME"/>
		<result property="startDate" column="START_DATE"/>
		<result property="title" column="TITLE"/>
		<result property="deptName" column="DEPT_NAME"/>
		<result property="salary" column="SALARY"/>
	</resultMap>
	
	<insert id="insertEmployee" parameterType="employee">
		insert into s_emp(id, name, start_date, title, dept_name, salary) 
		values((select nvl(max(id), 0) + 1 from s_emp), 
		#{name}, 
		#{startDate}, 
		#{title}, 
		#{deptName}, 
		#{salary})
	</insert>
	
	<select id="getEmployeeList" resultMap="employeeResult">
		select id, name, start_date, title, dept_name, salary 
		from s_emp 
		order by name
	</select>
</mapper>

 

 

MyBatis를 쓰게 되면 XML 형식의 매퍼 파일을 작성하게 되는데,

MyBatis는 SQL 쿼리와 Java 객체 간의 매핑을 관리해주는 ORM 프레임워크이기 때문에

이 XML 파일은 데이터베이스와의 상호작용을 정의하며,

SQL 쿼리와 결과를 Java 객체에 매핑하는 방법을 설정하고

각 구문을 정의하기도 한다.

 

여기서는 EmployeeDAO라는 매퍼에 대한 설정이 포함되어 있고

table의 정보를 조회하는 Select와 입력하는 Insert가 정의되어 있다.

 

 

 

기존 table은 drop 시키고

MyBatis의 클라이언트를 실행시키면

 

역시 안된다. 

 

테이블을 직접 생성해주니 데이터가 들어간다.

 

 

Hibernate 실습

package com.rubypaper.persistence.hibernate;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class EmployeeDAO {
	private SessionFactory sessionFactory;
	private Session session;
	private Transaction transaction;

	public EmployeeDAO() {
		String config = "com/rubypaper/persistence/hibernate/hibernate.cfg.xml";
		sessionFactory = new Configuration().configure(config).buildSessionFactory();
		session = sessionFactory.openSession();
		transaction = session.getTransaction();
	}

	public void insertEmployee(EmployeeVO vo) {
		System.out.println("===> Hibernate 기반으로 직원 등록 기능 처리");
		try {
			transaction.begin();
			session.persist(vo);
			transaction.commit();
		} catch (Exception e) {
			transaction.rollback();
		}
	}

	public List<EmployeeVO> getEmployeeList() {
		System.out.println("===> Hibernate 기반으로 직원 목록 조회 기능 처리");
		String jpql = "select e from EmployeeVO e order by e.id";
		List<EmployeeVO> employeeList = session.createQuery(jpql).getResultList();
		return employeeList;
	}
}

 

package com.rubypaper.persistence.hibernate;

import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;

@Data
@Entity
@Table(name="S_EMP")
public class EmployeeVO {
	@Id
	private Long id;
	
	private String name;
	
	@Column(name="START_DATE")
	private Timestamp startDate;
	
	private String title;
	
	@Column(name="DEPT_NAME")	
	private String deptName;
	
	private Double salary;	
	
	private String email;
}

 

Hibernate의 VO 파일은 조금 다르다.

Hibernate에서 @Entity 클래스를 직접 정의함으로써,

데이터베이스의 테이블과 Java 객체 간의 매핑을 설정할 수 있다.

 

@Entity 로 이 클래스가 Hibernate의 엔티티임을 나타낸다.

@Table 테이블 명을 정의하는 어노테이션이다. 지정하지 않으면 defalt로 클래스명이 테이블명이 된다.

 

@Id 어노테이션은 이 필드가 테이블의 기본 키(primary key)를 나타낸다.

 

@Column  컬럼명을 지정해주는 어노테이션이다.

이 어노테이션은 직접 쓰지 않아도 컬럼으로 인식해준다.

 

 

 

H2에서 따로 테이블을 만들지 않아도 이렇게 잘 들어간다.

 

 

 

 

비교를 위한 실습 환경 설정

  1. JDBC 환경 설정:
    • JDBC를 사용하여 데이터베이스와 연결하고 SQL 쿼리를 직접 실행.
    • 커넥션, 쿼리 실행, 결과 처리 등을 직접 관리함.
  2. MyBatis 환경 설정:
    • MyBatis를 사용하여 SQL 쿼리를 XML 파일에 정의하고, 자바 객체와 매핑.
    • MyBatis의 매핑 기능과 동적 SQL 기능을 활용.
  3. Hibernate 환경 설정:
    • Hibernate를 사용하여 Java 객체와 데이터베이스 테이블 간의 매핑을 자동으로 처리.
    • HQL을 사용하여 데이터베이스 쿼리를 작성하고, Hibernate의 캐싱 기능을 활용.

 

 

이번 실습을 통해

JDBC, MyBatis, Hibernate의

각 기술이 제공하는 기능과 차이점을 명확히 비교할 수 있었다.

 

조만간은 Hibernate가 어떻게 테이블을 생성하고

DB에 접근할 수 있었는지

이것도 함 해볼 예정이다!

1차 캐시, 2차 캐시 등등의 개념들이 나와서 내용이 길어질 것 같다.

 

아주 재미진 실습이었당.  @^-^@

 

'Back-end Develop' 카테고리의 다른 글

.JAR, .WAR 차이  (0) 2024.07.30
JPA  (0) 2024.07.19