JiwonDev

Spring DB ๊ธฐ์ˆ  ํŒŒํ—ค์น˜๊ธฐ #2

by JiwonDev

 

์Šคํ”„๋ง DB 2ํŽธ - ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ํ™œ์šฉ ๊ธฐ์ˆ  ๊ฐ•์˜ - ์ธํ”„๋Ÿฐ

๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ DB ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ธฐ์ˆ ์„ ํ™œ์šฉํ•˜๊ณ , ์™„์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํ”„๋ง DB ์ ‘๊ทผ ๊ธฐ์ˆ ์˜ ์›๋ฆฌ์™€ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๊ณ , ๋” ๊นŠ์ด์žˆ๋Š” ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ์„ฑ์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค., ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž

www.inflearn.com

 

 

๐Ÿ€ JdbcTemplate

implementation 'org.springframework.boot:spring-boot-starter-jdbc'

 

์ฐธ๊ณ ๋กœ JPA๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด spring-boot-starter-data-jpa ์— jdbc ์˜์กด์„ฑ๋„ ํฌํ•จ๋˜์–ด์žˆ์–ด ๋”ฐ๋กœ ์ถ”๊ฐ€ํ•  ํ•„์š” ์—†๋‹ค.

 

 

JdbcTemplate์€ DataSource๋ฅผ ์ด์šฉํ•ด ์ง์ ‘ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค๋งŒ ์Šคํ”„๋ง ๋ถ€ํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๊ทธ๋ƒฅ ๋ฐ”๋กœ ์“ฐ๋ฉด๋˜๋Š”๋ฐ,  JdbcTemplateAutoConfiguration ์— ์˜ํ•ด JdbcTemplate, NamedParameterJdbcTemplate ๋นˆ์ด ์—†๋‹ค๋ฉด ๋“ฑ๋กํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋‹ค.

JdbcTemplate์€ JdbcOperations ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์ด๋‹ค. ์ฆ‰ ์ˆ˜๋™์œผ๋กœ ๋“ฑ๋ก ํ•ด๋‘” ๋นˆ์ด ์—†๋‹ค๋ฉด ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

 

์ดํ›„ ์‚ฌ์šฉ๋ฒ•์€ ๋„ˆ๋ฌด๋‚˜ ๊ฐ„๋‹จํ•˜๋‹ค. dataSource๋กœ JdbcTemplate๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

  • execute() ๊ทธ๋ƒฅ sql ์ž์ฒด๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์„ ๋•Œ, ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉํ•  ์ผ์€ ๊ฑฐ์˜ ์—†๋‹ค.
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");

 jdbcTemplate.update(
         "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
         Long.valueOf(unionId));

 

  • update : ์˜ํ–ฅ๋ฐ›์€ row ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. (update, insert, delete)
  • insert ๋ฅผ ํ•  ๋•Œ ์ž๋™ ์ƒ์„ฑํ•œ pk ๋ฅผ ์–ป๊ณ ์‹ถ๋‹ค๋ฉด KeyHolder = new GenereatedKeyHolder()๋ฅผ update์— ๊ฐ™์ด ๋„˜๊ธฐ๋ฉด ๋œ๋‹ค.
String sql = "update item set item_name=?, price=?, quantity=? where id=?";
template.update(sql,
        updateParam.getItemName(),
        updateParam.getPrice(),
        updateParam.getQuantity(),
        itemId);

 

 

  • query: ๊ธฐ๋ณธ ๋ฉ”์„œ๋“œ, ์ž˜ ์‚ฌ์šฉํ•˜์ง€์•Š๋Š”๋‹ค. ๋‹จ๊ฑด ์กฐํšŒ๋Š” queryForObject() ์‚ฌ์šฉ ๊ถŒ์žฅ
  • queryForXXX: ์ฝ์–ด๋ณด๋ฉด ๋ฐ”๋กœ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ฐธ๊ณ ๋กœ queryForStream์€ ํƒ€์ž…๋งŒ Stream<>์ผ ๋ฟ์ด๋‹ค. ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ๋Š” ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.
 int countOfActorsNamedJoe = jdbcTemplate.queryForObject(
         "select count(*) from t_actor where first_name = ?", Integer.class,"Joe");

 String lastName = jdbcTemplate.queryForObject( "select last_name from t_actor where id = ?", String.class, 1212L);
 
 
 Actor actor = jdbcTemplate.queryForObject(
         "select first_name, last_name from t_actor where id = ?",
         (resultSet, rowNum) -> {
    Actor newActor = new Actor();
    newActor.setFirstName(resultSet.getString("first_name"));
    newActor.setLastName(resultSet.getString("last_name"));
    return newActor;
}, 1212L);

List<Actor> actors = jdbcTemplate.query(
         "select first_name, last_name from t_actor",
         (resultSet, rowNum) -> {
             Actor actor = new Actor();
             actor.setFirstName(resultSet.getString("first_name"));
             actor.setLastName(resultSet.getString("last_name"));
             return actor;
});

 

 

์‹ค์ œ Repository๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

public class JdbcTemplateItemRepository implements ItemRepository {

    private final JdbcTemplate template;

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

    @Override
    public Item save(Item item) {
        String sql = "insert into item(item_name, price, quantity) values (?,?,?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();
        template.queryFor
        template.update(connection -> {
            //์ž๋™ ์ฆ๊ฐ€ ํ‚ค
            PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
            ps.setString(1, item.getItemName());
            ps.setInt(2, item.getPrice());
            ps.setInt(3, item.getQuantity());
            return ps;
        }, keyHolder);

        long key = keyHolder.getKey().longValue();
        item.setId(key);
        return item;
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        String sql = "update item set item_name=?, price=?, quantity=? where id=?";
        template.update(sql,
                updateParam.getItemName(),
                updateParam.getPrice(),
                updateParam.getQuantity(),
                itemId);
    }

    @Override
    public Optional<Item> findById(Long id) {
        String sql = "select id, item_name, price, quantity from item where id = ?";
        try {
            Item item = template.queryForObject(sql, itemRowMapper(), id);
            return Optional.of(item);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();

        String sql = "select id, item_name, price, quantity from item";
        //๋™์  ์ฟผ๋ฆฌ
        if (StringUtils.hasText(itemName) || maxPrice != null) {
            sql += " where";
        }

        boolean andFlag = false;
        List<Object> param = new ArrayList<>();
        if (StringUtils.hasText(itemName)) {
            sql += " item_name like concat('%',?,'%')";
            param.add(itemName);
            andFlag = true;
        }

        if (maxPrice != null) {
            if (andFlag) {
                sql += " and";
            }
            sql += " price <= ?";
            param.add(maxPrice);
        }

        return template.query(sql, itemRowMapper(), param.toArray());
    }

    private RowMapper<Item> itemRowMapper() {
        return ((rs, rowNum) -> {
            Item item = new Item();
            item.setId(rs.getLong("id"));
            item.setItemName(rs.getString("item_name"));
            item.setPrice(rs.getInt("price"));
            item.setQuantity(rs.getInt("quantity"));
            return item;
        });
    }
}

 

 

 

๊ทธ ์™ธ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘” ํŽธ์˜๊ธฐ๋Šฅ๋“ค์ด ์กด์žฌํ•œ๋‹ค.

  • SimpleJdbcInsert ๋ฅผ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด ๋“ฑ๋กํ•ด๋‘๋ฉด, insert๋ฅผ ๋ฉ”์„œ๋“œ ํ•˜๋‚˜๋กœ ํ•  ์ˆ˜ ์žˆ๋‹ค.
public class JdbcTemplateItemRepository implements ItemRepository {

    private final NamedParameterJdbcTemplate template;
    private final SimpleJdbcInsert jdbcInsert;

    public JdbcTemplateItemRepositoryV3(DataSource dataSource) {
        this.template = new NamedParameterJdbcTemplate(dataSource);
        this.jdbcInsert = new SimpleJdbcInsert(dataSource)
                .withTableName("item")
                .usingGeneratedKeyColumns("id")
                .usingColumns("item_name", "price", "quantity"); // usingColumns ๋Š” ์ƒ๋žต ๊ฐ€๋Šฅ
    }

    @Override
    public Item save(Item item) {
        SqlParameterSource param = new BeanPropertySqlParameterSource(item);
        Number key = jdbcInsert.executeAndReturnKey(param);
        item.setId(key.longValue());
        return item;
    }
}

 

  • NamedParameterJdbcTemplate ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด :itemName ์œผ๋กœ ๋ฐ”์ธ๋”ฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • BeanPropertyRowMapper.newInstance(MyClass.class) ๊ฐ™์ด ํŽธ์˜์šฉ rowMapper๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ํ•„๋“œ camel ๋ณ€ํ™˜๋„ ์ง€์›!
  • SimpleJdbcCall ๊ฐ™์€ ํ”„๋กœ์‹œ์ € ์ฝœ๋„ ์žˆ์ง€๋งŒ, ์‚ฌ์šฉํ•  ์ผ์ด ์—†์œผ๋ฏ€๋กœ ์ƒ๋žต

public class JdbcTemplateItemRepository implements ItemRepository {

    private final NamedParameterJdbcTemplate template;
    private final SimpleJdbcInsert jdbcInsert;

    public JdbcTemplateItemRepositoryV3(DataSource dataSource) {
        this.template = new NamedParameterJdbcTemplate(dataSource);
        this.jdbcInsert = new SimpleJdbcInsert(dataSource)
                .withTableName("item") // ํ…Œ์ด๋ธ” ๋ช…
                .usingGeneratedKeyColumns("id") // pk ๋ช…
                // ์ƒ๋žตํ•˜๋”๋ผ๋„ DB ๋ฉ”ํƒ€ ์ฝ์–ด์„œ ๋งž์ถฐ์คŒ. ์ƒ๋žต ๊ฐ€๋Šฅํ•˜๋‚˜ ํŠน์ • ์นผ๋Ÿผ๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ
                .usingColumns("item_name", "price", "quantity");
    }
    
    @Override
    public Item save(Item item) {
        SqlParameterSource param = new BeanPropertySqlParameterSource(item);
        // ์ด๋ ‡๊ฒŒ ๋งค์šฐ ํŽธํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
        Number key = jdbcInsert.executeAndReturnKey(param);
        item.setId(key.longValue());
        return item;
    }
    
    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        String sql = "update item " +
                "set item_name=:itemName, price=:price, quantity=:quantity " +
                "where id=:id";

        SqlParameterSource param = new MapSqlParameterSource()
                .addValue("itemName", updateParam.getItemName())
                .addValue("price", updateParam.getPrice())
                .addValue("quantity", updateParam.getQuantity())
                .addValue("id", itemId); //์ด ๋ถ€๋ถ„์ด ๋ณ„๋„๋กœ ํ•„์š”ํ•˜๋‹ค.

        template.update(sql, param);
    }

    
    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();

        SqlParameterSource param = new BeanPropertySqlParameterSource(cond);

        String sql = "select id, item_name, price, quantity from item";
        //๋™์  ์ฟผ๋ฆฌ
        if (StringUtils.hasText(itemName) || maxPrice != null) {
            sql += " where";
        }

        boolean andFlag = false;
        if (StringUtils.hasText(itemName)) {
            sql += " item_name like concat('%',:itemName,'%')";
            andFlag = true;
        }

        if (maxPrice != null) {
            if (andFlag) {
                sql += " and";
            }
            sql += " price <= :maxPrice";
        }

        log.info("sql={}", sql);
        return template.query(sql, param, itemRowMapper());
    }

    private RowMapper<Item> itemRowMapper() {
        return BeanPropertyRowMapper.newInstance(Item.class); //camel ๋ณ€ํ™˜ ์ง€์›
    }
}

 

 

 

 

 

 

๐Ÿ€ MyBatis

https://mybatis.org/mybatis-3/ko/getting-started.html

implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'

์Šคํ”„๋ง ํ•„์š”์—†์œผ๋ฉด ๊ณต์‹๋ฌธ์„œ๋ณด๊ณ  org.mybatis.mybatis ์“ฐ๋ฉด ๋œ๋‹ค

spring:
  profiles:
    active: local
  datasource:
    url: jdbc:h2:tcp://localhost/~/test
    username: sa
  logging:
    level:
      org:
        springframework:
          jdbc: debug

mybatis:
  # xml ํŒŒ์ผ์—์„œ ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ ํŒจํ‚ค์ง€๋ช…, ์—ฌ๊ธฐ์— ์ ์–ด์•ผ ์ƒ๋žต ๊ฐ€๋Šฅํ•˜๋‹ค. ( ; ๋กœ ์—ฌ๋Ÿฌ ๊ณณ์„ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค)
  type-aliases-package: hello.itemservice.domain
  # mapper ํด๋ž˜์Šค์˜ ๊ธฐ๋ณธ ์œ„์น˜. ์ด๊ฒŒ ์—†์œผ๋ฉด ์‹ค์ œ ํด๋ž˜์Šค ํŒจํ‚ค์ง€ ์œ„์น˜์™€ ๋™์ผํ•˜๊ฒŒ resource/../ ์— ๋งŒ๋“ค์–ด ์ฃผ์–ด์•ผํ•œ๋‹ค.
  mapper-locations: classpath:sql/**/*.xml
  # db camel case -> java camelCase ์ž๋™ ๋ณ€ํ™˜
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    hello:
      itemservice:
        repository:
          mybatis: trace

 

Spring MyBatis๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ตฌํ˜„์ฒด๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์ฃผ์ง€ ์•Š์•„๋„๋œ๋‹ค. ๊ทธ๋ƒฅ @Mapper๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค ๋‹ฌ์•„์ฃผ๋ฉด ๊ตฌํ˜„์ฒด ๋นˆ์ด ์ƒ์„ฑ๋œ๋‹ค

์ฐธ๊ณ ๋กœ ๊ทธ๋ƒฅ myBatis๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด SqlSessionFactory, SqlSession์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// Open a new SqlSession
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    // Retrieve data from the database using MyBatis
    List<MyObject> myObjects = sqlSession.selectList("com.example.MyObjectMapper.selectMyObjects");
} ...

 

์šฐ๋ฆฐ ์Šคํ”„๋ง์—†์ด MyBatis ์“ธ์ผ ์—†์œผ๋ฏ€๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด @Mapper๋งŒ ๋‹ฌ์•„์ฃผ๋ฉด ๋. ๋นˆ์œผ๋กœ ์Šค์บ”๋œ๋‹ค.

@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);
}
@Slf4j
@Repository
@RequiredArgsConstructor
public class MyBatisItemRepository implements ItemRepository {

    private final ItemMapper itemMapper;

    @Override
    public Item save(Item item) {
        log.info("itemMapper class={}", itemMapper.getClass());
        itemMapper.save(item);
        return item;
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        itemMapper.update(itemId, updateParam);
    }

    @Override
    public Optional<Item> findById(Long id) {
        return itemMapper.findById(id);
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        return itemMapper.findAll(cond);
    }
}

 

xml path๋Š” ์‹ค์ œ ํด๋ž˜์Šค์™€ ๋™์ผํ•˜๊ฒŒ ๋งž์ถฐ์ฃผ๋ฉด ๋œ๋‹ค.

<?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>

 

 

sqlSessionTemplate ์ง์ ‘ ์‚ฌ์šฉ

์ด๋Ÿฐ์‹์œผ๋กœ ์ถ”์ƒํ™”ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์‹ซ์€ ๋ถ„๋“ค์„ ์œ„ํ•ด sqlSessionTemplate ์„ ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•ด์ค€๋‹ค. ์กฐ๊ธˆ ๋” ์ง๊ด€์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.๋Œ€์‹  MyBatis์˜ ๊ตฌ์กฐ์— ๋Œ€ํ•ด ์กฐ๊ธˆ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

 

 

์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” sqlSessionFactoryBean์„ ๋“ฑ๋กํ•ด์„œ ์“ฐ๋ฉด ๋œ๋‹ค. sqlSessionTemplate์„ ์ถ”๊ฐ€ํ•ด๋†“์ž

๋ฌผ๋ก  ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๋”๋ผ๋„ MybatisAutoConfiguration ์— ์˜ํ•ด์„œ ์•„๋ž˜ ๋นˆ๋“ค์€ ์ž๋™์œผ๋กœ ๋‹ค ๋“ฑ๋ก๋œ๋‹ค. ์ˆ˜๋™์œผ๋กœ ์„ค์ •ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉ

์Šคํ”„๋ง ๋งˆ์ด๋ฒ ํ‹ฐ์Šค ์˜์กด์„ฑ์„ ๋ถˆ๋Ÿฌ์˜ค๋ฉด, ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ MybatisAutoConfiguration ๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค. ํ•ด๋‹น ํด๋ž˜์Šค์— ๋‹ค ์žˆ์Œ
@Configuration
public class DatabaseConfiguration {

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        var sqlSessionFactory = new SqlSessionFactoryBean();

        // yml ์„ค์ •์„ ์ฝ”๋“œ๋กœ ์ง์ ‘ ์ถ”๊ฐ€
        sqlSessionFactory.setDataSource(dataSource);

        var configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setTypeHandlersPackage("com.your.package");
        
        // mapper-locations: classpath:sql/**/*.xml ๋“ฑ๋ก
        var resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/xml/*.xml"));

        return sqlSessionFactory.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

 

์„ค์ •์—๋ณด๋ฉด typeHandlers ์Šค์บ” ํŒจํ‚ค์ง€ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋Š” Enum Converter์ฒ˜๋Ÿผ ํŠน์ • DB ํƒ€์ž…์„ ๋งคํ•‘ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค

// RoleType ์ปจ๋ฒ„ํ„ฐ
@MappedTypes(Role.class)
public final class RoleTypeHandler implements TypeHandler<Role> {

    @Override
    public void setParameter(PreparedStatement ps, int i,
        Role parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.name());
    }

    @Override
    public Role getResult(ResultSet rs, String columnName) throws SQLException {
        return Role.findRole(rs.getString(columnName));
    }

    @Override
    public Role getResult(ResultSet rs, int columnIndex) throws SQLException {
        return Role.findRole(rs.getString(columnIndex));
    }

    @Override
    public Role getResult(CallableStatement cs, int columnIndex) throws SQLException {
        return Role.findRole(cs.getString(columnIndex));
    }
}

 

 

 

์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ

public final class MybatisAccountRepository implements AccountRepository, AccountReader {

    private static final String SAVE_FQCN = "com.yam.app.account.domain.AccountRepository.save";
    private static final String UPDATE_FQCN = "com.yam.app.account.domain.AccountRepository.update";

    private final SqlSessionTemplate template;

    public MybatisAccountRepository(SqlSessionTemplate template) {
        this.template = template;
    }

    @Override
    public boolean existsByEmail(String email) {
        return template.getMapper(AccountReader.class).existsByEmail(email);
    }

    @Override
    public void update(Account entity) {
        int result = template.update(UPDATE_FQCN, entity);
        if (result != 1) {
            throw new IllegalStateException(String.format(
                "Unintentionally, more records were updated than expected. : %s", entity));
        }
    }

    @Override
    public void save(Account entity) {
        int result = template.insert(SAVE_FQCN, entity);
        if (result != 1) {
            throw new IllegalStateException(String.format(
                "Unintentionally, more records were saved than expected. : %s", entity));
        }
    }

    @Override
    public Optional<Account> findByEmail(String email) {
        return template.getMapper(AccountReader.class).findByEmail(email);
    }

    @Override
    public MemberAccount findByEmailAndMemberId(String email,
        Long memberId) {
        return template.getMapper(AccountReader.class).findByEmailAndMemberId(email, memberId);
    }
}

 

 

 

 

๐Ÿ€ JPA, Hibernate

https://jiwondev.tistory.com/category/%F0%9F%8C%B1Backend/JDBC%20%26%20JPA?page=1

https://jiwondev.tistory.com/252

 

JPA ์„ฑ๋Šฅ ๊ฐœ์„ ํŒ

์ธํ”„๋Ÿฐ ๊น€์˜ํ•œ๋‹˜ ์‹ค์ „! ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA ํ™œ์šฉ2 - API ๊ฐœ๋ฐœ๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™” - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜ ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA๋ฅผ ํ™œ์šฉํ•ด์„œ API๋ฅผ ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  JPA ๊ทนํ•œ์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ

jiwondev.tistory.com

 

 

 

๐Ÿ€ ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA

https://jiwondev.tistory.com/253

 

Spring Data JPA

๊ธฐ์–ต์•ˆ๋‚  ๋•Œ ์‰ฝ๊ฒŒ ์ฐพ์œผ๋ ค๊ณ  ํ•œ ๊ธ€์— ๋‹ค ์ ์—ˆ์Šต๋‹ˆ๋‹ค. * ์Šคํฌ๋กค ์••๋ฐ• ์ฃผ์˜ * ์Šคํ”„๋ง ์„ค์ •์„ ์‰ฝ๊ฒŒ ํ•ด์ฃผ๋Š” Spring Boot ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ๋“ฏ์ด ์Šคํ”„๋ง์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” Spring Data ํ”„๋กœ์ ํŠธ

jiwondev.tistory.com

 

ํ•˜๋‚˜๋งŒ ์ถ”๊ฐ€ํ•˜์ž๋ฉด, JPA์˜ ์˜์กด์„ฑ์„ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์™„์ „ํžˆ ๊ฐ์ถœ ์ˆ˜ ์žˆ๋‹ค. ์šฐ์„  ์•„๋ž˜์™€ ๊ฐ™์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ •์˜ํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ๋ถ„๋ฆฌํ•  ๊ฒฝ์šฐ, ์ธํ„ฐํŽ˜์ด์Šค ๋ถ„๋ฆฌ ์›์น™์— ๋”ฐ๋ผ Repository ->  { Reader , Writer } ๋กœ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ์“ฐ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.

public interface SampleRepository {
    Optional<SampleEntity> findById(Long id);

    SampleEntity save(SampleEntity entity);
}

 

1๏ธโƒฃ JPA ๊ตฌํ˜„์ฒด๋งŒ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๊น”๋”ํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฐธ๊ณ ๋กœ QueryDsl๋“ฑ์„ ์“ด๋‹ค๋ฉด interface CustomJpaRepository ๋ฅผ ๋งŒ๋“ค๊ณ , SampleJpaRepository๊ฐ€ ์ƒ์†ํ•˜๊ฒŒ ํ•˜๋ฉด ๋™์ผํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

// ์ฐธ๊ณ ๋กœ @Repository๋ฅผ ์ƒ๋žตํ•ด๋„ @EnableJpaRepositories ์— ์˜ํ•ด JpaRepository ํƒ€์ž…์ด ์Šค์บ”๋˜์–ด ๋นˆ ๋“ฑ๋ก๋œ๋‹ค.
@Repository 
public interface SampleJpaRepository extends SampleRepository, JpaRepository<SampleEntity, Long> {

    @Override
    Optional<SampleEntity> findById(Long id);
}

 

 

2๏ธโƒฃ ์—ฌ๋Ÿฌ DB ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ปดํฌ์ง€์…˜์„ ํ™œ์šฉํ•ด์„œ, ์•„์˜ˆ ๋ณ„๋„์˜ RepositoryImpl ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ด์šฉํ•ด๋„ ์ข‹๋‹ค.

@Repository
public interface SampleJpaRepository extends JpaRepository<SampleEntity, Long> {

}

@RequiredArgsConstructor
public class SampleRepositoryImpl implements SampleRepository {

    private final SampleJpaRepository jpaRepository;
    private final SampleCustomRepository customRepository;

    @Override
    public Optional<SampleEntity> findById(Long id) {
        return customRepository.findById(id);
    }

    @Override
    public SampleEntity save(SampleEntity entity) {
        return jpaRepository.save(entity);
    }
}

 

ํ•˜์ง€๋งŒ ๊ฒฐ๊ตญ ๋ชจ๋“ ๊ฑด ํŠธ๋ ˆ์ด๋“œ ์˜คํ”„๋ผ๋Š” ๊ฑธ ๋ช…์‹ฌํ•˜์ž. ์šฐ๋ฆฌ๋Š” ์™œ DIP ์›์น™์„ ์ง€ํ‚ค๊ณ , ์ถ”์ƒํ™”์‹œ์ผœ์„œ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š”๊ฑธ๊นŒ? ๊ฒฐ๊ตญ ์ฝ”๋“œ ์ˆ˜์ •์˜ ์˜ํ–ฅ๋„๋ฅผ ์ค„์—ฌ ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ์™€ ๊ฐœ๋ฐœ ๋น„์šฉ์„ ์ค„์ด๊ธฐ ์œ„ํ•จ์ด๋‹ค. ์›์น™์„ ์ง€ํ‚ค๊ธฐ ์œ„ํ•œ ์›์น™์€ ์•„๋ฌด๋Ÿฐ ์˜๋ฏธ๊ฐ€ ์—†๋‹ค.

 

ํ˜„์‹ค์ ์œผ๋กœ DB ์˜์กด์„ฑ์ด ๋ฐ”๋€Œ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์–ผ๋งˆ๋‚˜ ๋ ๊นŒ? ์•„๋‹ˆ, ์• ์ดˆ์— ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค๊ณ  ํ•ด๋„ DB๋ฅผ ์‰ฝ๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์„๊นŒ?

๋ชจ๋“  ๊ฒƒ์€ ํŠธ๋ ˆ์ด๋“œ ์˜คํ”„์ด๋‹ค. ์„œ๋น„์Šค์— ๋”ฐ๋ผ Repository ์ž์ฒด๊ฐ€ ๋„ˆ๋ฌด๋‚˜ ๋ณต์žกํ•˜๋‹ค๋ฉด ์œ„ ์˜ˆ์ œ์ฒ˜๋Ÿผ ๋ถ„๋ฆฌํ•˜๋Š”๊ฒŒ ํ›จ์”ฌ ๋‚˜์„ ์ˆ˜ ์žˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ ๊ทธ ์ •๋„๋กœ ํฐ ์„œ๋น„์Šค๊ฐ€ ์•„๋‹ˆ๊ฑฐ๋‚˜ ๋‹น์žฅ ๋งŒ๋“œ๋Š”๊ฒŒ ๊ธ‰ํ•˜๋‹ค๋ฉด JpaRepository๋ฅผ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ •๋‹ต์ผ ์ˆ˜๋„ ์žˆ๋‹ค.

 

 

๐Ÿ€ Querydsl

https://jiwondev.tistory.com/255

 

์Šคํ”„๋งJPA์˜ ์˜์†์„ฑ์ปจํ…์ŠคํŠธ (EntityManager)

๐Ÿ’ญ JPA (ํ•˜์ด๋ฒ„๋„ค์ดํŠธ)์˜ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ JPA์—์„œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” DB์—์„œ ๊ฐ€์ ธ์˜จ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ์ฒซ๋ฒˆ์งธ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ ์ €์žฅ์†Œ์ž…๋‹ˆ๋‹ค.์ด๋ฅผ EntityManager ๊ฐ์ฒด์˜ API๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜์†

jiwondev.tistory.com

 

 

์Šคํ”„๋ง์—์„œ QuerydslRepositorySupport๋ผ๊ณ  ๋”ฐ๋กœ ์ง€์›ํ•ด์ฃผ๋Š”๊ฒŒ ์žˆ๊ธดํ•œ๋ฐ ์‚ฌ์šฉํ•ด๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ์‚ด์ง ๋ถˆํŽธํ•˜๋‹ค.

// ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์‚ฌ์šฉ์ž ์ •์˜ ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์† (MemberRepositoryCustom)
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
    List<Member> findByUsername(String username);
}

public class MemberRepositoryImpl extends QuerydslRepositorySupport implements MemberRepositoryCustom {
    public MemberRepositoryImpl() {
        super(Member.class);
    }

    //  QuerydslRepositorySupport ์‚ฌ์šฉ
    //  from์ ˆ์ด ๋จผ์ € ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    private Page<MemberTeamDto> getMemberTeamDtoQueryResults(MemberSearchCondition condition, Pageable pageable) {
        JPQLQuery<MemberTeamDto> jpaQuery = from(member)
                .leftJoin(member.team, team)
                .where(usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe()))
                .select(new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.username,
                        member.age,
                        team.id.as("teamId"),
                        team.name.as("teamName")));

        JPQLQuery<MemberTeamDto> query = getQuerydsl().applyPagination(pageable, jpaQuery);// offset, limit ์ง€์›

        query.fetch();
    }

}

 

 

์ฐจ๋ผ๋ฆฌ QueryDsl์—์„œ ์ œ๊ณตํ•˜๋Š” JpaQueryFactory (* EntityManager์™€ 1:1 ๊ด€๊ณ„)๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ํ›จ์”ฌ ๊น”๋”ํ•˜๊ณ  ํŽธํ•˜๋‹ค. ์ฐธ๊ณ ๋กœ Spring Jpa์—์„œ๋Š” `Impl` ์ด๋ผ๋Š” prefix๋ฅผ ๋ถ™์ด๋ฉด ์ž๋™์œผ๋กœ ๋นˆ ์Šค์บ”์ด ๋œ๋‹ค.

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}
// 1. Jpa๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ๊ฑธ๋กœ ํ™•์žฅํ•˜๊ณ  ์‹ถ์€ ๋ฉ”์„œ๋“œ๋Š” ์—ฌ๊ธฐ์—๋‹ค๊ฐ€ ๋”ฐ๋กœ ์ •์˜ํ•ด๋‘”๋‹ค.
public interface MemberRepositoryCustom {
    List<Member> findMemberCustom();
}

//2. ์ฐธ๊ณ ๋กœ 'Custom' ์€ ๊ผญ ์•ˆ๋ถ™์—ฌ๋„ ๋œ๋‹ค. ๋’ค์— 'Impl' ์ด๋ฆ„์„ ๊ธฐ์ค€์œผ๋กœ ๊ฐ™์€ ํŒจํ‚ค์ง€์•ˆ์˜ ํด๋ž˜์Šค๋ฅผ ์Šค์บ”ํ•œ๋‹ค.
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
 
    private final EntityManager em;
    public MemberRepositoryImpl(EntityManager em) { this.em = em; }
 
    @Override
    public List<Member> findMemberCustom() {
        return em.createQuery("select m from Member m")
            .getResultList();
    }
}

 

 

์ฐธ๊ณ ๋กœ `Impl` ์ ‘๋‘์‚ฌ ์„ค์ • ๋˜ํ•œ @EnableJpaRepository์—์„œ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ Jpa ์˜์กด์„ฑ์ด์žˆ์œผ๋ฉด ์„ค์ •ํ•ด์คŒ.

@EnableJpaRepositories(basePackages = "study.datajpa.repository", // ๋ฌผ๋ก  ์Šค์บ” ํŒจํ‚ค์ง€๋„ ๋ณ€๊ฒฝ๊ฐ€๋Šฅ
                            repositoryImplementationPostfix = "Impl") // ์—ฌ๊ธฐ

 

 

๋ฌผ๋ก  ์ด๋ฆ„ ๊ทœ์น™์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋ƒฅ JpaRepository, QueryDslRepostiory๋กœ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค. ๋งŒ๋“œ๋Š”๊ฑด ๋” ๊ท€์ฐฎ์•„์ง€์ง€๋งŒ service ์—์„œ ํ•˜๋‚˜๋งŒ ์“ฐ๊ณ ์‹ถ๋‹ค๋ฉด ItemRepository ์ž๋ฐ” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ , ItemRepositoryImpl ์—์„œ ์‹ค์ œ ๊ตฌํ˜„์ฒด๋ฅผ ๋นˆ์œผ๋กœ ๋ฐ›์•„์™€์„œ ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์“ฐ๋ฉด ๋œ๋‹ค.

@Repository
public class ItemQueryRepository{
       private final JPAQueryFactory query;
}

@Service
public class ItemService {
    private final ItemRepositoryV2 itemRepositoryV2;
    private final ItemQueryRepositoryV2 itemQueryRepositoryV2;
}

 

 

https://jiwondev.tistory.com/254

 

QueryDSL + JPA

์ฐธ๊ณ ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. spring: datasource: url: jdbc:h2:tcp://localhost/~/test username: sa password: driver-class-name: org.h2.Driver jpa: hibernate: ddl-auto: create p

jiwondev.tistory.com

 

 

 

 

๐Ÿ€ ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ์˜ต์…˜

์Šคํ”„๋ง์€ @Transactional๋กœ ์†์‰ฝ๊ฒŒ ํŠธ๋žœ์žญ์…˜์„ ๊ฑธ ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด๋†จ์ง€๋งŒ, ์‚ฌ์‹ค ์‹ค์ œ ํŠธ๋žœ์žญ์…˜ ๋™์ž‘๊ณผ 100% ์ผ์น˜ํ•˜๋Š”๊ฑด ์•„๋‹™๋‹ˆ๋‹ค.

  • ๊ฐœ๋ฐœ์ž๊ฐ€ @Transactional ๋กœ ๊ฑด ๊ฒƒ์„ ๋…ผ๋ฆฌ ํŠธ๋žœ์žญ์…˜์ด๋ผ๊ณ  ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • JDBC๋ฅผ ํ†ตํ•ด ์‹ค์ œ๋กœ ๊ฑธ๋ฆฌ๋Š” ๋ถ€๋ถ„์„ ๋ฌผ๋ฆฌ ํŠธ๋žœ์žญ์…˜์ด๋ผ๊ณ  ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.

 

 

์ด๋ ‡๊ฒŒ ๊ฐœ๋…์„ ๋‚˜๋ˆ ์„œ JDBC์—๋Š” ์—†๋Š” ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ๋ผ๋Š” ์žฌ๋ฐŒ๋Š” ๊ธฐ๋Šฅ์„ ์Šคํ”„๋ง์ด ๊ตฌํ˜„ํ•ด๋†“์•˜์Šต๋‹ˆ๋‹ค.

// ๊ธฐ๋ณธ๊ฐ’์€ REQUIRED, ์—†์œผ๋ฉด ๊ฑธ๊ณ  ์žˆ์œผ๋ฉด ๊ธฐ์กด ํŠธ๋žœ์žญ์…˜์„ ๊ฐ™์ด ์“ด๋‹ค.
 @Transactional(propagation = Propagation.REQUIRES_NEW)

์‚ฌ์‹ค ์ „ํŒŒ ์˜ต์…˜์„ ๋ณ€๊ฒฝํ•  ์ผ์€ ์ž˜ ์—†๊ธดํ•˜๋‹ค. ๊ตฌ์กฐ ์ž์ฒด๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š”๊ฒŒ ๋” ๋‚˜์œผ๋‹ˆ๊นŒ

 

๐Ÿ”ฅ ๊ธฐ๋ณธ๊ฐ’ REQUIRED์—์„œ ๊ณ ๋ คํ•ด์•ผํ•˜๋Š” ๋ฌธ์ œ๋“ค

MemberService, MemberRepository, LogRepository์— ๊ฐ๊ฐ ํŠธ๋žœ์žญ์…˜์ด ๊ฑธ๋ ค์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค.

๊ฐœ๋ฐœ์ž๋Š” ๋…ผ๋ฆฌ์ ์œผ๋กœ @Transaction์„ ์ „์ฒด๋กœ ๊ฑธ์—ˆ์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ํ•˜๋‚˜์˜ JDBC ๋ฌผ๋ฆฌ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

 

 

1๏ธโƒฃ (์™ธ๋ถ€ ๋กค๋ฐฑ) ๋‹น์—ฐํžˆ ์ตœ์ƒ์œ„ ํด๋ž˜์Šค์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ์ „์ฒด ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋ฉ๋‹ˆ๋‹ค.

 

 

2๏ธโƒฃ (๋‚ด๋ถ€ ๋กค๋ฐฑ) ์ผ๋ถ€๋งŒ ์„ฑ๊ณตํ•˜๊ณ  ๋‚ด๋ถ€์—์„œ Runtime ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์ „์ฒด๊ฐ€ ๋กค๋ฐฑ๋ฉ๋‹ˆ๋‹ค.

์ด ๊ฒฝ์šฐ try-catch๋กœ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋”๋ผ๋„ UnexpectedRollbackException ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ ์ „์ฒด ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  @Transactional(rollbackOnly=false) ์„ค์ •์ด๋‚˜ (rollbackFor=MyClass.class) ๋กœ ๋ฐ”๊ฟ€ ์ˆœ ์žˆ์ง€๋งŒ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํŠธ๋žœ์žญ์…˜๋™๊ธฐํ™” ๋งค๋‹ˆ์ €๊ฐ€ rollback์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํŠธ๋žœ์žญ์…˜์„ ์ถ”์ƒํ™”ํ•ด์„œ ์—„์ฒญ ํฌ๊ฒŒ ์žก์•˜๋‹ค๋ฉด ๋ฐ”๋กœ ์ฐพ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

 

์ •ํ™•ํžˆ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๊ณผ์ •์„ ๊ฑฐ์ณ ๋กค๋ฐฑ๋ฉ๋‹ˆ๋‹ค.

 

 

 

3๏ธโƒฃ AOP ํŠน์„ฑ์ƒ this๋ฅผ ํ†ตํ•œ ๋‚ด๋ถ€ ํ˜ธ์ถœ์€ ํŠธ๋žœ์žญ์…˜์ด ๊ฑธ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • JVM์€ class, method ๋“ฑ์˜ ์ •๋ณด๋Š” ๋”ฑ ํ•œ๋ฒˆ๋งŒ static ์˜์—ญ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์ดํ›„ ๊ฐ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋งŒ๋“ค์–ด์งˆ ๋•Œ ํž™๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค. 
  • ์ฆ‰ ์ธ์Šคํ„ด์Šค๋Š” class, method๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š๊ณ  ๊ฐ๊ฐ์˜ ํž™ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”๋ชจ๋ฆฌ๋Š” this ๋กœ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Spring AOP๋Š” ์ƒ์†์„ ํ™œ์šฉํ•œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค. this ๋กœ ์ฐธ์กฐํ•ด๋ฒ„๋ฆฌ๋ฉด ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํŠธ๋žœ์žญ์…˜์ด ๊ฑธ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

 

3๏ธโƒฃ ์Šคํ”„๋ง @Transactional ์€ public ๋ฉ”์„œ๋“œ๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

ํด๋ž˜์Šค์— ๋ถ™์ด๋Š”๊ฑด ๊ทธ ํด๋ž˜์Šค๊ฐ€ ๊ฐ€์ง„ ๋ชจ๋“  public ๋ฉ”์„œ๋“œ์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  protected์™€ default ์ ‘๊ทผ์ œ์–ด์ž๋Š” ์ƒ์†์ด ๊ฐ€๋Šฅํ•œ๋ฐ ์Šคํ”„๋ง์ด ์ •์ฑ…์ ์œผ๋กœ ๋ง‰์•„๋’€์Šต๋‹ˆ๋‹ค. ์• ์ดˆ์— ๊ฑฐ๊ธฐ์— ํŠธ๋žœ์žญ์…˜์ด ๊ฑธ์–ด์•ผํ•˜๋Š” ์ƒํ™ฉ ์ž์ฒด๋„ ์—†๊ณ , ๊ทธ๋Ÿฐ์‹์˜ ๋™์ž‘๋„ ์ด์ƒํ•˜๋‹ˆ๊นŒ์š”.

 

๋‹คํ–‰ํžˆ ๋ฉ”์„œ๋“œ์— ์ง์ ‘ ๊ฑธ๋ฉด IntelliJ๊ฐ€ ๋ฐ”๋กœ ๊ฒฝ๊ณ ๋ฅผ ๋„์›Œ์ฃผ๊ธฐ์— ์‹ค์ˆ˜ํ•  ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.

 

 

๊ทธ๋Ÿฐ๋ฐ Class์— @Transactional์„ ๋ถ™์ด๋Š” ๊ฒฝ์šฐ ๋”ฐ๋กœ ์•Œ๋ ค์ฃผ์ง€ ์•Š์œผ๋‹ˆ ์ฃผ์˜ํ•˜๋„๋ก ํ•ฉ์‹œ๋‹ค. ํŠธ๋žœ์žญ์…˜์€ public Method ์—๋งŒ ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค.

์—๋Ÿฌ๊ฐ€ ์•ˆ๋œฌ๋‹ค.

 

 

4๏ธโƒฃ ์ „ํŒŒ์˜ต์…˜์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ.. ๊ทธ๋Ÿฌ์ง€๋ง๊ณ  ๊ณ„์ธต์„ ๋ถ„๋ฆฌํ•ฉ์‹œ๋‹ค.

Required_New ๋“ฑ์œผ๋กœ ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ.. ๋™์ž‘์„ ์˜ˆ์ธกํ•˜๊ธฐ๋„ ์–ด๋ ต๊ณ  ์˜คํžˆ๋ ค ๋” ํฐ ๋ฒ„๊ทธ๋ฅผ ๋งŒ๋“ค์–ด ๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์›๋ž˜๋Š” ์—๋Ÿฌํ„ฐ์ง€๊ณ  ๋ง์•˜๋‹ค๋ฉด, ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ์˜ต์…˜์„ ํ†ตํ•ด ๋˜‘๊ฐ™์€ ํšŒ์›์ด 3๋ฒˆ์”ฉ ์ €์žฅ๋˜๋Š” ์ด์ƒํ•œ ๋ฒ„๊ทธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ๋„ ์žˆ์ฃ 

 

๊ทธ๋ƒฅ ์• ์ดˆ๋ถ€ํ„ฐ ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ตœ์ƒ๋‹จ์— @Transacational์„ ์‹œ์ž‘ํ•˜์ง€ ๋งˆ์„ธ์š”.

 

 

๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•ด์„œ ์œ„ ๊ทธ๋ฆผ์ฒ˜๋Ÿผ ์ปจํŠธ๋กค ํ•  ์ˆ˜ ์—†๋‹ค๋ฉด @TransactionEventListener ๋“ฑ์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

class Service(
    private val eventPublisher: ApplicationEventPublisher
) {
    @Transactional
    fun signUp(dto: MemberSignUpRequest) {
        val member = createMember(dto.toEntity())
        eventPublisher.publishEvent(MemberSignedUpEvent(member)) // ์ด๋ฒคํŠธ ๋ฐœ์†ก
    }
}

class EventHandler{
    // ์•„๋ž˜์˜ ๋ฉ”์„œ๋“œ๋Š” eventPublisher ์ฝ”๋“œ์— ์žˆ๋Š” ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณตํ•ด์•ผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. 
    @TransactionalEventListener
    fun memberSignedUpEventListener(event: MemberSignedUpEvent) {
        emailSenderService.sendSignUpEmail(event.member)
    }
}

 

 

ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๊ฐ€ ์—„์ฒญ ๋ณต์žกํ•ด์„œ ์ƒ์„ธํ•˜๊ฒŒ ๊ฑธ๊ณ ์‹ถ๋‹ค๋ฉด, Spring @Transactional์„ ์‚ฌ์šฉํ•˜์ง€๋ง๊ณ  TransactionTemplate์„ ์ง์ ‘ ์“ฐ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ๋ฒ•๋งŒ ๋‹ค๋ฅผ ๋ฟ ๋˜‘๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. 

@Service
@RequiredArgsConstructor
public class MyService {
    private final TransactionTemplate transactionTemplate;
    
    /** โญ๏ธ Java8 ๋žŒ๋‹ค๋กœ ์ •์˜๋œ ๋ฉ”์„œ๋“œ๋ฅผ ์“ฐ๋ฉด ๋งค์šฐ ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค. */
    void myTransactionalLambda() {
        transactionTemplate.executeWithoutResult(status -> { /*..ํŠธ๋žœ์žญ์…˜ ๋กœ์ง..*/ });
        
        var result = transactionTemplate.execute(status -> { /*..ํŠธ๋žœ์žญ์…˜ ๋กœ์ง..*/ return null; });
    }
    
    /** ์ต๋ช…ํด๋ž˜์Šค๋กœ ์‚ฌ์šฉํ•  ๋• ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค */
    MyResultDto myTransactionalMethod() {
        return transactionTemplate.execute(new TransactionCallback<MyResultDto>() {
            @Override
            public MyResultDto doInTransaction(TransactionStatus status) {
                // ์—ฌ๊ธฐ์— ํŠธ๋žœ์žญ์…˜์„ ์ ์šฉํ•  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
                // ํ•„์š”ํ•œ ๊ฒฝ์šฐ status.setRollbackOnly(); ํ˜ธ์ถœ๋กœ ๋กค๋ฐฑ์„ ์ง€์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
                MyResultDto result = new MyResultDto();
                return result;
            }
        });
    }
 
    void myTransactionalMethod() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                // ์—ฌ๊ธฐ์— ํŠธ๋žœ์žญ์…˜์„ ์ ์šฉํ•  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
                // ํ•„์š”ํ•œ ๊ฒฝ์šฐ status.setRollbackOnly(); ํ˜ธ์ถœ๋กœ ๋กค๋ฐฑ์„ ์ง€์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
            }
        });
    }
}

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

JiwonDev

JiwonDev

ํ™œ๋™ํ•˜๊ธฐ