데이터 접근 기술

  • SQLMapper : SQL을 작성하면 해당 SQL의 결과를 객체로 편리하게 매핑
    • JdbcTemplate
    • MyBatis
  • ORM 관련 기술 : 기본적인 SQL은 JPA가 처리하고, 개발자는 객체를 마치 자바 컬렉션에 저장하고 조회하듯이 사용
    • JPA, Hibernate
    • 스프링 데이터 JPA
    • Querydsl

DTO (Data Transfer Object)

  • 데이터 전송객체
  • 특별한 기능은 없고 데이터를 전달하는 용도로 사용되는 객체

JdbcTemplate

  • Jdbc를 직접 사용했을 경우 반복문제를 해결할 수 있다.
  • 동적쿼리는 해결하기가 어렵다.
private final JdbcTemplate template;

public JdbcTemplateItemRepository(DataSource datasource){
	this.template = new JdbcTemplate(dataSource);
}

public Item save(Item item){
	String sql = "insert into item (item_name,price) values (?,?)";
    KeyHolder keyholder = new GeneratedKeyHolder();
    template.update(connection -> {
    	PreparedStatement ps = connection.prepareStatement(sql,new String[]{"id"});
        ps.setString(1,item.getItemName());
        ps.setInt(2,item.getPrice());
        retunr ps;
        }, keyHolder);
        
        long key = keyHolder.getKey().longValue();
        item.setId(key);
        return item;
 }  
  • 이름 지정 바인딩 (NamedParameterJdbcTemplate)
  1. BeanPropertySqlParameterSource : 자바빈 프로퍼티 규약을 통해서 자동으로 파라미터 객체 생성
  2. Map : Map을 사용하여 파라미터 제공
  3. MapSqlParameterSource : Map과 유사한데 좀 더 SQL에 특화
    SqlParameterSource param = new MapSqlParameterSource()
    .addValue("itemName", updateParam.getItemName())
    .addValue("price",updateParam.getPrice())
    .addValue("id",itemId);
    template.update(sql,param);
private final NamedParameterJdbcTemplate template;

public JdbcTemplateItemRepository(DataSource dataSource){
	this.template = new NamedParameterJdbcTemplate(dataSource);
}

public Item save(Item item){
	String sql = "insert into item (item_name, price) values (:item_name, :price)";
    SqlParameterSource param = new BeanPropertySqlParameterSource(item);
    KeyHolder keyHolder = new GeneratedKeyHolder();
    template.update(sql, param, keyHolder);
    
    Long ket = keyHolder.getKey().longValue();
    item.setId(key);
    return item;
}

MyBatis

  • SQL을 XML에 편리하게 작성할 수 있고, 동적쿼리를 매우 편리하게 작성이 가능하다.
  • 설정 : mybatis-spring-boot-starter 라이브러리 추가
#application.properties 설정 MyBatis

mybatis.type-aliases-package=hello.itemservice.domain //마이바티스에서 타입정보를 사용할 때 패키지이름 적어야하는데 생략
mybatis.configuration.map-underscore-to-camel-case=true //언더바를 카멜로 자동변경기능 (itemName -> item_name)
logging.level.hello.itemservice.repository.mybatis=trace //쿼리 로그
ItemMapper

@Mapper
public interface ItemMapper{
	void save(Item item);
    
    void update(@Param("id") Long id, @Param("updateParam") ItemupdateDto updateParam);
    
    Optional<Item> findById(Long id);
    
    List<Item> findAll(ItemSearchCond itemSearch);
}

마이바티스 매핑XML을 호출해주는 매퍼 인터페이스
@Mapper 애노테이션을 붙여야 MyBatis로 인식한다.
ItemMapper.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="hello.itemservice.repository.mybatis.ItemMapper">

    <insert id="save" useGeneratedKeys="true" keyProperty="id">
        insert into item (item_name, price, quantity)
        values (#{itemName}, #{price}, #{quantity})
    </insert>
    <update id="update">
        update item
        set item_name=#{updateParam.itemName},
            price=#{updateParam.price},
            quantity=#{updateParam.quantity}
        where id = #{id}
    </update>

    <select id="findById" resultType="Item">
        select id, item_name, price, quantity
        from item
        where id = #{id}
    </select>

    <select id="findAll" resultType="Item">
        select id, item_name, price, quantity
        from item
        <where>
            <if test="itemName != null and itemName != ''">
                and item_name like concat('%',#{itemName},'%')
            </if>
            <if test="maxPrice != null">
                and price &lt;= #{maxPrice}
            </if>
        </where>
    </select>
</mapper>

//자바코드가 아니므로 resource 하위에 만들되, 패키지 위치에 맞추어서생성해야한다.
namespace 앞서 만든 매퍼인터페이스를 지정하면 된다.
  • JPA, 스프링 데이터JPA, Querydsl은 이후 자세하게 다룰 예정

스프링 트랜잭션

  • 스프링은 PlatformTransactionManager 라는 인터페이스를 통해 트랜잭션을 추상화한다.
public interface PlatformTransactionManager extends TransactionManager{
	TrasactionStatus getTransaction(@Nullalbe TransactionDefinition definition) throws TransactionException;
    
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}
  • 스프링은 트랜잭션을 추상화 제공, 주로 사용하는 데이터 접근 기술에 대한 트랜잭션 매니저의 구현체도 제공한다.
  • 스프링부트는 어떠한 데이터 접근 기술을 사용하는 자동으로 인식하여 적절한 트랜잭션 매니저를 선택해서 스프링 빈으로 등록해줘서 트랜잭션 매니저 선택 및 등록과정이 생략가능하다.

프록시 내부 호출(트랜잭션)

public void external(){
	internal();
}

@Transactional
public void internal(){
	~~~
}
  • 해당 internal()메서드 실행시 @Transactional 로 트랜잭션이 적용된다.

  • 해당 external()메서드 실행시 내부에 internal()메서드 실행시 internal()메서드에 @Transactional이 있지만 internal()메서드는 트랜잭션이 적용되지 않는다.

    메서드 앞에 별도의 참조가 없을 경우 'this'라는 뜨으로 자신의 인스턴스를 가르킨다.
    실제 대상 객체의 인터스턴스를 뜻한다,결과적으로 이러한 내부호출은 프록시를 거치지 않아서 트랜잭션을 적용할 수가 없다.

  • 해당 문제를 해결하기 위해서는 내부 호출 메서드를 새로운 클래스에 옮ㅎ긴 후 내부 호출을 외부 호출로 변경한다.

static class CallService{
	private final InternalService internalService;
  public void external(){
      internalService.internal();
  }
}

static class InternalService{
  @Transactional
  public void internal(){
      ~~~
  }
}

public 메서드만 트랜잭션 적용
스프링의 트랜잭션 AOP 기능은 public에서만 적용되도록 설정되어있다.
스프링부트3.0부터는 'protected','package-visible'에도 적용가능

트랜잭션AOP 초기화 시점에는 적용되지 않을 수 있다.
초기화 코드가 먼저 호출되고, 그 다음에 트랜잭션 AOP가 적용된다.

트랜잭션 옵션

  • value,transactionManager
    스프링 빈에 등록된 어떤 트랜잭션 매니저를 사용할지 지정할때 사용
    생략시에는 기본으로 등록된 트랜잭션 매니저를 사용
    @Transactional("aaaa")
  • rollbackFor
    예외 발생시 스프링 트랜잭션의 기본정책으로는 언체크예외(RuntimeException,Error)는 롤백
    체크예외(Exception)은 커밋한다.
    @Transactional(rollbackFor=Exception.class) 사용시 해당 예외 및 하위 예외도 롤백할지 지정이 가능하다.
  • nonRollbackFor
    rollbackFor과 반대로 기본정책에 추가로 어떤 예외가 롤백하면 안될지 지정한다.
  • propagation
    트랜잭션 전파 밑에 자세히 설명
  • isolation
    트랜잭션 격리 수준 지정
    DEFAULT : 데이터베이스 설정한 격리 수준 따른다.
    READ_UNCOMMITTED : 커밋되지 않은 읽기
    READ_COMMITTED : 커밋된 읽기
    REPEATABLE_READ: 반복 가능한 읽기
    SERIALIZABLE : 직렬화 기능
  • timeout
    트랜잭션 수행시간에 대한 타임아웃을 초단위로 지정한다.
  • readOnly
    트랜잭션은 기본적으로 읽기쓰기가 모두 가능하지만
    readOnly=true 사용시 읽기기능만 작동한다.

스프링 트랜잭션 전파(propagation)

  • 물리트랜잭션 / 논리 트랜잭션

    물리트랜잭션 : 실제 데이터베이스에 적용되는 트랜잭션을 뜻한다.
    논리트랜잭션 : 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위

  • 트랜잭션 원칙

  1. 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
  2. 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.
  • 트랜잭션 요청흐름

    트랜잭션 매니저에 커밋을 호출한다고 항상 실제 커넥션에 커밋을 하지 않는다.
    신규 트랜잭션 경우에만 실제 커넥션을 사용해서 커밋과 롤백을 수항핸다.
    -> 내부로직인 논리트랜잭션은 실제로 커밋이나 롤백을 수행하지 않음

  • 외부트랜잭션이 롤백 / 내부트랜잭션 커밋인 경우
    내부트랜잭션은 커밋이나 롤백을 하지 않으므로 외부트랜잭션에서 롤백인 경우
    모든 로직(물리트랜잭션)이 롤백된다.

  • 외부트랜잭션이 커밋 / 내부트랜잭션이 롤백인 경우
    내부트랜잭션이 커밋이나 롤백을 하지는 않지만 롤백인 경우 트랜잭션 동기화 매니저에 rollbackOnly=true로 설정한다.
    외부트랜잭션은 커밋이니 커밋을 진행하는데 우선적으로 트랜잭션 동기화 매니저에 롤백전용(rollbackOnly=true) 표시가 있는지 확인 후 표시가 있을 경우
    커밋이 아니라 롤백을 진행하여 물리트랜잭션은 롤백을 진행한다.

다양한 전파 옵션(propagation)

  • REQUIRES
    가장 많이 사용하는 기본설정이다.
    기존트랜잭션이 없으면 생성하고, 있으면 참여한다.
  • REQUIRES_NEW
    항상 새로운 트랜잭션을 사용한다.
    외부트랜잭션과 내부트랜잭션을 각각의 물리트랜잭션으로 분리하여 커밋과 롤백을 가각 별도로 이루어지게 한다.
  • SUPPORT
    트랜잭션을 지원한다는 뜻으로 기존트랜잭션이 없으면 없는대로 진행하고,
    있으면 참여한다.
  • NOT_SUPPORT
    트랜잭션을 지원하지 않는다는 의미이다.
    기존트랜잭션이 있든 없든 없이 진행한다. 기존트랜잭션이 있을 경우 보류한다.
  • MANDATORY
    의무사항으로 트랜잭션이 무조건 있어야한다.
    기존트랜잭션이 없으면 'IllegalTransactionStateException' 예외발생
    기존트랜잭션이 있으면 참여한다.
  • NEVER
    트랜잭션을 사용하지 않는다는 의미이다
    기존트랜잭션이 없으면 없이 진행한다.
    기존트랜잭션이 있으면 'IllegalTransactionStateException' 예외발생
  • NESTED
    기존트랜잭션 없으면 새로운 트랜잭션 생성한다.
    기존트랜잭션 있으면 중첩트랜잭션을 만든다.
    중첩트랜잭션은 외부 트랜잭션의 영향을 받지만, 중첩트랜잭션은 외부에 영향을 주지 않는다.
    중첩트랜잭션이 롤백 되어도 외부 트랜잭션은 커밋이 가능
    외부트랜잭션이 롤백되면 중첩 트랜잭션도 함께 롤백된다.
profile
한번 해봅시다.

0개의 댓글