티스토리 뷰

MyBatis 사용해보기

MyBatis 소개

MyBatis

  • 관계형 데이터베이스 프로그래밍을 더 쉽게 할 수 있도록 도와주는 Java Framework

Database를 사용하는 이유

  • 스프링부트는 자바로 만들어진 프레임워크
  • 스프링부트 내에서 프로그래밍 언어를 사용할 때 Java 클래스(POJO)를 통해 데이터를 다룬다
Plain Old Java Object (POJO)
an ordinary Java object not bound by any special restriction and not requiring any classpath
used for increasing the readablility and reusability of a program
basically defines an entity

a POJO should not
extend prespecified classes (ex. public class Example extends javax.servlet.http.HttpServlet...)
implement prespecified interfaces (ex. public class Example implements javax.ejb.EntityBean...)
contain prespecified annotations (ex. @javax.persistence.Entity public class Example...)

<Example>
no restriction on access-modifiers of fields (can be private, default, protected, or public)
not necessary to include any constructor
// Employee POJO class to represent entity Employee
public class Employee {
	// default field
	String name;

	// private salary
	private double salary;

	//arg-constructor to initialize fields
	public Employee(String name, double salary){
		this.name = name;
		this.salary = salary;
	}

	// getter method for name
	public String getName(){
		return name;
	}

	// getter method for salary
	public Double getSalary(){
		return salary;
	}
}


<Working example of the POJO class>
Controllers interact with Business Logic
Business Logic interacts with POJO to access the Database
in example below, a database entity is represented by POJO (POJO has the same members as the database entity)

 

Java Beans
Beans are special type of POJOs.
restrictions on POJO to be a bean
- all JavaBeans are POJOs but not all POJOs are JavaBeans
- should implement Serializable interface
- fields should be private (to provide complete control on fields)
- fields should have getters or setters of both
- should have a no-arg constructor (default constructor)
- fields are accessed only by constructor or getter setters

POJO Java Bean
doesn't have special restrictions other than those foced by Java language a special POJO which haver some restrictions
doens't provide much control on members provides complete control on members
can implement Serializable interface should implement serializable interface
fields can bre accessed by their names fiels only have private visibility
may/may not have a no-arg constuctor should have a no-arg constructor
used when you don't want to give restriction on your members and give user complete access of your entity used when you want to provide user your entity but only some part of your entity
https://www.geeksforgeeks.org/pojo-vs-java-beans/#:~:text=POJO%20stands%20for%20Plain%20Old,re%2Dusability%20of%20a%20program.

 

  • Java 코드로 작성한 데이터들은 List, HashMap 등을 이용해 다루게 되며, memory에 저장된다
    • 컴퓨터의 RAM 메모리 상에 저장됨 > 스프링부트 어플리케이션이 종료되는 순간 초기화 됨
  • (같은 물리적 서버에 있지 않더라도) 여러 서버 프로세스가 같은 기능을 하면서 Data를 공유해야 한다 (스프링부트 어플리케이션을 여러 서버 프로세스에 걸쳐 띄울 경우)
    • 관계형 데이터베이스 서버는 스프링부트 애플리케이션과는 또다른 프로세스 상에서 작동
  •  스프링부트 어플리케이션에서 기능을 통해 사용할 데이터를 외부의 Database에 저장하고, 여러 어플리케이션 프로세스에서 공유하면서 사용 (persistent data - 데이터가 유지됨)
  • 데이터베이스와 스프링부트 어플리케이션은 (물리적으로 같은 서버에 있을 수도 있고 아닐 수도 있지만) 서로 다른 서버 프로세스상에 있다

MyBatis 소개

  • Java 함수(class나 interface에서 사용하는 함수들)를 SQL 선언문(statement)과 연결지어 사용 (JPA와의 가장 큰 차이점)
    • SQL 선언문을 Java 함수에 연결시키고, 함수의 결과가 데이터베이스의 테이블로 나오고, 이를 다시 Java object로 연결
  • Java 클래스를 이용하여 SQL 결과를 받거나 SQL 선언문에서 사용할 인자를 전달
  • 결과가 Table(Relation)의 형태로 돌아옴
select * from restaurants;

MyBatis 작동 원리

id name password birthday
0 anne 123 19991212
1 becca 1234 20001212

Table의 한 Row를 Java 기준으로 보았을 때 class의 형태로 정의해서 사용하고, DTO(Data Transfer Object)라고 부름

Table의 결과가 여러 Row일 경우, class의 여러 instance들이 collection 형태로 돌아옴

MyBatis에서는 선언문들을 xml 형태로 저장함 (xml 파일 안에 저장됨)

xml 파일을 java application을 선언하면서 불러들이고, 내용을 읽어서 미리 정의해놓은 자바 코드 상의 함수와 연결시킴

어떤 함수와 어떤 선언문이 연결될지도 xml상에서 정의

MyBatis로 Database 사용해보기

YAML (Yet Another Markup Language > Yaml Ain't Markup Language)

  • '핵심은 마크업이 아니라 데이터이다'를 강조하기 위해 이름이 바뀜

properties와 yml 비교

  • properties와 다르게 계층 구조를 잘 나타냄
  • 동일한 구성이 중복되는 것을 방지
  • properties에 비해 가독성이 좋음
example.jdbc.url=127.0.0.1
example.jdbc.port=3306
example.jdbc.user=user
example.jdbc.password=password
example:
 jdbc:
  url: 127.0.0.1
  port: 3306
  user: user
  password: password

 

application.yml 예시

spring:
 datasource:
  driver-class-name: com.mysql.cj.jdbc.Driver
  url: jdbc:mysql://127.0.0.1:3306//demo_schema
  username: demo_user
  password: password
  
mybatis:
 mapper-locations: "classpath:/mybatis/mappers/*.xml"
 configuration:
  map-underscore-to-camel-case: true

mapper-locations: resource를 root directory로 보고 mybatis 폴더 안의 mappers 폴더 안의 xml파일 모두를 찾는다

underscore = snake case

map-underscore-to-camel-case

  • (Java에서 작성한) camel case를 MyBatis에서 자동으로 snake case로 변환해서 전달해주도록
  • 데이터 자체가 바뀌는 것이 아니라, column의 이름 등 인자로써 활용되는 것들을 치환해준다는 의미
<참고>
Snake case: red_apple
Camel case: redApple
Pascal case: RedApple

DAO(Data Access Object)

  • springboot 기준으로 repository 역할. 데이터를 주고받는 기능을 해주기 위한 객체(클래스)들
  • 실제로 DB에 접근하는 객체. Persistence Layer(DB에 데이터를 CRUD하는 계층)
  • Service와 DB를 연결하는 역할

DTO(Data Transfer Object)

  • 데이터를 담기 위한 객체들
  • 계층간 데이터 교환을 위한 객체(Beans)
  • DB에서 데이터를 얻어 Service나 Controller등으로 보낼 때 사용하는 객체
  • DB의 데이터가 Presentation Logic Tier로 넘어오게 될 때에는 DTO의 모습으로 오고감
  • 로직을 갖고 있지 않는 순수한 데이터 객체이며, getter/setter 메서드만을 가짐

DAO, DTO, Entity의 비교 참고자료

https://gmlwjd9405.github.io/2018/12/25/difference-dao-dto-entity.html

Mapper

  • mapper.xml 파일에서 작성한 SQL 선언문들을 각 mapper interface의 함수명으로 작성하면 xml에 쓰여진 SQL문과 연결됨

DTO 예시 (PostDto.java)

//id int
//title varchar
//content varchar
//writer varchar
//board int

public class PostDto {
    private int id;
    private String title;
    private String content;
    private String writer;
    private int board;
}
  • DTO의 인자값들을 table의 column값과 이름을 동일하게 설정해야 함
  • board: joint table에서 사용하기 위한 board id

mapper interface 예시 (PostMapper.java)

mapper.xml파일에서 작성한 SQL문이 mapper namespace에 있는 함수와 연결지어짐

public interface PostMapper {
    int createPost(PostDto dto);
    PostDto readPost(int id);
    List<PostDto> readAllPost();
    int updatePost(PostDto dto);
    int deletePost(int id);
}
  • void 대신 int를 사용하는 이유: insert, update, delete문의 경우 int를 사용. insert, update, delete의 경우 결과로써 몇 개의 row가 조작되었는지를 결과로 돌려주기 때문에 "몇 개의 row인가"가 int로서 결과로 return되기 때문.

mapper.xml 예시 (post-mapper.xml)

<?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="dev.summer.mybatis.mapper.PostMapper">
    <insert id="createPost" parameterType="dev.summer.mybatis.dto.PostDto">
        insert into POST(title, content, writer, board)
        values (#{title}, #{content}, #{writer}, ${board})
        <!-- #{title} == 'title' -->
        <!-- ${title} == title -->
    </insert>
    <select id="readPost" parameterType="int" resultType="dev.summer.mybatis.dto.PostDto">
        select * from post where id = ${id}
    </select>
    <select id="readAllPost" resultType="dev.summer.mybatis.dto.PostDto">
        select * from post
    </select>
    <update id="updatePost" parameterType="dev.summer.mybatis.dto.PostDto">
        update post set title = #{title},
                        content = #{content},
                        writer = #{writer},
                        board = ${board}
        where id = ${id}
    </update>
    <delete id="deletePost" parameterType="dev.summer.mybatis.dto.PostDto">
        delete from post where id = ${id}
    </delete>
</mapper>
  • mapper namespace: 어떤 interface와 매치할지 결정하고
  • insert id: 어떤 함수와 아래 SQL문을 매치할지 결정함
  • parameterType: 인터페이스의 함수에서 받을 인자/parameter type을 의미
  • resultType: 함수의 결과가 돌려줘야 하는 인자의 type
    <insert id="createPost" parameterType="dev.summer.mybatis.dto.PostDto">
        insert into POST(title, content, writer, board)
        values (#{title}, #{content}, #{writer}, ${board})
        <!-- #{title} == 'title' -->
        <!-- ${title} == title -->
    </insert>
  • insert를 이용해 POST라는 table의 4개의 column(title, content, writer, board)에 values를 이용해서 각 값을 넣어준다 (SQL문과 동일하게 작성)
    • id의 경우 auto-increment이기 때문에 넣지 않음
    • #{변수명}: 문자열로 사용. parameterType으로 전달받은 dto Object의 멤버변수 중 일치하는 것을 가져옴
    • ${변수명}: $로 선언할 경우 인자의 값이 그대로 SQL 선언문의 일부로서 작성됨
    <select id="readPost" parameterType="int" resultType="dev.summer.mybatis.dto.PostDto">
        select * from post where id = ${id}
    </select>
    <select id="readAllPost" resultType="dev.summer.mybatis.dto.PostDto">
        select * from post
    </select>
  • select * from post where id = ${id}
    • 인자를 하나 가져올 때(readPost)는 database에서는 일반적으로 primary key를 사용함 > 예제의 경우 id
  • select * from post
    • result type의 결과물이 한 row가 아니라 table이기 때문 (readAllPost)

DAO 예시 (PostDao.java)

@Repository
public class PostDao {
    private final SqlSessionFactory sessionFactory;

    public PostDao(
            @Autowired SqlSessionFactory sessionFactory
    ){
        this.sessionFactory = sessionFactory;
    }

    public int createPost(PostDto dto){
        SqlSession session = sessionFactory.openSession();
        // 세션 오픈
        // openSession(autoCommit: true/false): default는 true. select를 제외한 테이블에 데이터에 영향을 주는 것들에 대한 행위를 자동으로 저장할지
        PostMapper mapper = session.getMapper(PostMapper.class);
        // session에서 PostMapper(선언한 인터페이스 타입)와 동일한 구현체를 달라(getMapper)고 하면 PostMapper 인터페이스의 구현체가 mapper에 주입됨
        // 이제 mapper는 PostMapper 인터페이스의 함수들을 사용 가능
        int rowAffected = mapper.createPost(dto);
        // 이 시점에서 데이터베이스와 통신 완료
        session.close();
        // 세션을 남기지 않는다(close)
        return rowAffected;
    }
}
  • PostDao: 실제로 mapper를 사용하여 통신을 하는 클래스
  • 데이터베이스와 소통하기 위해 사용한 session은 통신을 마친 후 닫아줌. 데이터베이스가 또다른 곳과 연결될 때 session을 다시 활용하기 위함.
  • session을 열고닫으면서 mapper를 새로 받는 이유: mapper instance는 thread-safe하지 않기 때문에, 요청이 두번 연속적으로 빠르게 들어왔을 때 데이터베이스 통신이 길어졌을 경우 서로 다른 함수의 결과가 서로에게 영향을 미칠 수 있음
    public int createPost(PostDto dto){
        try (SqlSession session = sessionFactory.openSession()) {
            PostMapper mapper = session.getMapper(PostMapper.class);
            return mapper.createPost(dto);
  • Java 일정 버전 이상에서는 위와 같이 쓸 수도 있음. try with resources 형태로, try문의 {} 안에서만 ()안의 변수(session)가 살아있도록 만들어주는 문법.
SqlSession
SqlSession은 하나의 Java interface
SqlSession interface를 통해 명령을 실행하고(execute commands), 매퍼를 가져오고(get mappers), manage transactions할 수 있다.
MyBatis에서는 SqlSessionFactory의 instance에 의해 SqlSession이 생성됨
SqlSessionFactory는 SqlSession들의 다양한 인스턴스를 만들기 위한 함수들을 포함하고 있음
SqlSessionFactory는 SqlSessionFactoryBuilder에 의해 생성되며,
SqlSessionFactoryBuilder는 xml파일, 어노테이션, 또는 직접 작성한 java configuration으로부터 SqlSessionFactory를 생성할 수 있음
세션을 한번 생성하면 매핑된 구문을 실행하거나 연결(connection)을 커밋 또는 롤백하기 위해 사용하고,
더 이상 필요하지 않은 상태가 되면 세션을 닫는다.
https://mybatis.org/mybatis-3/java-api.html

SQL 조건문을 mapper.xml에서 작성하는법

    <select id="readPostQuery" parameterType="dev.summer.mybatis.dto.PostDto" resultType="dev.summer.mybatis.dto.PostDto">
        select * from post
        where title = #{title}
        <if test="writer != null">
            and writer = #{writer}
        </if>
    </select>

Collection을 넣어서 작성하는 방법

    <insert id="createAllPost" parameterType="dev.summer.mybatis.dto.PostDto">
        insert into POST(title, content, writer, board)
        values
               <foreach collection="list" item="item" separator=",">
               (#{item.title}, #{item.content}, #{item.writer}, ${item.board})
               </foreach>
    </insert>
  • item: collection에 들어가 있는 item 들을 무엇으로 부를 것인가 (예시의 경우 item) > item.title
  • separator: list 각 아이템별로 separator를 기준으로 나뉘어진다 (예시의 경우 ,)
@Mapper
public interface PostMapper {
    int createPost(PostDto dto);
    int createAllPost(List<PostDto> dtoList);
    PostDto readPost(int id);
    List<PostDto> readAllPost();
    PostDto readPostQuery(PostDto dto);
    int updatePost(PostDto dto);
    int deletePost(int id);
}

board-mapper.xml

<?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="dev.summer.mybatis.mapper.BoardMapper">
    <insert id="createBoard"
            useGeneratedKeys="true"
            keyProperty="id"
            parameterType="dev.summer.mybatis.dto.BoardDto"
    >
        insert into board(name) values (#{name})
    </insert>
</mapper>
  • useGeneratedKeys가 true면 auto-generated key값을 이용하겠다는 뜻
  • keyProperty: 어떤 객체(?)에 key값을 지정해 줄 것인가

No MyBatis mapper was found in~ 오류 발생

원인1 오타: driver-class-name으로 작성해야 하는데 drive-class-name으로 작성

원인2: (원인은 알 수 없으나) 각 mapper interface에 @mapper 어노테이션을 달아주니 해결


ORM

관계형 데이터베이스의 한계

관계형 데이터베이스

  • primary key가 존재하며, 다른 테이블의 primary key를 가리키기 위한 foreign key가 존재
  • 관계형 데이터베이스에서 사용하는 자료의 형태가 객체 지향 관점에서 맞지 않아서 생기는 간극
    • 관계형 데이터베이스는 유연하게 데이터를 다루기 위한 용도라기 보다는 데이터를 저장하기 위한 용도
    • 이를 해결하기 위해 SQL이라는 언어를 사용해 데이터를 조회함

Object Relational Mapping

관계형 데이터를 객체로 표현하는 프로그래밍 기법

  • 하나의 프레임워크를 의미하는 것이 아닌, 어떤 프레임워크가 ORM을 구현하는 기술
  • 어떤 프레임워크를 사용하든 등장할 수 있는 개념

JPA Hibernate

Java Persistence API(JPA, 현재는 Jakarta Persistence로 이름을 바꿈)

  • JPA는 ORM 기술 자체를 구현하는 것은 아님
  • 이미 존재하는 Java 객체들에 데이터 상의 테이블에는 어떤 식으로 표현될지를 정의하기 위해 탄생한 어노테이션들 (@Entity, @DynamicUpdate, @Id, @GeneratedValue, ...)
  • JPA 자체는 관계형 데이터를 객체로 표기하는 기능 뿐
    • 자체적으로는 어떤 동작도 수행하지 않으며, Hibernate와 같은 ORM 툴이 JPA specification을 implement해서 데이터를 영속시킴
  • object-oriented domain models와 관계형 데이터베이스 시스템 사이의 다리 역할

Hibernate

  • Java에서 ORM을 사용할 때 사용하는 프레임워크
  • JPA를 이용해ORM을 구현한 기술 중 하나
  • JPA의 API를 활용해서 데이터베이스를 다룸
  • JPA에 대한 이해가 충분하면 (Hibernate와 같은) 직접 ORM 프레임워크를 만들 수 있다

JPA 활용하기

Entity 작성하기

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/demo_jpa_schema
    username: demo_jpa
    password: password
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect

ddl-auto: 테이블을 생성하고 제거하는 과정을 자동으로 하는 것에 대한 옵션. 상용 환경에서는 create로 사용하지 않음 (일반적으로는 none 사용)

show-sql: jpa가 작동하면서 실제로 실행하는 sql문을 보여줄지 말지에 대한 설정

dialect: mysql을 사용한다는 것을 알려줌

 

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class PostEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String content;
    private String writer;
}

jpa를 사용할 때는 primitive type보다는 클래스 기반의 object를 사용할 것 (ex. int 대신 Long)

@Id를 통해 JPA에 테이블의 primary key의 역할을 하는 변수를 알려줌

@GeneratedValue: id 생성하는 규칙을 설정하기 위한 어노테이션

 

@Entity
public class BoardEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @OneToMany(
            targetEntity = PostEntity.class,
            fetch = FetchType.LAZY,
            mappedBy = "boardEntity"
    )
    private List<PostEntity> postEntityList = new ArrayList<>();

mappedBy: postEntity의 ManyToOne 관계에 정의된 boardEntity와 연결하기 위해 작성

 

RDB???

fetchtype

 

BaseEntity: 모든 entity들이 가져야 할 속성에 대해 정의하고 싶을 때. @MappedSuperclass @EntityListeners


CRUD에 데이터베이스 적용

Controller: front gateway

Service: business logic

Repository: data access

 

데이터를 주고받기 위한 객체(Dto)

Entity를 Dto 대신 사용하는 것은 좋지 않음

Entity는 데이터의 표현만 나타내는 것이기도 하지만, 그 외에도 내부 객체를 가지고 있음. 즉, 데이터 자체만 전송하는 것 이상으로 전달하는 데이터가 있다는 점이 약점. 따라서 단순한 CRUD operation에서 Entity를 Dto로 그대로 사용하는 것은 좋은 선택이 아님.

 

사용자 <-> controller <-> Dao <-> Entity

 

'Java > project lion JSB the origin' 카테고리의 다른 글

Ch.7 Spring Boot 기능활용 (2)  (0) 2022.03.13
Ch.6 Spring Boot 기능활용(1)  (0) 2022.03.04
Ch.4 CRUD & Data (1)  (0) 2022.02.19
Ch.3 Spring Boot Basics (2)  (0) 2022.02.13
Ch.2 Spring Boot Basics(1)  (0) 2022.02.05
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함