
안녕하세요, 오늘은 JDBC, SQL Mapper, ORM에 대해 알아보았습니다.
JDBC의 한계와 발전
데이터를 생성한 프로그램의 실행이 종료되더라도
사라지지 않는 데이터의 특성, 영구히 저장
💡 직접 작성한 SQL 문의 실행 결과와 객체(Object)의 필드를 Mapping하여 **데이터를 객체화한다.**SQL Mapper
예시코드
// 1. XML OR Gradle에 Spring JDBC 의존성 추가
// 2. application.properties OR application.yml에 데이터베이스 연결 설정
@RestController
public class MemberController {
private final MemberRepository memberRepository;
public MemberController(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@GetMapping("/members")
public List<Member> getAllMembers() {
return memberRepository.getAllMembers();
}
}
// Member Object
public class Member {
private Long id;
private String name;
private int age;
// Getter and Setter methods
}
// Repository Anotation의 역할에 대해 공부해주세요.
@Repository
public class MemberRepository {
private final JdbcTemplate jdbcTemplate;
public MemberRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// Member 객체로 리턴한다.
public List<Member> getAllMembers() {
String query = "SELECT * FROM MEMBER";
return jdbcTemplate.query(query, (rs, rowNum) -> {
Member member = new Member ();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
member.setAge(rs.getInt("age"));
return member;
});
}
}
Query를 JAVA에서 XML로
MyBatis 장점
Mybatis는 동적 쿼리를 지원한다.
동적 쿼리(Dynamic SQL)란?
상황에 따라 분기처리(IF)를 통해 쿼리를 동적으로 만들어주는것.
MyBatis 예시코드
// User 클래스
public class User {
private Long id;
private String userName;
private String email;
// Getter and Setter methods
}
// Mapper Interface 생성 : SQL 쿼리와 매핑을 정의하는 인터페이스
public interface UserMapper {
User getUserById(Long id);
}
public class Main {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
try (Reader reader = Resources.getResourceAsReader(resource)) {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUserById(1L);
System.out.println(user.getId() + ", " + user.getUsername() + ", " + user.getEmail);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Mapper XML
SQL 쿼리와 객체 매핑을 정의한다.
<mapper namespace="com.example.UserMapper">
<select id="getUserById" resultType="com.example.User">
SELECT id, userName, email
FROM users
WHERE id = #{id}
</select>
</mapper>
MyBatis 설정 파일 작성
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="userName" value="userName" />
<property name="password" value="password" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/UserMapper.xml" />
</mappers>
</configuration>
public class Member {
// 필드들..
// 객체 안의 객체
private Team team;
} -> ERD?
public class Member extends Person {
// 필드들..
} -> ERD?
public class Member extends Person implements Workable {
// 필드들..
} -> ERD?
각각 지향하는 목적이 다르기 때문에 사용 방법과 표현 방식에 차이가 있다.
패러다임 불일치 문제를 해결하기 위해 ORM이 등장하게 된다.
💡 객체와 관계형 DB를 매핑한다. SQL Query가 아닌 메서드로 데이터 조작 ex) `SELECT * FROM USER;`→ user.findAll();ORM(Object Relational Mapping)
장점
단점
JPA
JPA는 JAVA ORM에 대한 API 표준 명세이다.
인터페이스로 구성되어 있으며 사용하려면 ORM 프레임워크를 사용하여야 합니다.

Person 추상 클래스를 상속받는 Member 클래스
abstract class Person {
private Long id;
private String name;
}
public class Member extends Person {
// private Long id;
// private String name;
private String nickName;
}
관계형 데이터베이스에는 상속이라는 개념이 없다.
Member 객체를 저장하고자 한다면?
INSERT INTO person 필드들..
INSERT INTO member 필드들..
총 Query를 두번 실행해야 한다.
Member 객체를 조회하려면?
SELECT p.*, m.*
FROM person p
JOIN member m
ON p.id = m.id;
JOIN문을 활용하여 조회하여야 한다.
만약 더 복잡한 상속관계가 있다면?
Query문 또한 점점 늘어나고 복잡하게 만들어진다.
이러한 상속에 의한 패러다임 문제는 JPA를 통해 쉽게 해결이 가능하다.
// INSERT INTO person 필드들.. , INSERT INTO member 필드들..
Member member = new Member();
jpa.persist(member); // JPA save()
// SELECT p.*, m.*
// FROM person p
// JOIN member m
// ON p.id = m.id;
Member member = jpa.find(Member.class, memberId); // JPA findById(memberId);
데이터 중심 설계
Team, Member 클래스
public class Member {
private Long id; // Member PK
private String nickName;
private Long teamId; // Team FK
}
public class Team {
private Long id; // Team PK
private String name;
}
Team team = member.getTeam(); // ??
RDB에서의 구조

만약 객체를 테이블 연관관계에 맞추어 모델링하면
저장, 조회는 편리하겠지만 객체지향의 특징을 사용하지 못합니다.
객체간의 연관성에 대한 문제는 해결되지만,
테이블 기준으로 본다면 저장하거나 조회하는것이 복잡해지게 된다.
ex) INSERT Query가 두번 실행되야한다.
public class Member {
private Long id;
private String nickName;
private Team team;
}
public class Team {
private Long id;
private String name;
}
JPA는 객체의 참조로 연관 관계 저장을 가능하도록 만들어준다.
member.setTeam(team);
jpa.persist(member); // INSERT 2 개.
Member member = jpa.find(Member.class, id);
Team team = member.getTeam();
개발자는 Member와 Team 관계를 설정하고 객체를 저장하기만 하면 된다.
객체내에서는 그래프 탐색을 자유롭게 할 수 있지만,
SQL을 다룰 때는 작성하는 쿼리에따라 객체의 그래프 탐색의 범위가 제한된다.
SQL 사용시 객체의 그래프 탐색 범위 제한

직접 구현한 DAO에서 객체를 가져온 경우
반환받은 엔티티를 신뢰하고 사용할 수 없다.
public void getStationByLineId(Long lineId) {
Line line = lineDAO.findLine(lineId); // findSection() findStation()
line.getSection().getStation(); // ?? 이게 신뢰할 수 있나? + 계속 작성해야하나?
}
엔티티가 SQL에 논리적으로 종속되어서 문제가 발생한다.
온전히 객체만으로 동작을 파악하기 힘들다.
Line과 연관된 모든 객체 그래프를 조회해서 메모리에 올려두는 것은 자원의 낭비가 심하다.
이러한 문제를 JPA에서는 지연로딩으로 해결한다.
public void lazyLoading(Long lineId) {
// Line만 조회하는 sql문이 실행된다.
Line line = lineDao.findById(lineId);
// 아래 코드가 실행되는 시점에 section 데이터를 조회한다.
Section section = line.getSection.get(0);
// 아래 코드가 실행되는 시점에 station 데이터를 조회한다.
String stationName = section.getStation().getName();
}
JPA에서는 연관된 객체를 즉시 조회할지 사용되는 시점에 지연로딩을 사용하여 조회할지
설정만으로 정의하여 성능 최적화에 이용하기도 한다.
ex) 10000번 조회하는데 10000회 연관객체를 함께 조회한다면?
Member 조회 할때, 항상 → 연관관계를 통해 Company 테이블을 조회한다면
즉시로딩 = 항상 Mebmer를 조회할때 Company같이 조회되도록 만드는것.
지연로딩 = Member는 Member를 조회할 때 , Company는 Company 조회할 때 쿼리가 실행되게 만듬
public class MemberDAO {
public Member findMemberById(Long memberId) {
// "SELECT * FROM MEMBER WHERE MEMBER_ID = ?
// JDBC API를 실행
return member;
}
}
동일성 비교
// 조회한 회원 비교하기
String memberId = "100";
Member member1 = memberDAO.findMemberById(memberId);
Member member2 = memberDAO.findMemberById(memberId);
member1 == member2; // false 다르다.
member1과 member2는 같은 DB Row에서 조회했지만
객체 측면에서 바라보면 둘은 다른 인스턴스이다.
JPA에서 동일성 비교
같은 트랜잭션 내에서 같은 아이디로 같은 객체가 조회되는 것을 보장한다.
// 조회한 회원 비교하기
String memberId = "100";
Member member1 = jpa.findMemberById(Member.class, memberId);
Member member2 = jpa.findMemberById(Member.class, memberId);
member1 == member2; // True 같다.
ORM을 사용함으로써 우리는
데이터베이스 서버가 없는 상태에서도 개발이 가능하도록 되었습니다.
→ ex) Entity, Contorller, Service, DTO, Repository
→ DB없이 개발이 가능하다.
과제 진행은 앞으로 모두 MySQL로 통일! → Database 연결 MySQL, MongoDB, Oracle
JDBC → SQL Mapper(JDBC Template, Mybatis) → ORM(JPA Hibernate)
모두 JDBC API를 내부적으로 사용하고 있다.
SQL Mapper, ORM 들은 내부적으로 Prepared Statement를 사용하고있다.
SQL Mapper, ORM 들은 Database Connection등 리소스 관리를 자동으로 해주고있다.
ORM은 컴파일 에러가 발생한다. 객체지향 패러다임에 대응할 수 있다.
ORM을 이용하면 우리는 개발에 집중할 수 있다!
Insert 문이 몇번 나가고, join 몇번 나가고, SQL 작성하고, 테스트하고
개발자가 할일 = 연관관계 설정 끝. JPA가 알아서 다 해준다. 생산성 올라간다.
오늘의 코드카타
class Solution {
public String solution(String bin1, String bin2) {
String answer = "";
int num1 = Integer.parseInt(bin1, 2);
int num2 = Integer.parseInt(bin2, 2);
int res = num1 + num2;
answer = Integer.toBinaryString(res);
return answer;
}
}
이진수 더하기