DTO (Data Transfer Object)
- 데이터 전송객체
- 특별한 기능은 없고 데이터를 전달하는 용도로 사용되는 객체
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;
}
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;
}
#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 <= #{maxPrice}
</if>
</where>
</select>
</mapper>
//자바코드가 아니므로 resource 하위에 만들되, 패키지 위치에 맞추어서생성해야한다.
namespace 앞서 만든 매퍼인터페이스를 지정하면 된다.
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가 적용된다.
물리트랜잭션 / 논리 트랜잭션
물리트랜잭션 : 실제 데이터베이스에 적용되는 트랜잭션을 뜻한다.
논리트랜잭션 : 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위
트랜잭션 원칙
트랜잭션 요청흐름
트랜잭션 매니저에 커밋을 호출한다고 항상 실제 커넥션에 커밋을 하지 않는다.
신규 트랜잭션 경우에만 실제 커넥션을 사용해서 커밋과 롤백을 수항핸다.
-> 내부로직인 논리트랜잭션은 실제로 커밋이나 롤백을 수행하지 않음
외부트랜잭션이 롤백 / 내부트랜잭션 커밋인 경우
내부트랜잭션은 커밋이나 롤백을 하지 않으므로 외부트랜잭션에서 롤백인 경우
모든 로직(물리트랜잭션)이 롤백된다.
외부트랜잭션이 커밋 / 내부트랜잭션이 롤백인 경우
내부트랜잭션이 커밋이나 롤백을 하지는 않지만 롤백인 경우 트랜잭션 동기화 매니저에 rollbackOnly=true로 설정한다.
외부트랜잭션은 커밋이니 커밋을 진행하는데 우선적으로 트랜잭션 동기화 매니저에 롤백전용(rollbackOnly=true) 표시가 있는지 확인 후 표시가 있을 경우
커밋이 아니라 롤백을 진행하여 물리트랜잭션은 롤백을 진행한다.