Spring DB Part II
Spring DB Part II
์ํ๋์ ์คํ๋ง DB 2ํธ - ๋ฐ์ดํฐ ์ ๊ทผ ํ์ฉ ๊ธฐ์ ๊ฐ์๋ฅผ ์์ฝํ ๋ด์ฉ์ ๋๋ค.
Intro
ํ๋ก์ ํธ ๊ตฌ์กฐ
ํ ์คํธ ์ฝ๋
์ธํฐํ์ด์ค๋ฅผ ํ ์คํธํ์
๊ธฐ๋ณธ์ ์ผ๋ก ์ธํฐํ์ด์ค๋ฅผ ๋์์ผ๋ก ํ ์คํธํ๋ฉด ๊ตฌํ์ฒด๊ฐ ๋ณ๊ฒฝ๋์์ ๋ ๊ฐ์ ํ ์คํธ๋ก ํด๋น ๊ตฌํ์ฒด๊ฐ ์ ๋์ํ๋์ง ๊ฒ์ฆ ๊ฐ๋ฅ
์๋ณ์ ์ ํ ์ ๋ต
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ธฐ๋ณธํค๊ฐ ๋ง์กฑํด์ผํ๋ ์กฐ๊ฑด
null ๊ฐ์ ํ์ฉํ์ง ์๋๋ค.
์ ์ผํด์ผ ํ๋ค.
๋ณํด์ ์ ๋๋ค.
ํ ์ด๋ธ์ ๊ธฐ๋ณธํค๋ฅผ ์ ํํ๋ ๋ ๊ฐ์ง ์ ๋ต
์์ฐํค(natural key)
๋น์ฆ๋์ค์ ์๋ฏธ๊ฐ ์๋ ํค (ex. ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ, ์ด๋ฉ์ผ, ์ ํ๋ฒํธ)
๋๋ฆฌํค, ๋์ฒดํค(surrogate key)
๋น์ฆ๋์ค์ ๊ด๋ จ ์๋ ์์๋ก ๋ง๋ค์ด์ง ํค (ex, ์ค๋ผํด ์ํ์ค, auto_increment, identity, ํค์์ฑ ํ ์ด๋ธ ์ฌ์ฉ)
์์ฐํค๋ณด๋ค๋ ๋๋ฆฌํค ๊ถ์ฅ
๊ธฐ๋ณธํค์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ค๋ฉด ๋๋ฆฌํค๊ฐ ์ผ๋ฐ์ ์ผ๋ก ์ข์ ์ ํ
๋น์ฆ๋์ค ํ๊ฒฝ์ ์ธ์ ๊ฐ ๋ณํ๋ค..
Spring JdbcTemplate
๊ฐ๋จํ๊ณ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ
์ฅ์
์ค์ ์ด ํธ๋ฆฌ
spring-boot-starter-jdbc ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง ์ถ๊ฐํ๊ณ ๋ณ๋์ ์ถ๊ฐ ์ค์ ์ ๋ถํ์
๋ฐ๋ณต ๋ฌธ์ ํด๊ฒฐ
ํ ํ๋ฆฟ ์ฝ๋ฐฑ ํจํด์ด ๋๋ถ๋ถ์ ๋ฐ๋ณต ์์ ์ ๋์ ์ฒ๋ฆฌ
SQL ์์ฑ, ํ๋ฆฌ๋ฏธํฐ ์ ์, ์๋ต ๊ฐ ๋งคํ๋ง ํ์
๋์ ์ฒ๋ฆฌํด์ฃผ๋ ๋ฐ๋ณต ์์
์ปค๋ฅ์ ํ๋
statement ์ค๋น/์คํ
๊ฒฐ๊ณผ ๋ฐ๋ณต ๋ฃจํ ์คํ
์ปค๋ฅ์ /statement/resultset ์ข ๋ฃ
ํธ๋์ญ์ ์ ๋ค๋ฃจ๊ธฐ ์ํ ์ปค๋ฅ์ ๋๊ธฐํ
์์ธ ๋ฐ์์ ์คํ๋ง ์์ธ ๋ณํ๊ธฐ ์คํ...
๋จ์
๋์ ์ฟผ๋ฆฌ ์์ฑ์ ์ด๋ ค์(๊ฐ๋ฐ์๊ฐ ์ง์ ์์ฑํด ์ฃผ์ด์ผ ํจ..)
JdbcTemplate
์์ ๊ธฐ๋ฐ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ
NamedParameterJdbcTemplate
์ด๋ฆ ๊ธฐ๋ฐ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ
๋ฐ์ธ๋ฉ์ผ๋ก ์ธํ ๋ฌธ์ ๋ฅผ ์ค์ด๊ธฐ ์ํด NamedParameterJdbcTemplate๋ SQL์์
?
๋์:parameterName
์ ์ฌ์ฉ์ฝ๋๋ฅผ ์ค์ด๋ ๊ฒ๋ ์ค์ํ์ง๋ง, ๋ชจํธํจ์ ์ ๊ฑฐํด์ ์ฝ๋๋ฅผ ๋ช ํํ๊ฒ ๋ง๋๋ ๊ฒ์ด ์ ์ง๋ณด์ ๊ด์ ์์ ๋งค์ฐ ์ค์
SqlParameterSource
BeanPropertySqlParameterSource
์๋์ผ๋ก ํ๋ผ๋ฏธํฐ ๊ฐ์ฒด๋ฅผ ์์ฑ
getXXX()๋ฅผ ํ์ฉํด ์๋ ์์ฑ
String sql = "insert into item (item_name, price, quantity) " +
"values (:itemName, :price, :quantity)";
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(sql, param, keyHolder);
MapSqlParameterSource
SQL์ ๋ ํนํ๋ ๊ธฐ๋ฅ ์ ๊ณต
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); // ๋ณ๋๋ก ํ์
Map
String sql = "select id, item_name, price, quantity from item where id = :id ";
Map<String, Object> param = Map.of("id", id);
Item item = template.queryForObject(sql, param, itemRowMapper());
BeanPropertyRowMapper
ResultSet ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์ ์๋ฐ๋น ๊ท์ฝ์ ๋ง์ถฐ ๋ฐ์ดํฐ ๋ณํ
์ธ๋์ค์ฝ์ด ํ๊ธฐ๋ฒ์ ์นด๋ฉ๋ก ์๋ ๋ณํ
BeanPropertyRowMapper<Item> rowMapper = BeanPropertyRowMapper.newInstance(Item.class);
SimpleJdbcInsert
INSERT SQL์ ํธ์๊ธฐ๋ฅ ์ ๊ณต
์์ฑ ์์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์ ๋ฉํ ๋ฐ์ดํฐ๋ฅผ ์กฐํํด์ ํ ์ด๋ธ์ ์ด๋ค ์ปฌ๋ผ์ด ์๋์ง ํ์ธ
withTableName
: ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ํ ์ด๋ธ๋ช ์ง์ usingGeneratedKeyColumns
: key๋ฅผ ์์ฑํ๋ PK ์ปฌ๋ผ๋ช ์ง์ usingColumns
: INSERT SQL์ ์ฌ์ฉํ ์ปฌ๋ผ ์ง์ (์๋ ๊ฐ๋ฅ)ํน์ ์ปฌ๋ผ๋ง ์ง์ ํด์ ์ ์ฅํ๊ณ ์ถ์ ๊ฒฝ์ฐ ์ฌ์ฉ
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");
}
@Override
public Item save(Item item) {
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
Number key = jdbcInsert.executeAndReturnKey(param);
item.setId(key.longValue());
return item;
}
SimpleJdbcCall
์คํ ์ด๋ ํ๋ก์์ ๋ฅผ ํธ๋ฆฌํ๊ฒ ํธ์ถ
Calling a Stored Procedure with SimpleJdbcCall
Using JdbcTemplate
์กฐํ
๋จ๊ฑด: .queryForObject
, ๋ชฉ๋ก: .query
๋จ๊ฑด ์กฐํ (์ซ์)
int rowCount = jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
๋จ๊ฑด ์กฐํ (์ซ์ ์กฐํ, ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ)
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) -> { // ๊ฒฐ๊ณผ ๊ฐ์ฒด ๋งคํ์ ์ํด RowMapper ์ฌ์ฉ
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;
});
๋ณ๊ฒฝ(INSERT, UPDATE, DELETE)
.update()
, (๋ฐํ๊ฐ int๋ SQL ์คํ ๊ฒฐ๊ณผ์ ์ํฅ๋ฐ์ ๋ก์ฐ ์)
๋ฑ๋ก
jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
์์
jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
์ญ์
jdbcTemplate.update(
"delete from t_actor where id = ?",
Long.valueOf(actorId));
๊ธฐํ ๊ธฐ๋ฅ
DDL
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
์คํ ์ด๋ ํ๋ก์์ ํธ์ถ
jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));
๐ JDBC TEST
@SpringBootTest
๋ @SpringBootApplication
์ ์ฐพ๊ณ ํด๋น ์ค์ ์ ์ฌ์ฉ
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถ๋ฆฌ
DB ์์ฑ: jdbc:h2:~/testcase
DB ์ ์: jdbc:h2:tcp://localhost/~/testcase
ํ ์คํธ์ ์ค์ํ ์์น
ํ ์คํธ๋ ๋ค๋ฅธ ํ ์คํธ์ ๊ฒฉ๋ฆฌํด์ผ ํ๋ค.
ํ ์คํธ๋ ๋ฐ๋ณตํด์ ์คํํ ์ ์์ด์ผ ํ๋ค
๋ฐ์ดํฐ ๋กค๋ฐฑ
ํธ๋์ญ์ ๊ด๋ฆฌ์๋
PlatformTransactionManager
๋ฅผ ์ฃผ์ ๋ฐ์์ ์ฌ์ฉ์คํ๋ง ๋ถํธ๋ ์ ์ ํ ํธ๋์ญ์ ๋งค๋์ ๋ฅผ ์คํ๋ง ๋น์ผ๋ก ์๋ ๋ฑ๋ก
@BeforeEach
: ๊ฐ์ ํ ์คํธ ์ผ์ด์ค ์คํ ์ง์ ์ ํธ์ถ(ํธ๋์ญ์ ์์ ์์น)transactionManager.getTransaction(new DefaultTransactionDefinition())
@AfterEach
: ๊ฐ์ ํ ์คํธ ์ผ์ด์ค ์๋ฃ ์งํ์ ํธ์ถ(ํธ๋์ญ์ ๋กค๋ฐฑ ์์น)transactionManager.rollback(status)
๐ @Transactionanl
Spring @Transactional์ ๋ก์ง์ด ์ฑ๊ณต์ ์ผ๋ก ์ํ๋๋ฉด ์ปค๋ฐ์ด ๋์ํ์ง๋ง
ํ ์คํธ์์ ์ฌ์ฉํ๋ฉด ํ ์คํธ๋ฅผ ํธ๋์ญ์ ์์์ ์คํํ๊ณ , ํ ์คํธ๊ฐ ๋๋๋ฉด ํธ๋์ญ์ ์ ์๋์ผ๋ก ๋กค๋ฐฑ
๊ฐ์ ๋ก ์ปค๋ฐ์ ํ๊ณ ์ถ์ ๊ฒฝ์ฐ์๋,
@Commit
๋๋@Rollback(value = false)
๋ฅผ ๊ฐ์ด ์ฌ์ฉ
๐ Embedded mode DB
H2 ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ JVM ์์์ ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋๋ก ๋์ํ๋ ๊ธฐ๋ฅ์ ์ ๊ณต
DB๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ด์ฅํด์ ํจ๊ป ์คํ
Spring Boot๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ์ค์ ์ด ์์ผ๋ฉด ์๋ฒ ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉ (commit)
dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
jdbc:h2:mem:db
: ์๋ฒ ๋๋(๋ฉ๋ชจ๋ฆฌ) ๋ชจ๋๋ก ๋์ํ๋ H2 ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉDB_CLOSE_DELAY=-1
: ์๋ฒ ๋๋ ๋ชจ๋์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ฐ๊ฒฐ์ด ๋ชจ๋ ๋์ด์ง๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์ข ๋ฃ๋๋ ํ์์ ๋ฐฉ์ง
MyBatis
๊ธฐ๋ณธ์ ์ผ๋ก JdbcTemplate์ด ์ ๊ณตํ๋ ๋๋ถ๋ถ ๊ธฐ๋ฅ ์ ๊ณต
SQL์ XML์ ์์ฑํ๊ณ ๋์ ์ฟผ๋ฆฌ๋ฅผ ํธ๋ฆฌํ๊ฒ ์์ฑํ ์ ์๋ ์ฅ์
๋์ ์ฟผ๋ฆฌ์ ๋ณต์กํ ์ฟผ๋ฆฌ๊ฐ ๋ง๋ค๋ฉด
MyBatis
, ๋จ์ํ ์ฟผ๋ฆฌ๊ฐ ๋ง์ผ๋ฉดJdbcTemplate
์ ํ
์ค์
mybatis.type-aliases-package
ํ์ ์ ๋ณด ์ฌ์ฉ ์ ํจํค์ง ์ด๋ฆ ์๋ต์ ์ํ ์ค์ (์ง์ ํ ํจํค์ง์ ๊ทธ ํ์ ํจํค์ง๊ฐ ์๋์ผ๋ก ์ธ์)
์ฌ๋ฌ ์์น ์ง์ ์
,
,;
๋ก ๊ตฌ๋ถ
mybatis.configuration.map-underscore-to-camel-case
JdbcTemplate#BeanPropertyRowMapper์ฒ๋ผ ์ธ๋๋ฐ๋ฅผ ์นด๋ฉ๋ก ์๋ ๋ณ๊ฒฝํด์ฃผ๋ ๊ธฐ๋ฅ ํ์ฑํ
logging.level.hello.itemservice.repository.mybatis=trace
MyBatis์์ ์คํ๋๋ ์ฟผ๋ฆฌ ๋ก๊ทธ ํ์ธ์ ์ํ ์ค์
์ ์ฉ
XML ํ์ผ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ ๊ฒฝ์ฐ
resources/mapper ๋ฅผ ํฌํจํ ๊ทธ ํ์ ํด๋์ ์๋ XML
application.properties
mybatis.mapper-locations=classpath:mapper/**/*.xml
INSERT
<insert>
id
: Mapper Class์ ์ค์ ํ ๋ฉ์๋ ์ด๋ฆ ์ง์ ํ๋ผ๋ฏธํฐ
: #{} ๋ฌธ๋ฒ์ ์ฌ์ฉํ๊ณ ๋งคํผ์์ ๋๊ธด ๊ฐ์ฒด์ ํ๋กํผํฐ ์ด๋ฆ์ ๊ธฐ์#{}
: PreparedStatement ๋ฅผ ์ฌ์ฉ(like. JDBC ? ์นํ)useGeneratedKeys
: ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ํค๋ฅผ ์์ฑํด ์ฃผ๋ IDENTITY ์ ๋ต์ผ ๋ ์ฌ์ฉkeyProperty
: ์์ฑ๋๋ ํค์ ์์ฑ ์ด๋ฆ ์ง์
UPDATE
<update>
ํ๋ผ๋ฏธํฐ๊ฐ ํ ๊ฐ๋ง ์์ผ๋ฉด
@Param
์ ์ง์ ํ์ง ์์๋ ๋์ง๋ง, ๋ ๊ฐ ์ด์์ด๋ฉด@Param
์ผ๋ก ์ด๋ฆ์ ์ง์ ํด์ ํ๋ผ๋ฏธํฐ ๊ตฌ๋ถ
SELECT
<select>
resultType
: ๋ฐํ ํ์ ๋ช ์(๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ฒด์ ๋งคํ) -> ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ฒด๋ก ๋ฐ๋ก ๋ณํ๋ฐํ ๊ฐ์ฒด๊ฐ ํ๋์ด๋ฉด Item, Optional ์ฌ์ฉ, ํ๋ ์ด์์ด๋ฉด ์ปฌ๋ ์ ์ฌ์ฉ
๋์ ์ฟผ๋ฆฌ
<if>
: ํด๋น ์กฐ๊ฑด์ด ๋ง์กฑํ๋ฉด ๊ตฌ๋ฌธ์ ์ถ๊ฐ<where>
: ์ ์ ํ๊ฒ where ๋ฌธ์ฅ ์์ฑ
ํน์๋ฌธ์
< : < > : > & : & and price <![CDATA[<=]]> #{maxPrice}
์คํ
ItemRepository ๋ฅผ ๊ตฌํํ
MyBatisItemRepository
์์ฑ๋จ์ํ ItemMapper ์ ๊ธฐ๋ฅ์ ์์
Mapper Interface ์ ๋์
์ ํ๋ฆฌ์ผ์ด์ ๋ก๋ฉ ์์ ์ MyBatis ์คํ๋ง ์ฐ๋ ๋ชจ๋์ @Mapper๊ฐ ๋ถ์ ์ธํฐํ์ด์ค๋ฅผ ํ์
ํด๋น ์ธํฐํ์ด์ค๊ฐ ๋ฐ๊ฒฌ๋๋ฉด ๋์ ํ๋ก์ ๊ธฐ์ ์ ์ฌ์ฉํด์ Mapper Interface์ ๊ตฌํ์ฒด ์์ฑ(class
com.sun.proxy.$Proxy66
)์์ฑ๋ ๊ตฌํ์ฒด๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก
MyBatis ์คํ๋ง ์ฐ๋ ๋ชจ๋
์ธํฐํ์ด์ค๋ง์ผ๋ก XML ๋ฐ์ดํฐ๋ฅผ ์ฐพ์์ ํธ์ถ (Mapper ๊ตฌํ์ฒด ์ฌ์ฉ)
Mapper ๊ตฌํ์ฒด๋ ์คํ๋ง ์์ธ ์ถ์ํ๋ ํจ๊ป ์ ์ฉ
MyBatis์์ ๋ฐ์ํ ์์ธ๋ฅผ DataAccessException(์คํ๋ง ์์ธ ์ถ์ํ)์ ๋ง๊ฒ ๋ณํ
JdbcTemplate์ ๊ธฐ๋ณธ์ ์ธ ์ค์ ๋ค์ ๋ชจ๋ ์๋์ผ๋ก ์ค์ (๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ , ํธ๋์ญ์ ๊ด๋ จ ๊ธฐ๋ฅ ๋ฑ..)
MyBatis ์คํ๋ง ์ฐ๋ ๋ชจ๋์ด ์๋์ผ๋ก ๋ฑ๋กํด์ฃผ๋ ๋ถ๋ถ์
MybatisAutoConfiguration
class ์ฐธ๊ณ
๊ธฐ๋ฅ
if
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = โACTIVEโ
<if test="title != null">
AND title like #{title}
</if>
</select>
choose (when, otherwise)
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = โACTIVEโ
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim (where, set)
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
<where>
๋ ๋ฌธ์ฅ์ด ์์ผ๋ฉดwhere
๋ฅผ ์ถ๊ฐ (๋ง์ฝ and๊ฐ ๋จผ์ ์์๋๋ค๋ฉด and๋ฅผ ์ ๊ฑฐ)
foreach
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
์ฌ์ฌ์ฉ์ ์ํ SQL ์กฐ๊ฐ
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
Result Maps
์ปฌ๋ผ๋ช ๊ณผ ๊ฐ์ฒด ํ๋กํผํฐ๋ช ์ด ๋ค๋ฅผ ๊ฒฝ์ฐ ์ ์ฉ
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="username"/>
<result property="password" column="password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
JPA
SQL ์ค์ฌ์ ์ธ ๊ฐ๋ฐ์ ๋ฌธ์ ์
SQL์ ์์กด์ ์ธ ๊ฐ๋ฐ
CRUD ์ฝ๋์ ๋ฐ๋ณต
ํ๋ ์์ ์ ๋ง์ ์์ SQL ์์ ์ด ํ์
๊ฐ์ฒด์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ์ ํจ๋ฌ๋ค์ ๋ถ์ผ์น
๊ฐ์ฒด <-> SQL ๋งคํ ์์ ์ ๋ง์ ๋ ธ๋ ฅ์ด ํ์
๊ณ์ธต๋ถํ ์ ์ด๋ ค์
์ฒ์ ์คํํ๋ SQL์ ๋ฐ๋ผ ํ์ ๋ฒ์ ๊ฒฐ์
๊ฐ์ฒด ๊ทธ๋ํ ํ์์์ ์ํฐํฐ ์ ๋ขฐ ๋ฌธ์
JPA(Java Persistence API)
์๋ฐ ์ง์์ ORM(Object-relational mapping) ๊ธฐ์ ํ์ค
์ ํ๋ฆฌ์ผ์ด์ ๊ณผ JDBC ์ฌ์ด์์ ๋์
์ฅ์
๊ฐ์ฒด ์ค์ฌ ๊ฐ๋ฐ(์์ฐ์ฑ, ์ ์ง๋ณด์)
์์, ์ฐ๊ด๊ด๊ณ
ํจ๋ฌ๋ค์์ ๋ถ์ผ์น ํด๊ฒฐ
๊ฐ์ฒด ๊ทธ๋ํ ํ์
์ฑ๋ฅ ์ต์ ํ ๊ธฐ๋ฅ
1์ฐจ ์บ์์ ๋์ผ์ฑ ๋ณด์ฅ
์ฐ๊ธฐ ์ง์ฐ(insert query ๋ชจ์ผ๊ธฐ)
์ง์ฐ ๋ก๋ฉ(๊ฐ์ฒด ์ค์ ์ฌ์ฉ ์ ๋ก๋ฉ)
๋ฐ์ดํฐ ์ ๊ทผ ์ถ์ํ ๋ ๋ฆฝ์ฑ
ํ์ค
์ค์
application.properties
org.hibernate.SQL=DEBUG
: hibernate๊ฐ ์์ฑํ๊ณ ์คํํ๋ SQL ํ์ธorg.hibernate.type.descriptor.sql.BasicBinder=TRACE
: SQL์ ๋ฐ์ธ๋ฉ ๋๋ ํ๋ผ๋ฏธํฐ ํ์ธ
๊ฐ๋ฐ
JPA์์ ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ ๊ฐ์ฒด์ ํ ์ด๋ธ์ ๋งคํํ๋ ๊ฒ
์์ธ
EntityManager๋ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด JPA ๊ด๋ จ ์์ธ๋ฅผ ๋ฐ์
@Repository
๋ฅผ ํตํด ์คํ๋ง์ด ์์ธ ๋ณํ์ ์ฒ๋ฆฌํ๋ AOP ์์ฑ
JPA๋
PersistenceException
๊ณผ ๊ทธ ํ์ ์์ธ๋ฅผ ๋ฐ์์ถ๊ฐ๋ก
IllegalStateException
,IllegalArgumentException
๋ฐ์
@Repository์ ๊ธฐ๋ฅ
์ปดํฌ๋ํธ ์ค์บ์ ๋์ + ์์ธ ๋ณํ AOP ์ ์ฉ ๋์
์คํ๋ง + JPA ์ฌ์ฉ ์ ์คํ๋ง์ JPA ์์ธ ๋ณํ๊ธฐ(PersistenceExceptionTranslator) ๋ฑ๋ก
์์ธ ๋ณํ AOP Proxy๋ JPA ๊ด๋ จ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด JPA ์์ธ ๋ณํ๊ธฐ๋ฅผ ํตํด ๋ฐ์ํ ์์ธ๋ฅผ ์คํ๋ง ๋ฐ์ดํฐ ์ ๊ทผ ์์ธ๋ก ๋ณํ (PersistenceException -> DataAccessException)
์ค์ JPA ์์ธ๋ฅผ ๋ณํํ๋ ์ฝ๋:
EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible()

Spring Data JPA
JPA๋ฅผ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๋ํ์ ์ธ ๊ธฐ๋ฅ
๊ณตํต ์ธํฐํ์ด์ค ๊ธฐ๋ฅ
์ฟผ๋ฆฌ ๋ฉ์๋ ๊ธฐ๋ฅ
์ฐธ๊ณ
Spring Data JPA๊ฐ Repository ๊ตฌํ ํด๋์ค(Proxy)๋ฅผ ์๋์ผ๋ก ์์ฑํ๊ณ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก
์คํ๋ง ์์ธ ์ถ์ํ ์ง์ (Spring Data JPA๊ฐ ๋ง๋ค์ด์ฃผ๋ Proxy์์ ์์ธ ๋ณํ์ ์ฒ๋ฆฌ)
์ ์ฉ
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
JPA, hibernate, Spring Data JPA, Spring JDBC ๊ธฐ๋ฅ์ด ๋ชจ๋ ํฌํจ
Spring Data
Repository interface
@Indexed
public interface Repository<T, ID> {
}
CrudRepository interface
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAllById(Iterable<? extends ID> ids);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
PagingAndSortingRepository interface
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
Spring Data JPA
JpaRepository interface
๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋ฅ ์ ๊ณต
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
@Override
List<T> findAll();
@Override
List<T> findAll(Sort sort);
@Override
List<T> findAllById(Iterable<ID> ids);
@Override
<S extends T> List<S> saveAll(Iterable<S> entities);
void flush();
<S extends T> S saveAndFlush(S entity);
<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
void deleteAllInBatch(Iterable<T> entities);
void deleteAllByIdInBatch(Iterable<ID> ids);
void deleteAllInBatch();
T getOne(ID id);
T getById(ID id);
@Override
<S extends T> List<S> findAll(Example<S> example);
@Override
<S extends T> List<S> findAll(Example<S> example, Sort sort);
}
Using JpaRepository example
public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
List<Item> findByItemNameLike(String itemName);
List<Item> findByPriceLessThanEqual(Integer price);
// Query Method (์๋ ๋ฉ์๋์ ๊ฐ์ ๊ธฐ๋ฅ ์ํ)
List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);
// JPQL
@Query("select i from Item i where i.itemName like :itemName and i.price <=:price")
List<Item> findItems(@Param("itemName") String itemName, @Param("price") Integer price);
}
QueryDSL
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์ ์
์ผ๋ฐ ์ฟผ๋ฆฌ๋ ๋ฌธ์์ด๋ฏ๋ก Type-check๊ฐ ๋ถ๊ฐ๋ฅํ๊ณ , ์คํ ์ ๊น์ง๋ ์๋ ์ฌ๋ถ ํ์ธ ๋ถ๊ฐ
์ฟผ๋ฆฌ๋ฅผ Java๋ก type-safeํ๊ฒ ๊ฐ๋ฐํ ์ ์๋๋ก ์ง์ํ๋ ํ๋ ์์ํฌ๊ฐ QueryDSL -> ์ฃผ๋ก JPQL์ ์ฌ์ฉ
ํด๊ฒฐ
DSL(DomainSpecificLanguage) : ํน์ ๋๋ฉ์ธ์ ํนํ๋์ด ์ ํ์ ์ธ ํํ๋ ฅ์ ๊ฐ์ง ํ๋ก๊ทธ๋๋ฐ ์ธ์ด
QueryDSL(QueryDomainSpecificLanguage) : ์ฟผ๋ฆฌ์ ํนํ๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด
JPA, MongoDB, SQL ๊ฐ์ ๊ธฐ์ ๋ค์ ์ํด type-safe SQL์ ๋ง๋๋ ํ๋ ์์ํฌ
type-safe, ๋จ์, ์ฌ์ด ์ฅ์
Q์ฝ๋ ์์ฑ์ ์ํ APT(Annotation Processing Tool) ์ค์ ์ด ํ์
์ค์
Build Tool์ ๋ฐ๋ฅธ QClass ์์ฑ ๋ฐฉ๋ฒ
Gradle : Gradle์ ํตํด ๋น๋
Gradle IntelliJ
Gradle -> Tasks -> build -> clean
Gradle -> Tasks -> other -> compileJava
Gradle Console
./gradlew clean compileJava
build/generated/sources/annotationProcessor ํ์์ ์์ฑ
IntelliJ IDEA : IntelliJ๊ฐ ์ง์ ์๋ฐ๋ฅผ ์คํํด์ ๋น๋
Build Project / Start
src/main/generated ํ์์ ์์ฑ
์ ์ฉ
Querydsl ์ฌ์ฉ์ ์ํด JPAQueryFactory ํ์
JPAQueryFactory ๋ JPA ์ฟผ๋ฆฌ์ธ JPQL์ ๋ง๋ค๊ธฐ ์ํด EntityManager ํ์
JdbcTemplate ์ค์ ๊ณผ ์ ์ฌ
ํ์ฉ ๋ฐฉ์
๊ตฌ์กฐ์ ์์ ์ฑ vs ๋จ์ํ ๊ตฌ์กฐ์ ๊ฐ๋ฐ์ ํธ๋ฆฌ์ฑ
Trade Off
DI, OCP ๋ฅผ ์งํค๊ธฐ ์ํด ์ด๋ํฐ๋ฅผ ๋์ ํ๊ณ , ๋ ๋ง์ ์ฝ๋๋ฅผ ์ ์ง
์ด๋ํฐ ์ ๊ฑฐ๋ก ๊ตฌ์กฐ๊ฐ ๋จ์ํด ์ง์ง๋ง, DI, OCP๋ฅผ ํฌ๊ธฐํ๊ณ , Service ์ฝ๋๋ฅผ ์ง์ ๋ณ๊ฒฝ
๋ค๋ง, ์ํฉ์ ๋ฐ๋ผ์ ๊ตฌ์กฐ์ ์์ ์ฑ์ด ์ค์ํ ์๋ ์๊ณ , ๋จ์ํจ์ด ๋ ๋์ ์ ํ์ผ ์ ์๋ค.
์ถ์ํ ๋น์ฉ์ ๋์ด์ค ๋งํผ ํจ๊ณผ๊ฐ ์์ ๊ฒฝ์ฐ ์ถ์ํ ๋์ ์ด ์ค์ฉ์
์ํฉ์ ๋ง๋ ์ ํ์ด ์ค์
๋จผ์ , ๊ฐ๋จํ๊ณ ๋น ๋ฅด๊ฒ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ํํ๊ณ , ์ดํ ๋ฆฌํํ ๋ง์ ์ถ์ฒ
์ค์ฉ์ ์ธ ๊ตฌ์กฐ
SpringDataJPA์ QueryDSL Repository๋ฅผ ๋ถ๋ฆฌํด์ ๊ธฐ๋ณธ CRUD์ ๋จ์ ์กฐํ๋ SpringDataJPA ๋ด๋น, ๋ณต์กํ ์กฐํ ์ฟผ๋ฆฌ๋ Querydsl ๋ด๋น
๋ฐ์ดํฐ ์ ๊ทผ ๊ธฐ์ ์กฐํฉ
JPA, SpringDataJPA, Querydsl ์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๊ณ , ๋ณต์กํ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ, ํด๋น ๋ถ๋ถ์๋ JdbcTemplate ์ด๋ MyBatis ๋ฅผ ํจ๊ป ์ฌ์ฉ
ํธ๋์ญ์ ๋งค๋์ง์ ๊ฒฝ์ฐ
JpaTransactionManager
ํ๋๋ง ์คํ๋ง ๋น์ ๋ฑ๋กํ๋ฉด, JPA, JdbcTemplate, MyBatis ๋ฅผ ํ๋์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ด์ ์ฌ์ฉ ๊ฐ๋ฅJPA, JdbcTemplate์ ํจ๊ป ์ฌ์ฉํ ๊ฒฝ์ฐ JPA์ ํ๋ฌ์ ํ์ด๋ฐ์ด ๋ค๋ฅด๋ค๋ฉด ๋ณ๊ฒฝํ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ง ๋ชปํ ์ ์์
JPA๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํธ๋์ญ์ ์ด ์ปค๋ฐ๋๋ ์์ ์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์
JPA ํธ์ถ์ด ๋๋ ์์ ์ ํ๋ฌ์๋ฅผ ์ฌ์ฉํ๊ณ , JdbcTemplate ๋ฅผ ํธ์ถํ์ฌ ํด๊ฒฐ ๊ฐ๋ฅ
Spring Transaction
Spring Transaction ์ถ์ํ
PlatformTransactionManager
์ธํฐํ์ด์ค๋ฅผ ํตํด ํธ๋์ญ์ ์ถ์ํ๋ฐ์ดํฐ ์ ๊ทผ ๊ธฐ์ ๋ง๋ค ๋ชจ๋ ๋ค๋ฅธ ํธ๋์ญ์ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ถ์ํ
package org.springframework.transaction;
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
Spring์ Transaction์ ์ถ์ํํด์ ์ ๊ณตํ๊ณ , ๋ฐ์ดํฐ ์ ๊ทผ ๊ธฐ์ ์ ๋ํ TransactionManager์ ๊ตฌํ์ฒด๋ ์ ๊ณต
์ฌ์ฉ์๋ ํ์ํ ๊ตฌํ์ฒด๋ฅผ Spring Bean์ผ๋ก ๋ฑ๋กํ๊ณ , ์ฃผ์ ๋ฐ์์ ์ฌ์ฉ
Spring Boot๋ ์ด๋ค ๋ฐ์ดํฐ ์ ๊ทผ ๊ธฐ์ ์ ์ฌ์ฉํ๋์ง๋ฅผ ์๋์ผ๋ก ์ธ์ํด์ ์ ์ ํ TransactionManager ์ ํ ๋ฐ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก (์ ํ, ๋ฑ๋ก ๊ณผ์ ์๋ต)
JdbcTemplate, MyBatis ์ฌ์ฉ ์
DataSourceTransactionManager(JdbcTransactionManager)
๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กJPA ์ฌ์ฉ ์
JpaTransactionManager
์ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก
์ฌ์ฉ ๋ฐฉ์
์ ์ธ์ ํธ๋์ญ์ ๊ด๋ฆฌ vs ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ํธ๋์ญ์ ๊ด๋ฆฌ
์ ์ธ์ ํธ๋์ญ์
๊ด๋ฆฌ
(Declarative Transaction Management)
@Transactional
ํ๋๋ง ์ ์ธํ์ฌ ํธ๋ฆฌํ๊ฒ ํธ๋์ญ์ ์ ์ ์ฉ(๊ณผ๊ฑฐ์๋ XML์ ์ค์ )์ด๋ฆ ๊ทธ๋๋ก "ํด๋น ๋ก์ง์ ํธ๋์ญ์ ์ ์ ์ฉํ๊ฒ ๋ค."๋ผ๊ณ ์ ์ธํ๋ฉด ํธ๋์ญ์ ์ด ์ ์ฉ๋๋ ๋ฐฉ์
๊ธฐ๋ณธ์ ์ผ๋ก ํ๋ก์ ๋ฐฉ์์ AOP ์ ์ฉ
ํธ๋์ญ์ ์ ์ฒ๋ฆฌํ๋ ๊ฐ์ฒด์ ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ ์๋น์ค ๊ฐ์ฒด๋ฅผ ๋ช ํํ๊ฒ ๋ถ๋ฆฌ

ํธ๋์ญ์ ์ ์ปค๋ฅ์ ์
setAutocommit(false)
์ง์ ์ผ๋ก ์์๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ ์ฌ์ฉํ์ฌ ๊ฐ์ ํธ๋์ญ์ ์ ์ ์งํ๊ธฐ ์ํด ์คํ๋ง ๋ด๋ถ์์๋ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ฅผ ์ฌ์ฉ
JdbcTemplate์ ํฌํจํ ๋๋ถ๋ถ์ ๋ฐ์ดํฐ ์ ๊ทผ ๊ธฐ์ ๋ค์ ํธ๋์ญ์ ์ ์ ์งํ๊ธฐ ์ํด ๋ด๋ถ์์ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ฅผ ํตํด ๋ฆฌ์์ค(์ปค๋ฅ์ )๋ฅผ ๋๊ธฐํ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ ํธ๋์ญ์
๊ด๋ฆฌ
(programmatic transaction management)
TransactionManager ๋๋ TransactionTemplate ๋ฑ์ ์ฌ์ฉํด์ ํธ๋์ญ์ ๊ด๋ จ ์ฝ๋๋ฅผ ์ง์ ์์ฑ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ ํธ๋์ญ์ ๊ด๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด, ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋๊ฐ ํธ๋์ญ์ ์ด๋ผ๋ ๊ธฐ์ ์ฝ๋์ ๊ฐํ๊ฒ ๊ฒฐํฉ๋๋ ๋จ์
์ ์ธ์ ํธ๋์ญ์ ๊ด๋ฆฌ๊ฐ ํจ์ฌ ๊ฐํธํ๊ณ ์ค์ฉ์ ์ด๊ธฐ ๋๋ฌธ์ ์ค๋ฌด์์๋ ๋๋ถ๋ถ ์ ์ธ์ ํธ๋์ญ์ ๊ด๋ฆฌ๋ฅผ ์ฌ์ฉ
์ ์ฉ
AOP ์ ์ฉ ๋ฐฉ์์ ๋ฐ๋ผ์ ์ธํฐํ์ด์ค์ @Transactional ์ ์ธ ์ AOP๊ฐ ์ ์ฉ์ด ๋์ง ์๋ ๊ฒฝ์ฐ๋ ์์ผ๋ฏ๋ก, ๊ฐ๊ธ์ ๊ตฌ์ฒด ํด๋์ค์ @Transactional ์ฌ์ฉ ๊ถ์ฅ
Transaction ์ ์ฉ ํ์ธ
TransactionSynchronizationManager.isActualTransactionActive();
TransactionSynchronizationManager.isCurrentTransactionReadOnly();
ํธ๋์ญ์ ํ๋ก์๊ฐ ํธ์ถํ๋ ํธ๋์ญ์ ๋ก๊ทธ ํ์ธ์ ์ํ ์ค์
logging.level.org.springframework.transaction.interceptor=TRACE
Getting transaction for [hello.springtx.apply...BasicService.tx]
.. ์ค์ ๋ฉ์๋ ํธ์ถ
.. ํธ๋์ ์
๋ก์ง ์ปค๋ฐ ๋๋ ๋กค๋ฐฑ
Completing transaction for [hello.springtx.apply...BasicService.tx]

@Transactional ์ด ํน์ ํด๋์ค๋ ๋ฉ์๋์ ์๋ค๋ฉด, Transaction AOP๋ ํ๋ก์๋ฅผ ๋ง๋ค์ด์ ์คํ๋ง ์ปจํ ์ด๋์ ๋ฑ๋ก -> ์ค์ ๊ฐ์ฒด ๋์ ํ๋ก์๋ฅผ ์คํ๋ง ๋น์ ๋ฑ๋ก๋๊ณ ํ๋ก์๋ ๋ด๋ถ์ ์ค์ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐ
ํ๋ก์๋ ๊ฐ์ฒด๋ฅผ ์์ํด์ ๋ง๋ค์ด์ง๊ธฐ ๋๋ฌธ์ ๋คํ์ฑ์ ํ์ฉ
์ ์ฉ ์์น
์คํ๋ง์์ ์ฐ์ ์์๋ ํญ์ ๋ ๊ตฌ์ฒด์ ์ด๊ณ ์์ธํ ๊ฒ์ด ๋์ ์ฐ์ ์์๋ฅผ ๊ฐ์ง.
ํด๋์ค์ ์ ์ฉํ๋ฉด ๋ฉ์๋๋ ์๋ ์ ์ฉ
์ฃผ์์ฌํญ
@Transactional์ ์ ์ธํ๋ฉด
์คํ๋ง ํธ๋์ญ์ AOP
์ ์ฉํธ๋์ญ์ AOP๋ ๊ธฐ๋ณธ์ ์ผ๋ก
ํ๋ก์ ๋ฐฉ์์ AOP
์ฌ์ฉ
์คํ๋ง์ ๋์ ๊ฐ์ฒด ๋์ ํ๋ก์๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ฏ๋ก ํ๋ก์ ๊ฐ์ฒด๊ฐ ์์ฒญ์ ๋จผ์ ๋ฐ๊ณ , ํ๋ก์ ๊ฐ์ฒด์์ ํธ๋์ญ์ ์ฒ๋ฆฌ์ ์ค์ ๊ฐ์ฒด ํธ์ถ
๋ฐ๋ผ์, ํธ๋์ญ์ ์ ์ ์ฉํ๋ ค๋ฉด ํญ์ ํ๋ก์๋ฅผ ํตํด์ ๋์ ๊ฐ์ฒด๋ฅผ ํธ์ถํด์ผ ํจ
โญ๏ธ ๋ง์ฝ, ํ๋ก์๋ฅผ ๊ฑฐ์น์ง ์๊ณ ๋์ ๊ฐ์ฒด๋ฅผ ์ง์ ํธ์ถํ๊ฒ ๋๋ฉด AOP๊ฐ ์ ์ฉ๋์ง ์๊ณ , ํธ๋์ญ์ ๋ ์ ์ฉ๋์ง ์๋๋ค.
๋์ ๊ฐ์ฒด์ ๋ด๋ถ์์ ๋ฉ์๋ ํธ์ถ์ด ๋ฐ์ํ๋ฉด ํ๋ก์๋ฅผ ๊ฑฐ์น์ง ์๊ณ ๋์ ๊ฐ์ฒด๋ฅผ ์ง์ ํธ์ถํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์
ํ๋ก์ ํธ์ถ
@Transactional
public void internal() {
log.info("call internal");
}

ํด๋ผ์ด์ธํธ๊ฐ service.internal()์ ํธ์ถํ๋ฉด service์ ํธ๋์ญ์ ํ๋ก์ ํธ์ถ
internal() ๋ฉ์๋์ @Transactional์ด ์ ์ธ๋์ด ์์ผ๋ฏ๋ก ํธ๋์ญ์ ํ๋ก์๋ ํธ๋์ญ์ ์ ์ ์ฉ
ํธ๋์ญ์ ์ ์ฉ ํ ์ค์ service ๊ฐ์ฒด ์ธ์คํด์ค์ internal() ํธ์ถ
์ค์ service๊ฐ ์ฒ๋ฆฌ ์๋ฃ๋๋ฉด ์๋ต์ด ํธ๋์ญ์ ํ๋ก์๋ก ๋์์ค๊ณ , ํธ๋์ญ์ ํ๋ก์๋ ํธ๋์ญ์ ์ ์๋ฃ
๋์ ๊ฐ์ฒด ์ง์ ํธ์ถ
public void external() {
log.info("call external");
internal();
}
@Transactional
public void internal() {
log.info("call internal");
}

ํด๋ผ์ด์ธํธ๊ฐ service.external()์ ํธ์ถํ๋ฉด service์ ํธ๋์ญ์ ํ๋ก์ ํธ์ถ
external() ๋ฉ์๋์๋ @Transactional์ด ์์ผ๋ฏ๋ก ํธ๋์ญ์ ํ๋ก์๋ ํธ๋์ญ์ ์ ์ ์ฉํ์ง ์๊ณ , ์ค์ service ๊ฐ์ฒด ์ธ์คํด์ค์ external() ํธ์ถ
external()์ ๋ด๋ถ์์ (this.)internal() ์ง์ ํธ์ถ
๋ด๋ถ ํธ์ถ์ ํ๋ก์๋ฅผ ๊ฑฐ์น์ง ์์ผ๋ฏ๋ก ํธ๋์ญ์ ์ ์ฉ์ด ๋ถ๊ฐ๋ฅ
@Transactional์ ์ฌ์ฉํ๋ ํธ๋์ญ์ AOP๋ ํ๋ก์๋ฅผ ์ฌ์ฉํ๋ฉด์ ๋ฉ์๋ ๋ด๋ถ ํธ์ถ์ ํ๋ก์๋ฅผ ์ ์ฉํ ์ ์๋ค.
๊ฐ์ฅ ๋จ์ํ ๋ฐฉ๋ฒ์ผ๋ก ๋ด๋ถ ํธ์ถ์ ์ธ๋ถ ํธ์ถ๋ก ๋ณ๊ฒฝํ๊ธฐ ์ํด internal()๋ฅผ ๋ณ๋ ํด๋์ค๋ก ๋ถ๋ฆฌํ๊ธฐ
๋์ ๊ฐ์ฒด ์ธ๋ถ ํธ์ถ

ํด๋ผ์ด์ธํธ๊ฐ service.external()์ ํธ์ถํ๋ฉด ์ค์ service ๊ฐ์ฒด ์ธ์คํด์ค ํธ์ถ
service๋ ์ฃผ์ ๋ฐ์ internalService.internal() ํธ์ถ
internalService๋ ํธ๋์ญ์ ํ๋ก์์ด๋ฏ๋ก(@Transactional) ํธ๋์ญ์ ์ ์ฉ
ํธ๋์ญ์ ์ ์ฉ ํ ์ค์ internalService ๊ฐ์ฒด ์ธ์คํด์ค์ internal() ํธ์ถ
์ฐธ๊ณ
์คํ๋ง ํธ๋์ญ์ AOP ๊ธฐ๋ฅ์ ๊ณผ๋ํ ํธ๋์ญ์ ์ ์ฉ์ ๋ง๊ธฐ ์ํด public ๋ฉ์๋์๋ง ์ ์ฉ๋๋๋ก ๊ธฐ๋ณธ ์ค์
public ์ด ์๋๊ณณ์ @Transactional ์ด ๋ถ์ผ๋ฉด ํธ๋์ญ์ ์ ์ฉ ๋ฌด์
์ด๊ธฐํ ์์
์ด๊ธฐํ ์ฝ๋(ex.@PostConstruct)์ @Transactional์ ํจ๊ป ์ฌ์ฉํ๋ฉด ํธ๋์ญ์ ์ ์ฉ ๋ถ๊ฐ
์ด๊ธฐํ ์ฝ๋๊ฐ ๋จผ์ ํธ์ถ๋๊ณ ์ดํ ํธ๋์ญ์ AOP๊ฐ ์ ์ฉ๋๊ธฐ ๋๋ฌธ
@PostConstruct
@Transactional
public void initV1() {
boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("Hello init @PostConstruct tx active={}", isActive); // false
}
๋์์ผ๋ก
@ApplicationReadyEvent
์ฌ์ฉApplicationReadyEvent๋ ํธ๋์ญ์ AOP๋ฅผ ํฌํจํ ์คํ๋ง ์ปจํ ์ด๋๊ฐ ์์ ํ ์์ฑ๋ ์ดํ ์ด๋ฒคํธ๊ฐ ์ ์ธ๋ ๋ฉ์๋ ํธ์ถ
@EventListener(value = ApplicationReadyEvent.class)
@Transactional
public void init2() {
boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("Hello init ApplicationReadyEvent tx active={}", isActive); // true
}
์ต์
String value()
default "";String transactionManager()
default "";@Transactional ์์ ํธ๋์ญ์ ํ๋ก์๊ฐ ์ฌ์ฉํ ํธ๋์ญ์ ๋งค๋์ ์ง์
์๋ต ์ ๊ธฐ๋ณธ์ผ๋ก ๋ฑ๋ก๋ ํธ๋์ญ์ ๋งค๋์ ์ฌ์ฉ
์ฌ์ฉ ํธ๋์ญ์ ๋งค๋์ ๊ฐ ๋ ์ด์์ด๋ผ๋ฉด, ํธ๋์ญ์ ๋งค๋์ ์ด๋ฆ์ ์ง์ ํด์ ๊ตฌ๋ถ
@Transactional("memberTxManager")
@Transactional("orderTxManager")
Class<? extends Throwable>[] rollbackFor()
default {};ํน์ ์์ธ ๋ฐ์ ์ ๋กค๋ฐฑ์ ํ๋๋ก ์ง์
Exception(์ฒดํฌ ์์ธ)์ด ๋ฐ์ํด๋ ๋กค๋ฐฑํ๋๋ก ์ค์ ๊ฐ๋ฅ
@Transactional(rollbackFor = Exception.class)
Class<? extends Throwable>[] noRollbackFor()
default {};rollbackFor ์ ๋ฐ๋๋ก ํน์ ์์ธ ๋ฐ์ ์ ๋กค๋ฐฑ์ ํ์ง ์๋๋ก ์ง์
Propagation propagation()
default Propagation.REQUIRED;ํธ๋์ญ์ ์ ํ ์ต์
Isolation isolation()
default Isolation.DEFAULT;ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค ์ง์
๊ธฐ๋ณธ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ ๊ธฐ์ค(DEFAULT)
ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค์ ์ง์ ์ง์ ํ๋ ๊ฒฝ์ฐ๋ ๋๋ฌพ (์ฐธ๊ณ )
DEFAULT : ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ค์ ํ ๊ฒฉ๋ฆฌ ์์ค์ ๋ฐ๋ฅธ๋ค.
READ_UNCOMMITTED : ์ปค๋ฐ๋์ง ์์ ์ฝ๊ธฐ
READ_COMMITTED : ์ปค๋ฐ๋ ์ฝ๊ธฐ
REPEATABLE_READ : ๋ฐ๋ณต ๊ฐ๋ฅํ ์ฝ๊ธฐ
SERIALIZABLE : ์ง๋ ฌํ ๊ฐ๋ฅ
int timeout()
default TransactionDefinition.TIMEOUT_DEFAULT;ํธ๋์ญ์ ์ํ ์๊ฐ์ ๋ํ ํ์์์์ ์ด ๋จ์๋ก ์ง์
๊ธฐ๋ณธ ๊ฐ์ ํธ๋์ญ์ ์์คํ ์ ํ์์์
String[] label()
default {};ํธ๋์ญ์ ์ ๋ ธํ ์ด์ ์ ์๋ ๊ฐ์ ์ฝ์ด์ ํน์ ๋์์ ํ ๊ฒฝ์ฐ ์ฌ์ฉ
์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉํ์ง ์์
boolean readOnly()
default false;readOnly=true ์ต์ ์ฌ์ฉ ์ ์ฝ๊ธฐ ์ ์ฉ ํธ๋์ญ์ ์์ฑ
๋๋ผ์ด๋ฒ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ๋ผ ์ ์ ๋์ํ์ง ์๋ ๊ฒฝ์ฐ๋ ์์.
์ฝ๊ธฐ์์ ๋ค์ํ ์ฑ๋ฅ ์ต์ ํ
ํฌ๊ฒ ์ธ ๊ณณ์์ ์ ์ฉ
ํ๋ ์์ํฌ
JdbcTemplate: ์ฝ๊ธฐ ์ ์ฉ ํธ๋์ญ์ ์์์ ๋ณ๊ฒฝ ๊ธฐ๋ฅ์ ์คํํ๋ฉด ์์ธ
JPA: ์ฝ๊ธฐ ์ ์ฉ ํธ๋์ญ์ ์ ๊ฒฝ์ฐ ์ปค๋ฐ ์์ ์ ํ๋ฌ์๋ฅผ ํธ์ถํ์ง ์๊ณ , ๋ณ๊ฒฝ์ด ๋ถํ์ํ๋ ๋ณ๊ฒฝ ๊ฐ์ง๋ฅผ ์ํ ์ค๋ ์ท ๊ฐ์ฒด๋ ์์ฑํ์ง ์์
JDBC ๋๋ผ์ด๋ฒ
์ฝ๊ธฐ ์ ์ฉ ํธ๋์ญ์ ์์ ๋ณ๊ฒฝ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ฉด ์์ธ
์ฝ๊ธฐ, ์ฐ๊ธฐ(master, slave) ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ตฌ๋ถํด์ ์์ฒญ
DB / ๋๋ผ์ด๋ฒ ๋ฒ์ ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋์
๋ฐ์ดํฐ๋ฒ ์ด์ค
์ฝ๊ธฐ ์ ์ฉ ํธ๋์ญ์ ์ ๊ฒฝ์ฐ ์ฝ๊ธฐ๋ง ํ๋ฉด ๋๋ฏ๋ก, ๋ด๋ถ์์ ์ฑ๋ฅ ์ต์ ํ ๋ฐ์
์์ธ์ ๋กค๋ฐฑ
๋ด๋ถ์์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ์ง ๋ชปํ๊ณ ํธ๋์ญ์ ๋ฒ์(@Transactional ์ ์ฉ AOP) ๋ฐ์ผ๋ก ์์ธ๋ฅผ ๋์ง ๊ฒฝ์ฐ -> ์คํ๋ง ํธ๋์ญ์ AOP๋ ์์ธ์ ์ข ๋ฅ์ ๋ฐ๋ผ ํธ๋์ญ์ ์ ์ปค๋ฐํ๊ฑฐ๋ ๋กค๋ฐฑ
์ธ์ฒดํฌ ์์ธ(RuntimeException, Error, ๊ทธ ํ์ ์์ธ) ๋ฐ์ ์ ํธ๋์ญ์
๋กค๋ฐฑ
์ฒดํฌ ์์ธ(Exception, ๊ทธ ํ์ ์์ธ) ๋ฐ์ ์ ํธ๋์ญ์
์ปค๋ฐ
์ ์ ์๋ต(๋ฆฌํด) ์ ํธ๋์ญ์ ์
์ปค๋ฐ
์ฐธ๊ณ
ํธ๋์ญ์ ์ปค๋ฐ/๋กค๋ฐฑ ๋ก๊ทธ ํ์ธ์ ์ํ ์ค์
# ์ฌ์ฉ์ค์ธ TransactionManager
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG #JPA log
logging.level.org.hibernate.resource.transaction=DEBUG
ํธ๋์ญ์ ์์ฑ ๋ก๊ทธ(Creating new transaction with name)์ ํธ๋์ญ์ ์ปค๋ฐ/๋กค๋ฐฑ ๋ก๊ทธ(Committing JPA transaction on EntityManager) ํ์ธ
์คํ๋ง ๊ธฐ๋ณธ์ ์ผ๋ก ์์ธ๋ฅผ ์๋ ์ ์ฑ ์ ๋ฐ๋ฆ
์ฒดํฌ ์์ธ / Commit : ๋น์ฆ๋์ค ์์ธ (ex. ์๊ณ ๋ถ์กฑ ..)
๋น์ฆ๋์ค ์์ธ๋ ๋ฐ๋์ ์ฒ๋ฆฌํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ฏ๋ก ์ค์ํ๊ณ , ์ฒดํฌ ์์ธ๋ฅผ ๊ณ ๋ คํ ์ ์์.
rollbackFor ์ต์ ์ ์ฌ์ฉํด์ ๋น์ฆ๋์ค ์ํฉ์ ๋ฐ๋ผ ๋กค๋ฐฑ ์ ํ ๊ฐ๋ฅ
์ธ์ฒดํฌ ์์ธ / Rollback : ๋ณต๊ตฌ ๋ถ๊ฐ๋ฅํ ์์ธ (ex. DB ์ ๊ทผ ์ค๋ฅ, SQL ๋ฌธ๋ฒ ์ค๋ฅ ..)
ํธ๋์ญ์
์ ํ
Spring Transaction Propagation commit and rollback
Spring Transaction Propagation Use transaction twice

๋ก๊ทธ๋ฅผ ๋ณด๋ฉด ํธ๋์ญ์ 1,2๊ฐ ๊ฐ์ conn0 ์ปค๋ฅ์ ์ ์ฌ์ฉ์ค์ธ๋ฐ, ์ด๊ฒ์ ์ปค๋ฅ์ ํ ๋๋ฌธ์ ๊ทธ๋ฐ ๊ฒ
ํธ๋์ญ์ 1์ conn0์ ๋ชจ๋ ์ฌ์ฉ ํ ์ปค๋ฅ์ ํ์ ๋ฐ๋ฉํ๊ณ , ์ดํ ํธ๋์ญ์ 2๊ฐ conn0์ ์ปค๋ฅ์ ํ์์ ํ๋
ํ์นด๋ฆฌ ์ปค๋ฅ์ ํ์์ ์ปค๋ฅ์ ์ ํ๋ํ๋ฉด ์ค์ ์ปค๋ฅ์ ์ ๊ทธ๋๋ก ๋ฐํํ๋ ๊ฒ์ด ์๋๋ผ ๋ด๋ถ ๊ด๋ฆฌ๋ฅผ ์ํด ํ์นด๋ฆฌ ํ๋ก์ ์ปค๋ฅ์ ๊ฐ์ฒด๋ฅผ ์์ฑํด์ ๋ฐํํ๋๋ฐ, ์ด ๊ฐ์ฒด์ ์ฃผ์๋ฅผ ํ์ธํ๋ฉด ์ปค๋ฅ์ ํ์์ ํ๋ํ ์ปค๋ฅ์ ๊ตฌ๋ถ์ด ๊ฐ๋ฅ
HikariProxyConnection@2120431435 wrapping conn0: ...
HikariProxyConnection@1567077043 wrapping conn0: ...
์ปค๋ฅ์ ์ด ์ฌ์ฌ์ฉ ๋์์ง๋ง, ๊ฐ๊ฐ ์ปค๋ฅ์ ํ์์ ์ปค๋ฅ์ ์ ์กฐํ
๊ธฐ๋ณธ

ํธ๋์ญ์ ์ด ์งํ์ค์ธ ์ํ์์ ๋ด๋ถ์ ์ถ๊ฐ๋ก ํธ๋์ญ์ ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ์คํ๋ง์ ์ธ๋ถ/๋ด๋ถ ํธ๋์ญ์ ์ ๋ฌถ์ด์ ํ๋์ ํธ๋์ญ์ ์ ์์ฑ
ํธ๋์ญ์ ์ ํ์ ๊ธฐ๋ณธ ์ต์ ์ธ
REQUIRED
๊ธฐ์ค๋ด๋ถ ํธ๋์ญ์ ์ ์ธ๋ถ ํธ๋์ญ์ ์ ์ฐธ์ฌ(์ธ/๋ด๋ถ ํธ๋์ญ์ ์ด ํ๋์ ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์)
์ต์ ์ ํตํด ๋ค๋ฅธ ๋์๋ฐฉ์ ์ ํ ๊ฐ๋ฅ
๋ ผ๋ฆฌ ํธ๋์ญ์ ๋ค์ ํ๋์ ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์
๋ฌผ๋ฆฌ ํธ๋์ญ์
: ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฉ๋๋ ํธ๋์ญ์ (์์, ์ปค๋ฐ, ๋กค๋ฐฑ ๋จ์)๋ ผ๋ฆฌ ํธ๋์ญ์
: ํธ๋์ญ์ ๋งค๋์ ๋ฅผ ํตํด ํธ๋์ญ์ ์ ์ฌ์ฉํ๋ ๋จ์
๋ชจ๋ ํธ๋์ญ์ ๋งค๋์ ๊ฐ ์ปค๋ฐ๋์ด์ผ ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ด ์ปค๋ฐ
ํ๋์ ํธ๋์ญ์ ๋งค๋์ ๋ผ๋ ๋กค๋ฐฑ๋๋ฉด ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ ๋กค๋ฐฑ
์ธ๋ถ ํธ๋์ญ์ ๋ง ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์์/์ปค๋ฐ
์ฒ์ ์์ ํธ๋์ญ์ ์ด ์ค์ ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ ๊ด๋ฆฌ
์์น
๋ชจ๋
๋ ผ๋ฆฌ ํธ๋์ญ์
์ด ์ปค๋ฐ๋์ด์ผ๋ฌผ๋ฆฌ ํธ๋์ญ์
์ด ์ปค๋ฐํ๋์
๋ ผ๋ฆฌ ํธ๋์ญ์
์ด๋ผ๋ ๋กค๋ฐฑ๋๋ฉด๋ฌผ๋ฆฌ ํธ๋์ญ์
์ ๋กค๋ฐฑ
ํ๋ฆ
์์ฒญ ํ๋ฆ

์ธ๋ถ ํธ๋์ญ์
\1. txManager.getTransaction() ํธ์ถ๋ก ์ธ๋ถ ํธ๋์ญ์ ์์
\2. ํธ๋์ญ์ ๋งค๋์ ๋ ๋ฐ์ดํฐ์์ค๋ฅผ ํตํด ์ปค๋ฅ์ ์์ฑ
\3. ์์ฑ ์ปค๋ฅ์ ์ ์๋ ์ปค๋ฐ ๋ชจ๋๋ก ์ค์ (๋ฌผ๋ฆฌ ํธ๋์ญ์ ์์)
\4. ํธ๋์ญ์ ๋งค๋์ ๋ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ์ ์ปค๋ฅ์ ์ ๋ณด๊ด
\5. ํธ๋์ญ์ ๋งค๋์ ๋ ํธ๋์ญ์ ์ ์์ฑํ ๊ฒฐ๊ณผ๋ฅผ TransactionStatus์ ๋ด์์ ๋ฐํ
isNewTransaction๋ก ์ ๊ท ํธ๋์ญ์ ์ฌ๋ถ ํ์ธ, true
\6. ๋ก์ง1์ด ์คํ๋๊ณ ์ปค๋ฅ์ ์ด ํ์ํ ๊ฒฝ์ฐ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ฅผ ํตํด ํธ๋์ญ์ ์ด ์ ์ฉ๋ ์ปค๋ฅ์ ํ๋ ํ ์ฌ์ฉ
๋ด๋ถ ํธ๋์ญ์
\7. txManager.getTransaction() ํธ์ถ๋ก ๋ด๋ถ ํธ๋์ญ์ ์์
\8. ํธ๋์ญ์ ๋งค๋์ ๋ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ฅผ ํตํด ๊ธฐ์กด ํธ๋์ญ์ ์กด์ฌ ํ์ธ
\9. ๊ธฐ์กด ํธ๋์ญ์ ์ด ์กด์ฌํ๋ฏ๋ก ๊ธฐ์กด ํธ๋์ญ์ ์ ์ฐธ์ฌ
๋ฌผ๋ฆฌ์ ์ผ๋ก ์๋ฌด ํ๋์ ํ์ง ์๊ณ , ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ์ ๋ณด๊ด๋ ๊ธฐ์กด ์ปค๋ฅ์ ์ฌ์ฉ
\10. isNewTransaction = false
\11. ๋ก์ง2๊ฐ ์คํ๋๊ณ , ์ปค๋ฅ์ ์ด ํ์ํ ๊ฒฝ์ฐ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ฅผ ํตํด ์ธ๋ถ ํธ๋์ญ์ ์ด ๋ณด๊ดํ ๊ธฐ์กด ์ปค๋ฅ์ ํ๋ ํ ์ฌ์ฉ
์๋ต ํ๋ฆ

๋ด๋ถ ํธ๋์ญ์
\12. ๋ก์ง2๊ฐ ๋๋๊ณ ํธ๋์ญ์ ๋งค๋์ ๋ฅผ ํตํด ๋ด๋ถ ํธ๋์ญ์ ์ปค๋ฐ
\13. ํธ๋์ญ์ ๋งค๋์ ๋ ์ปค๋ฐ ์์ ์ ์ ๊ท ํธ๋์ญ์ ์ฌ๋ถ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋์
์ฌ๊ธฐ์๋ ์ ๊ท ํธ๋์ญ์ ์ด ์๋๋ฏ๋ก ์ค์ ๋ฌผ๋ฆฌ ์ปค๋ฐ ํธ์ถ์ ํ์ง ์์
์์ง ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ด ๋๋์ง ์์
์ธ๋ถ ํธ๋์ญ์
\14. ๋ก์ง1์ด ๋๋๊ณ ํธ๋์ญ์ ๋งค๋์ ๋ฅผ ํตํด ์ธ๋ถ ํธ๋์ญ์ ์ปค๋ฐ
\15. ํธ๋์ญ์ ๋งค๋์ ๋ ์ปค๋ฐ ์์ ์ ์ ๊ท ํธ๋์ญ์ ์ฌ๋ถ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋์
์ธ๋ถ ํธ๋์ญ์ ์ ์ ๊ท ํธ๋์ญ์ ์ด๋ฏ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ ์ค์ ์ปค๋ฐ ํธ์ถ
\16. ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ปค๋ฐ์ด ๋ฐ์๋๊ณ , ๋ฌผ๋ฆฌ ํธ๋์ญ์ ๋ ๋.
๋ ผ๋ฆฌ์ ์ธ ์ปค๋ฐ: ํธ๋์ญ์ ๋งค๋์ ์ ์ปค๋ฐํ๋ ๊ฒ์ด ๋ ผ๋ฆฌ์ ์ธ ์ปค๋ฐ์ด๋ผ๋ฉด,
๋ฌผ๋ฆฌ ์ปค๋ฐ: ์ค์ ์ปค๋ฅ์ ์ ์ปค๋ฐ
ํ๋ฆ ํต์ฌ
ํธ๋์ญ์ ๋งค๋์ ์ ์ปค๋ฐ์ ํ๋ค๊ณ ํญ์ ์ค์ ์ปค๋ฅ์ ์ ๋ฌผ๋ฆฌ ์ปค๋ฐ์ด ๋ฐ์ํ์ง ์์
์ ๊ท ํธ๋์ญ์ ์ธ ๊ฒฝ์ฐ์๋ง ์ค์ ์ปค๋ฅ์ ์ ์ฌ์ฉํด์ ๋ฌผ๋ฆฌ ์ปค๋ฐ/๋กค๋ฐฑ ์ํ
์ธ/๋ด๋ถ ํธ๋์ญ์
๋กค๋ฐฑ
์ธ๋ถ ๋กค๋ฐฑ

์ธ๋ถ ํธ๋์ญ์ ์์ ์์ํ ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ ๋ฒ์๊ฐ ๋ด๋ถ ํธ๋์ญ์ ๊น์ง ์ฌ์ฉ
์ดํ ์ธ๋ถ ํธ๋์ญ์ ์ด ๋กค๋ฐฑ๋๋ฉด์ ์ ์ฒด ๋ด์ฉ์ ๋ชจ๋ ๋กค๋ฐฑ
๋ด๋ถ ๋กค๋ฐฑ

๋ด๋ถ ํธ๋์ญ์ ์ ๋กค๋ฐฑํ๋ฉด ์ค์ ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ด ๋กค๋ฐฑ๋์ง๋ ์๊ณ , ๊ธฐ์กด ํธ๋์ญ์ ์ ๋กค๋ฐฑ ์ ์ฉ ๋งํฌ ํ์
Participating transaction failed - marking existing transaction as rollbackonly
์ดํ ์ธ๋ถ ํธ๋์ญ์ ์ด ์ปค๋ฐ์ ํธ์ถํ์ง๋ง, ์ ์ฒด ํธ๋์ญ์ ์ด ๋กค๋ฐฑ ์ ์ฉ์ผ๋ก ํ์๋์ด ๋ฌผ๋ฆฌ ํธ๋์ญ์ ๋กค๋ฐฑ -> UnexpectedRollbackException
Global transaction is marked as rollback-only
์ธ๋ถ ํธ๋์ญ์
๊ณผ ๋ด๋ถ ํธ๋์ญ์
๋ถ๋ฆฌ
REQUIRES_NEW ์ต์ ์ฌ์ฉ
์ธ๋ถ/๋ด๋ถ ํธ๋์ญ์ ์ด ๊ฐ๊ฐ ๋ณ๋์ ๋ฌผ๋ฆฌ ํธ๋์ญ์ (๋ณ๋์ DB connection ์ฌ์ฉ)์ ๊ฐ์ง
๊ฐ ํธ๋์ญ์ ์ ๋กค๋ฐฑ์ด ์๋ก์๊ฒ ์ํฅ์ ์ฃผ์ง ์์
์์ฒญ ํ๋ฆ
์๋ต ํ๋ฆ
์ ํ ์ต์
์ค๋ฌด์์ ๋๋ถ๋ถ REQUIRED
์ต์
์ฌ์ฉ, ์์ฃผ ๊ฐ๋ REQUIRES_NEW
์ฌ์ฉ
๋๋จธ์ง๋ ๊ฑฐ์ ์ฌ์ฉํ์ง ์์ผ๋ ์ฐธ๊ณ ๋ง..
REQUIRED
๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ๊ธฐ๋ณธ ์ค์ (ํธ๋์ญ์ ํ์)
๊ธฐ์กด ํธ๋์ญ์ X: ์๋ก์ด ํธ๋์ญ์ ์์ฑ
๊ธฐ์กด ํธ๋์ญ์ O: ๊ธฐ์กด ํธ๋์ญ์ ์ ์ฐธ์ฌ
REQUIRES_NEW
ํญ์ ์๋ก์ด ํธ๋์ญ์ ์์ฑ
๊ธฐ์กด ํธ๋์ญ์ X: ์๋ก์ด ํธ๋์ญ์ ์์ฑ
๊ธฐ์กด ํธ๋์ญ์ O: ์๋ก์ด ํธ๋์ญ์ ์์ฑ
SUPPORT
ํธ๋์ญ์ ์ง์
๊ธฐ์กด ํธ๋์ญ์ X: ํธ๋์ญ์ ์์ด ์งํ
๊ธฐ์กด ํธ๋์ญ์ O: ๊ธฐ์กด ํธ๋์ญ์ ์ฐธ์ฌ
NOT_SUPPORT
ํธ๋์ญ์ ์ง์์ ํ์ง ์์
๊ธฐ์กด ํธ๋์ญ์ X: ํธ๋์ญ์ ์์ด ์งํ
๊ธฐ์กด ํธ๋์ญ์ O: ํธ๋์ญ์ ์์ด ์งํ(๊ธฐ์กด ํธ๋์ญ์ ์ ๋ณด๋ฅ)
MANDATORY
ํธ๋์ญ์ ์ด ๋ฐ๋์ ์์ด์ผ ํจ
๊ธฐ์กด ํธ๋์ญ์ X: IllegalTransactionStateException ์์ธ ๋ฐ์
๊ธฐ์กด ํธ๋์ญ์ O: ๊ธฐ์กด ํธ๋์ญ์ ์ฐธ์ฌ
NEVER
ํธ๋์ญ์ ์ ์ฌ์ฉํ์ง ์์
๊ธฐ์กด ํธ๋์ญ์ X: ํธ๋์ญ์ ์์ด ์งํ
๊ธฐ์กด ํธ๋์ญ์ O: IllegalTransactionStateException ์์ธ ๋ฐ์
NESTED
๊ธฐ์กด ํธ๋์ญ์ X: ์๋ก์ด ํธ๋์ญ์ ์์ฑ
๊ธฐ์กด ํธ๋์ญ์ O: ์ค์ฒฉ ํธ๋์ญ์ ์์ฑ
์ค์ฒฉ ํธ๋์ญ์ ์ ์ธ๋ถ ํธ๋์ญ์ ์ ์ํฅ์ ๋ฐ์ง๋ง, ์ค์ฒฉ ํธ๋์ญ์ ์ ์ธ๋ถ์ ์ํฅ์ ์ฃผ์ง ์์
isolation
, timeout
, readOnly
๋ ํธ๋์ญ์
์ฒ์ ์์ ์์๋ง ์ ์ฉ(์ฐธ์ฌํ๋ ๊ฒฝ์ฐ์๋ ์ ์ฉ๋์ง ์์)
ํ์ฉ
์์ ํ๋ก์ ํธ : commit
์ปค๋ฐ๊ณผ ๋กค๋ฐฑ : commit
๋จ์ผ ํธ๋์ญ์ : commit
์ ํ ์ปค๋ฐ(default REQUIRED) : commit
์ ํ ๋กค๋ฐฑ : commit
โญ๏ธ ๋ณต๊ตฌ
REQUIRED
์ UnexpectedRollbackException

LogRepository ์์ ์์ธ ๋ฐ์
์์ธ๋ฅผ ํ ์คํ๋ฉด LogRepository ์ ํธ๋์ญ์ AOP๊ฐ ํด๋น ์์ธ ์ ๋ฌ๋ฐ์
์ ๊ท ํธ๋์ญ์ ์ด ์๋๋ฏ๋ก ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ ๋กค๋ฐฑํ์ง ์๊ณ , ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ์ rollbackOnly=true ํ์
์ดํ ํธ๋์ญ์ AOP๋ ์ ๋ฌ ๋ฐ์ ์์ธ๋ฅผ ๋ฐ์ผ๋ก ํ ์ค
์์ธ๊ฐ MemberService ์ ํ ์ค๋๊ณ , MemberService ๋ ํด๋น ์์ธ ๋ณต๊ตฌ ๋ฐ ์ ์ ๋ฆฌํด
์ ์ ํ๋ฆ์ด ๋์์ผ๋ฏ๋ก MemberService ์ ํธ๋์ญ์ AOP๋ ์ปค๋ฐ ํธ์ถ
์ปค๋ฐ ํธ์ถ ์ ์ ๊ท ํธ๋์ญ์ ์ด๋ฏ๋ก ์ค์ ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ปค๋ฐ. ์ด๋!! rollbackOnly ์ฒดํฌ
rollbackOnly=true ์ํ์ด๋ฏ๋ก ๋ฌผ๋ฆฌ ํธ๋์ญ์ ๋กค๋ฐฑ
ํธ๋์ญ์ ๋งค๋์ ๋ UnexpectedRollbackException ์์ธ ํ ์ค
ํธ๋์ญ์ AOP๋ ์ ๋ฌ๋ฐ์ UnexpectedRollbackException ์ ํด๋ผ์ด์ธํธ์๊ฒ ํ ์ค
REQUIRES_NEW

LogRepository ์์ ์์ธ ๋ฐ์
์์ธ๋ฅผ ํ ์คํ๋ฉด LogRepository ์ ํธ๋์ญ์ AOP๊ฐ ํด๋น ์์ธ ์ ๋ฌ๋ฐ์
REQUIRES_NEW ๋ฅผ ์ฌ์ฉํ ์ ๊ท ํธ๋์ญ์ ์ด๋ฏ๋ก ๋ฌผ๋ฆฌ ํธ๋์ญ์ ๋กค๋ฐฑ ๋ฐ ๋ฐํ(rollbackOnly ํ์ X)
์ดํ ํธ๋์ญ์ AOP๋ ์ ๋ฌ ๋ฐ์ ์์ธ๋ฅผ ๋ฐ์ผ๋ก ํ ์ค
์์ธ๊ฐ MemberService ์ ํ ์ค๋๊ณ , MemberService ๋ ํด๋น ์์ธ ๋ณต๊ตฌ ๋ฐ ์ ์ ๋ฆฌํด
์ ์ ํ๋ฆ์ด ๋์์ผ๋ฏ๋ก MemberService ์ ํธ๋์ญ์ AOP๋ ์ปค๋ฐ ํธ์ถ
์ปค๋ฐ ํธ์ถ ์ ์ ๊ท ํธ๋์ญ์ ์ด๋ฏ๋ก ์ค์ ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ปค๋ฐ. rollbackOnly๊ฐ ์ฒดํฌ๋์ด ์์ง ์์ผ๋ฏ๋ก ๋ฌผ๋ฆฌ ํธ๋์ญ์ ์ปค๋ฐ ๋ฐ ์ ์ ํ๋ฆ ๋ฐํ
REQUIRED
์ ์ฉ ์ ๋
ผ๋ฆฌ ํธ๋์ญ์
์ด ํ๋๋ผ๋ ๋กค๋ฐฑ๋๋ฉด ๊ด๋ จ ๋ฌผ๋ฆฌ ํธ๋์ญ์
๋ชจ๋ ๋กค๋ฐฑ
-> REQUIRES_NEW
๋ฅผ ํตํ ํธ๋์ญ์
๋ถ๋ฆฌ๋ก ํด๊ฒฐ (๋จ, ํ๋์ HTTP ์์ฒญ์ 2๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์
์ ์ฌ์ฉํ๋ ๋จ์ )
-> ์ฑ๋ฅ์ด ์ค์ํ๋ค๋ฉด ํธ๋์ญ์ ์ ์์ฐจ์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ตฌ์กฐ๋ฅผ ์ ํํ๋ ๊ฒ์ด ์ข์ ์๋ ์์
Last updated