주노 님의 블로그

20240921 본캠프 51일차 TIL 본문

TIL

20240921 본캠프 51일차 TIL

juno0432 2024. 9. 26. 21:00

오늘 해야할 일 ✔️ 🔺 ❌

 

✔️JPA 3주차까지!

 


jpa강의 : 추후 수정예정

더보기

H2데이터베이스의 사용방식

  • Sever Mode : 직접 엔진을 설치하는 모드, 외부에서 db엔진을 구동하기때문에 애플리케이션을 종료해도 데이터가 사라지지 않는다 >> 배포용도
  • In-Memory Mode : 엔진을 설치하지않고 애플리케이션 내부의 엔진을 사용함, 어플리케이션을 종료하면 db도 같이 날라간다. >> 테스트 용도
  • Embedde Mode : 애플리케이션 내부의 엔진을 사용하는 방식이다. 다만 데이터가 외부에 저장되므로, 애플리케이션을 종료해도 데이터가 사라지지않는다. >> 개발용도

트랜잭션

  • 데이터베이스의 상태를 변화시키기 위해서 수행하는 작업의 단위
  • 데이터를 생성 수정 삭제후 만들어지는 결과를 하나의 트랜잭션으로 관리될수있으며, 그사이 문제가 생길 경우 롤백 가능함

트랜잭션의 3가지 특징

  • 원자성
    1. 트랜잭션이 데이터베이스에 모두 반영되던가, 아니면 반영되지 않아야 한다.
  • 일관성
    1. 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다.
    2. 트랜잭션이 진행되는동안 데이터가 변경되어도, 업데이트된 트랜잭션이아닌 처음 트랜잭션을 진행하기 이전의 데이터로 진행해야한다.
  • 독립성
    1. 두개의 트랜잭션이 동시에 실행되고 있을경우 어떤 트랜잭션이라도 다른 트랜잭션 연산에 끼어들면 안된다.

트랜잭션 관리와 보안

  • 트랜잭션의 독립성을 보장하기 위해, begin, commit, rollback로 트랜잭션을 관리한다
    begin : 트랜잭션을 시작하는 연산이다
    commit : 하나의 트랜잭션이 끝났다는것을 알려주기 위한 연산이다
    rollback : 하나의 트랜잭션 처리가 비정상적으로 종료되어, 원자성이 깨질경우 트랜잭션이 실행되기 전으로 돌아간다.
  • grant와 revoke로 권한을 설정한다
  • 트랜잭션 격리 수준 (낮은 순서)
    READ COMMITTED : 트랜잭션이 끝나지 않은 데이터를 다른 트랜잭션이 읽을 수 있음
    DIRTY READ 발생가능 >> 변경되지 않은 다른 데이터를 읽을 수 있음

    READ UNCOMMITTED : 트랜잭션이 완료된 데이터만 다른  트랜잭션이 읽을 수 있음
    Non-Repeatable Read가 발생할 수 있음 >> 트랜잭션 b가 수정하고 트랜잭션 a가 다시 읽었을때 다른 값이 반환될 수 있음

    REPEATABLE READ : 처음 데이터를 읽었을때와 같은 데이터가 보장됨
    Phantom Read 발생가능 >> 트랜잭션 b가 새로운 데이터를 삽입하면, a가 다시 읽었을때 새로운 레코드가 생길 수 있음

    SERIALIZABLE : 트랜잭션이 완전히 순차적으로 실행할 수 있음, 모든 동시성문제가 해결됨
    성능저하가 발생할 수 있음

데이터베이스 드라이버

  • 애플리케이션과 데이터베이스간의 통신을 중개한다.
  • 드라이버의 종류 : oracle, mysql, postgreSQL등 각자에 맞는 드라이버가 존재한다
  • 드라이버의 동작 방식
    1. 연결 초기화 : 드라이버에 연결을 요청 및 로그인등을 수행함
    2. SQL전송 및 실행 : 애플리케이션에서 받은 sql명령을 데이터베이스의 형태로 변경 및 전송한다
    3. 결과 처리 : 데이터베이스에서 결과를 보내면 드라이버는 애플리케이션의 형태로 변경 및 전송한다. 
    4. 연결 종료 : 작업이 완료되면 연결을 종료한다
  • JDBC 드라이버는?
    1. 연결 객체 생성으로 쿼리를 받을수있는 상태로 만들어주고.
    2. statement를 생성하여 쿼리를 요청하게 해주고.
    3. ResultSet을 생성해 쿼리 결과를 받아올 수 있게 해준다.

spring에서 jdbc다루기

  • spring boot starter jdbc를 gradle에서 임포트를 하면된다
    1. jdbc api 지원, dataasource(연결하기 위한 기본 설정구성), jdbctemplate등을 지원해준다.
  • 순서
    1. 드라이버 연결을 진행하기위한 연결정보를 설정한다
    		String url = "jdbc:h2:mem:test"; 	// spring.datasource.url
    		String username = "sa";

    2. getConnection을 사용해 연결 을 시도한다
    try (Connection connection = DriverManager.getConnection(url, username, null)) {

    try- with-resource형식으로 구현함
    3.statement를 생성한다
    // 테이블 생성 (statement 생성)
    				String creatSql = "CREATE TABLE USERS (id SERIAL, username varchar(255))";
    				try (PreparedStatement statement = connection.prepareStatement(creatSql)) {
    					statement.execute();
    				}
    
    				// 데이터 추가 (statement 생성)
    				String insertSql = "INSERT INTO USERS (username) VALUES ('teasun kim')";
    				try (PreparedStatement statement = connection.prepareStatement(insertSql)) {
    					statement.execute();
    				}
    
    				// 데이터 조회 (statement 생성 후 rs = resultSet 수신 & next() 조회)
    				String selectSql = "SELECT * FROM USERS";
    				try (PreparedStatement statement = connection.prepareStatement(selectSql)) {
    					var rs = statement.executeQuery();
    					while (rs.next()) {
    						System.out.printf("%d, %s", rs.getInt("id"), rs.getString("username"));
    					}
    				}
    createStatement vs PreparedStatementcreateStatement
    createStatement : 쿼리를 실행할수 있는 객체를 생성함
    구문분석 > 치환 > 실행 > 추출 과정을 수행하며, 어떤 sql문인지 이해하기 쉬움, 구문분석 과정때문에 상대적으로 효율이 떨어짐.
    PreparedStatement : 쿼리를 저장할수있는 객체를 생성함 재사용.
    구문분석 과정을 캐싱하여, 나머지 3단계만 거치므로, 성능이 향상되며, sql인젝션을 방어할 수 있음

    statement.execute();

    execute메서드를 통해 실행한다.
    var rs = statement.executeQuery();
    
    ResultSet executeQuery() throws SQLException;

    쿼리의 결과는 resultset으로 받는다.
    while (rs.next()) {
        System.out.printf("%d, %s", rs.getInt("id"), rs.getString("username"));
    }

    또한 각 컬럼을 rs.next를 사용하여 차례대로 받아온다.

jdbc Template

  • jdbc로 직접 sql을 작성하면 생기는 문제
    1. 중복 코드 발생가능
    2. checked Exception처리해야함
    connection, statement등 자원 관리를 따로 해줘야함

  • 위의 문제를 해결하기위해 Persistence 프레임워크가 생겼음
    종류
    1. SQL Mapper : JDBC Template, MyBatis
    2. ORM : JPA, Hibernate

  • SQL 매퍼란?
    SQL과 객체를 매핑하여 데이터를 객체화함

  • 실습
    public void createTable() {
        jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS users (id SERIAL, name VARCHAR(255))");
    }
    
    // 사용자 추가 (Create)
    public void insertUser(String name) {
        jdbcTemplate.update("INSERT INTO users (name) VALUES (?)", name);
    }
    
    // 사용자 ID로 User 조회 (Read)
    public User findUserById(Long id) {
        return jdbcTemplate.queryForObject(
            "SELECT * FROM users WHERE id = ?",
            new UserRowMapper(), // 이자리에 매퍼를 생성자로 넣어주면 됨
            id
        );
    }
    
    // 사용자 이름 변경 (Update)
    public void updateUser(Long id, String newName) {
        jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", newName, id);
    }
    
    // 사용자 삭제 (Delete)
    public void deleteUser(Long id) {
        jdbcTemplate.update("DELETE FROM users WHERE id = ?", id);
    }
    생성, 수정, 삭제는 update, 조회는 queryForObject를
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        var user = new User();
        user.setId(rs.getInt("ID"));
        user.setName(rs.getString("NAME"));
        return user;
    }


    반환 결과는 위를 반환하도록 한다.

    rowmapper를 사용하면 구현의 중복이 없어 가독성이 향상한다.

쿼리 파일 만들기

  • 별도의 쿼리 파일을 만들어 sql을 관리함
  • 원래의 row mapper를 사용하던것을 mybatis를 사용함
  • sql쿼리들을 xml파일을 만들어 sql을 분리함
  • 장점
    connection을 줄이고 실제 sql문을 사용하므로, 빠른 개발이 가능하게 한다.

mybatis

  • 동작원리
    출처 : 내일배움캠프 jpa심화 3주차 강의자료
    실행시 한번 실행되는 프로세스
    (1) 응용 프로그램이 SqlSessionFactoryBuilder를 위해 SqlSessionFactory를 빌드요청을 함
    (2) SqlSessionFactoryBuilder는 SqlSessionFactory를 생성하기 위해 설정파일을 읽어, 설정파일대로 SessionFactory를 생성함.
    (3) SqlSessionFactoryBuilder는 Mybatis 구성 파일의 정의에 따라 SqlSessionFactory를 생성합니다.

    각 요청시마다 실행되는 프로세스
    (4) 요청이 들어옴
    (5) 들어온 요청대로 SqlSessionFactoryBuilder를 사용하여 sqlSession을 가져옴
    (6) SqlSessionFactory는 SqlSession을 생성하고 이를 애플리케이션에 반환함.
    (7) 응용 프로그램이 SqlSession에서 매퍼 인터페이스의 구현 개체를 가져옴, 그 기반으로 mapping query를 찾을 수 있음
    (8) 응용 프로그램이 매퍼 인터페이스 메서드를 호출함.
    (9) 매퍼 인터페이스의 구현 개체가 SqlSession 메서드를 호출하고 SQL 실행을 요청함.
    (10) SqlSession은 매핑 파일에서 실행할 SQL을 가져와 SQL을 실행합니다.

    mapper interface와 mapping file만 구성해준다면, 나머지는 mybatis가 구현해준다.

  • mapper 인터페이스를 정의하는 방법
    1. Dao 클래스 정의
    // UserDao.java
    import org.apache.ibatis.session.SqlSession;
    import org.springframework.stereotype.Component;
    
    import com.thesun4sky.querymapper.domain.User;
    
    @Component
    public class UserDao {
    
      // SqlSession 멤버 변수로 사용하며 쿼리파일 수행 요청
      private final SqlSession sqlSession;
    
      public UserDao(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
      }
    
      public User selectUserById(long id) {
        return this.sqlSession.selectOne("selectUserById", id);
      }
    
    }
     위처럼 sqlSesstion을 사용하고, selectOne 메서드를 직접 호출하고, 첫번째인자로 쿼리파라미터를 지정해놓으면 xml파일에 정의된 방법대로 실행가능하다.

    2. mapper Interface 정의
    // UserMapper.java
    @Mapper
    public interface UserMapper {
    
      User selectUserById(@Param("id") Long id);
    
    }
     메서드명이 xml파일의 쿼리에 들어있는 값과 매칭이 되는 방식
    @Mapper어노테이션을 받아옴
    <!-- UserMapper.xml -->
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.thesun4sky.querymapper.mapper.UserMapper">
        <select id="selectUserById" resultType="User">
            select id, name from users where id = #{id}
        </select>
    </mapper>
     mapper 태그를 사용하여 묶어줄 파일을 알려주고
    select 태그를 사용하여 id는 사용될 메서드와 매칭을 시켜주면 아래의 쿼리를 실행한다.

  • 구현해보자
    User.java >> Serializable을 상속받아야 객체를 만들어서 객체를 받을수있다.

    dao
    @Component
    public class UserDao {
    
      private final SqlSession sqlSession;
    
      public UserDao(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
      }
    
      public User selectUserById(long id) {
        return this.sqlSession.selectOne("selectUserById", id);
      }
    
    }

    mapper

    @Mapper
    public interface UserMapper {
    
      User selectUserById(@Param("id") Long id);
    
    }

    둘다 같은 방식이다.

ORM

  • 쿼리매퍼의 DB의존성 및 중복 쿼리 문제로 ORM이 만들어짐

    문제점
    1. 상속 문제
    객체 : 객체간의 멤버변수나 상속관계를 맺을 수 있음
    RDB : 상속관계가 없고 모두 독립적임
    해결방법 : 매핑정보에 @OneToMany등 상속 정보를 넣어줌

    2. 관계 문제
    객체 : 참조를 통해 관계를 가지며 방향을 가짐
    RDB : 외래키를 통해 설정하며 JOIN으로 조회시만 참조가 가능함
    해결방법 : 매핑 정보에 방향 정보를 넣어줌 (@JoinColumn, @MappedBy)

    3. 탐색 문제
    객체 : 참조를 통해 다른 객체로 순차적 탐색이 가능함
    RDB : 탐색시 참조하는 만큼 추가 쿼리나 JOIN이 발생함
    해결 방법 : 매핑/조회 정보로 참조탐색 시점을 관리한다(@fetchType, fetchJoin)

    4. 밀도문제
    객체 : 멤버 객체크기가 매우 클 수 있음
    rdb : 기본 데이터 타입만 존재함
    해결 방법 : 크기가 큰 멤버 객체는 테이블을 분리하여 상속으로 처리함(@embedded)

    5. 식별성 문제
    객체 : 객체의 hashCode 또는 정의한 equals() 메소드를 통해 식별
    RDB : PK로만 식별
    해결방법 : PK를 객체 ID로 설정하고 EntityManager은 해당 값으로 개겣를 식별하여 관리함

    해결책
    1. 영속성 컨텍스트(1차 캐시)를 활용한 쓰기지연
    영속성이란? 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성을 말하며, 데이터를 db에 영구 저장함으로써 데이터에 영속성을 부여함 

    영속성의 4가지 상태
    비영속 : 엔티티가 생성된 상태
    영속 : 엔티티가 영속성 컨텍스트에 저장되어, 영속성 컨텍스트가 관리할 수 있는 상태
    준영속 : 영속성 컨텍스트에 저장되어있다가 분리된 상태
    삭제 : 영속성 컨텍스트와 데이터베이스에서 삭제된 상태

    new > (비영속상태) > persist(),merge() > (영속성 컨텍스트에 저장된 상태) > flush()  > (DB에 쿼리가 전송된 상태) >  commit() > (DB에 쿼리가 반영된 상태)

    쓰기지연이 발생하는 시점
    1. flush() 동작이 발생하기 전까지 최적화 함
    2. flush() 동작으로 전송된 쿼리는 최적화 되지않고, commit()로 반영만 가능함

    쓰기지연의 효과
    1. 여러개의 객체를 생성할 경우 모아서 한번에 쿼리를 전송한다.
    2. 영속성 상태의 객체가 생성 및 수정이 여러번 일어나더라도 해당 트랜잭션 종료시 쿼리는 한번만 전송함.
    3. 영속성 상태에서 객체가 생성되었다 삭제되었다면 실제 DB에는 아무 동작이 전송되지 않을 수 있음
    4. 여러동작이 발생하더라도 쿼리는 트랜잭션당 최적화되어 최종으로 변경된 최소쿼리만 날라감.

ORM을 사용하는 방법

  • JpaRepository를 사용한다.
    repository : 기본 기능을 직접 구현해야한다
    JpaRepository :  기본 메서드를 거의 구현되어 사용된 기능만 사용한다(@NotRepositoryBean) >> 빈 생성을 막다가 상속을 받으면 생성됨
  • 테이블 매핑 어노테이션
    • 테이블 관련 어노테이션 종류
      @Entity : entity임을 명시
      @Table : RDB의 테이블 이름
      @Id : 기본키를 매핑함
      @GenerateValue : 기본키의 생성 방법을 매핑하는 어노테이션 (table, sequence, identity등을 지정가능)
      @column : 생략가능 컬럼값을 정의할수있음 (unique, nullable, length 등등 제약을 지정)
      @temporal : date, calendar등을 지정함
      @Transient : 컬럼으로 매핑되지않고싶은 멤버변수를 지정, 자바 오브젝트에서만 사용하고 싶을때.

    • 필드 타입 종류
      @column : 생략가능 컬럼값을 정의할수있음 (unique, nullable, length 등등 제약을 지정)  
      @Enumerated :  enum매핑 용도로 사용되며 , @Enumerated(EnumType.STRING)으로 사용권장 >> 기본 타입은 1,2,3등 순서로 지정됨
      @Embeddable : 복합값 타입으로 사용할 클래스를 지정
      @Embedded : 특정 필드에 복합 값 객체를 적용하는데 사용
      @AttirbuteOverrides : 복합 값 객체가 여러개 있을 경우 해당 객체의 필드를 어떻게 매핑할지 명시하기 위해서
      @AttributeOverride : 복합 값 필드 이름을 재정의하는데 사용
      @NoArgsConstructor(access = AccessLevel.PROTECTED)
      @Entity
      @Setter
      @Getter
      public class Member{
      
          @Id @GeneratedValue
          @Column(name = "MEMBER_ID")
          private Long id;
      
      
          private String name;
      
          @Embedded
          @AttributeOverrides({
                  @AttributeOverride(name = "city", column = @Column(name = "home_city")),
                  @AttributeOverride(name = "street", column = @Column(name = "home_street")),
          })
          private Address homeAddress;
      
      
          @Embedded
          @AttributeOverrides({
                  @AttributeOverride(name = "city", column = @Column(name = "company_city")),
                  @AttributeOverride(name = "street", column = @Column(name = "company_street")),
          })
          private Address companyAddress;
      
      
      }


'TIL' 카테고리의 다른 글

20240930 본캠프 53일차 TIL  (0) 2024.09.30
20240927 본캠프 52일차 TIL  (0) 2024.09.27
20240925 본캠프 50일차 TIL  (0) 2024.09.25
20240919 본캠프 46일차 TIL  (0) 2024.09.19
20240912 본캠프 44일차 TIL  (0) 2024.09.12