๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

MySQL ๋ฒ„์ „์— ๋”ฐ๋ฅธ @Transactional(readOnly=true)์˜ ๋™์ž‘ ๊ณผ์ •

by dkswnkk 2024. 1. 24.

๊ฐœ์š”

์ด ๊ธ€์€ ํƒœํ˜„๋‹˜์˜ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€์—์„œ ์˜๊ฐ์„ ๋ฐ›์•„ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ†ตํ•ด RDBMS๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ณผ์ •์—์„œ, ์šฐ๋ฆฌ๋Š” ์„ฑ๋Šฅ ์ตœ์ ํ™”, ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ, ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ์œ ์ง€ ๋“ฑ ์—ฌ๋Ÿฌ ์ด์œ ๋กœ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— @Transactional(readOnly=true) ์–ด๋…ธํ…Œ์ด์…˜์„ ์ž์ฃผ ์‚ฌ์šฉํ•˜๊ณค ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด ์–ด๋…ธํ…Œ์ด์…˜์€ ์–ด๋– ํ•œ ์›๋ฆฌ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ผ๊นŒ์š”? ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” @Transactional(readOnly=true)์˜ JDBC ๋‹จ๊ณ„์—์„œ์˜ ๋™์ž‘ ๊ณผ์ •์„ ์œ„์ฃผ๋กœ ์‚ดํŽด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

 

 

๋™์ž‘ ๊ณผ์ •

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— @Transactional(readOnly=true)์„ ๊ฑธ๊ณ  ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ ๋™์ž‘ํ•˜๋Š” ์ „์ฒด์ ์ธ ๊ณผ์ •์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘
  2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ค€๋น„
  3. ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ ์ „ํŒŒ
  4. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰
  5. ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ

๊ฐ ๊ณผ์ •์— ๋Œ€ํ•ด ์ƒ์„ธํ•˜๊ฒŒ ํ•œ๋ฒˆ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
 

1. ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘

์•„๋ž˜์™€ ๊ฐ™์ด @Transactional(readOnly=true)๊ฐ€ ์„ค์ •๋œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํ˜ธ์ถœํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

@Service
public class MyService {
    @Transactional(readOnly=true)
    public MyData getData(int id) {
        // ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
    }
    
}

์œ„ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์Šคํ”„๋ง์˜ AOP ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ด๋ฅผ ์ธ์ง€ํ•˜๊ณ , ํŠธ๋žœ์žญ์…˜ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ํ†ตํ•ด ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์Šคํ”„๋ง์˜ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๋Š” AOP(Aspect-Oriented Programming) ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘๋™ํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์žฌ์‚ฌ์šฉ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ํšจ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ํŠธ๋žœ์žญ์…˜ ์ธํ„ฐ์…‰ํ„ฐ๋Š” ์ค‘์ถ”์ ์ธ ์—ญํ• ์„ ๋‹ด๋‹นํ•˜๋ฉฐ, ํŠธ๋žœ์žญ์…˜์˜ ์‹œ์ž‘, ์ข…๋ฃŒ, ํ•„์š”์‹œ ๋กค๋ฐฑ ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

@Transactional(readOnly=true) ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด getData ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ, ํŠธ๋žœ์žญ์…˜ ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์ƒˆ๋กœ์šด ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ํŠธ๋žœ์žญ์…˜์˜ ์†์„ฑ๋“ค(์ฝ๊ธฐ ์ „์šฉ, ๊ฒฉ๋ฆฌ ์ˆ˜์ค€, ํƒ€์ž„์•„์›ƒ, ์ „ํŒŒ ๋ฐฉ์‹ ๋“ฑ)์ด ํŠธ๋žœ์žญ์…˜ ๋งค๋‹ˆ์ €์—๊ฒŒ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
 

2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ค€๋น„

DataSourceTransactionManager์˜ doBegin()

ํŠธ๋žœ์žญ์…˜์ด ์‹œ์ž‘๋˜๋ฉด ์Šคํ”„๋ง์˜ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ์ž๋Š” DataSourceTransactionalManager.doBegin ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ DataSourceUtils.prepareConnectionForTransaction ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ๋กœ ํŠธ๋žœ์žญ์…˜ ์ •์˜๋ฅผ ๋ฐ›์•„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ํ™•๋ณดํ•˜๊ณ , ํ•ด๋‹น ์—ฐ๊ฒฐ์— ํŠธ๋žœ์žญ์…˜์˜ ์†์„ฑ๋“ค์„ ์ ์šฉํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

// DataSourceUtils.java
public static Integer prepareConnectionForTransaction(Connection con, TransactionDefinition definition)
        throws SQLException {
    Assert.notNull(con, "No Connection specified");
    if (definition.isReadOnly()) {
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Setting JDBC Connection [" + con + "] read-only");
            }
            con.setReadOnly(true);
        }
        catch (SQLException | RuntimeException ex) {
            // ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
        }
    }
    // ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ์„ค์ • ๋“ฑ์˜ ์ถ”๊ฐ€ ์ฝ”๋“œ
}

DataSourceUtils์˜ prepareConnectionForTransaction

DataSourceUtils์˜ prepareConnectionForTransaction ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ definition.isReadOnly()๋Š” @Transactional(readOnly=true) ์–ด๋…ธํ…Œ์ด์…˜์˜ ๊ฐ’์ด true์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ์ด ๊ฐ’์ด true์ผ ๊ฒฝ์šฐ, con.setReadOnly(true)๊ฐ€ ํ˜ธ์ถœ๋˜์–ด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์ด ์ฝ๊ธฐ ์ „์šฉ ๋ชจ๋“œ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ด ์„ค์ •์— ๋”ฐ๋ผ ํ•ด๋‹น ์—ฐ๊ฒฐ์„ ํ†ตํ•ด ์ˆ˜ํ–‰๋˜๋Š” ๋ชจ๋“  SQL ๋ช…๋ น์€ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์—†์ด ์ฝ๊ธฐ ์ „์šฉ ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ ์ด ๋ฉ”์„œ๋“œ๋Š” ํŠธ๋žœ์žญ์…˜ ์ •์˜์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์„ ์„ค์ •ํ•˜๋Š” ๋ถ€๋ถ„๋„ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด, ํŠธ๋žœ์žญ์…˜์˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์ด ์ •์˜๋˜์–ด ์žˆ๋‹ค๋ฉด, ํ•ด๋‹น ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๋ชจ๋“  ๊ณผ์ •์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜๋ฉด, ํŠธ๋žœ์žญ์…˜์„ ์œ„ํ•œ ์ค€๋น„๊ฐ€ ๋ชจ๋‘ ๋งˆ๋ฌด๋ฆฌ๋œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ์ดํ›„์˜ ์ฒ˜๋ฆฌ๋Š” ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง€๋ฉฐ, ํŠธ๋žœ์žญ์…˜์ด ์ข…๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์ด ์—ฐ๊ฒฐ์€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.  ํŠธ๋žœ์žญ์…˜์ด ์ข…๋ฃŒ๋œ ํ›„์—๋Š” resetConnectAfterTransaction ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์—ฐ๊ฒฐ ์ƒํƒœ๊ฐ€ ์›๋ž˜๋Œ€๋กœ ๋ณต์›๋ฉ๋‹ˆ๋‹ค.
 

3. ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ ์ „ํŒŒ

์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ์„ค์ •๋œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์€ MySQL์˜ JDBC ๋“œ๋ผ์ด๋ฒ„์— ์ „๋‹ฌ๋˜๋ฉฐ JDBC ๋“œ๋ผ์ด๋ฒ„๋Š” Connection.setReadOnly(true) ํ˜ธ์ถœ์„ ์ธ์ง€ํ•˜๊ณ , ๋‚ด๋ถ€์ ์œผ๋กœ ConnectionImpl.setReadOnlyInternal ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

ConnectionImpl.setReadOnlyInternal ๋ฉ”์„œ๋“œ๋Š” MySQL์˜ JDBC ๋“œ๋ผ์ด๋ฒ„์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ๋กœ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์˜ ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” MySQL ์„œ๋ฒ„์˜ ๋ฒ„์ „์„ ํ™•์ธํ•œ ํ›„, ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ๋ฅผ MySQL ์„œ๋ฒ„์— ์ „ํŒŒํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.

// ConnectionImpl.java
@Override
public void setReadOnlyInternal(boolean readOnlyFlag) throws SQLException {
    synchronized (getConnectionMutex()) {
        // note this this is safe even inside a transaction
        if (this.readOnlyPropagatesToServer.getValue() && versionMeetsMinimum(5, 6, 5)) {
            if (!this.useLocalSessionState.getValue() || readOnlyFlag != this.readOnly) {
                this.session.execSQL(null, "set session transaction " + (readOnlyFlag ? "read only" : "read write"), -1, null, false,
                        this.nullStatementResultSetFactory, null, false);
            }
        }
        this.readOnly = readOnlyFlag;
    }
}

ConnectionImpl์˜ setReadOnlyInternal

์œ„ ์ฝ”๋“œ์—์„œ versionMeetsMinimum(5, 6, 5)๋Š” MySQL ์„œ๋ฒ„์˜ ๋ฒ„์ „์ด 5.6.5 ์ด์ƒ์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ 5.6.5 ๋ฒ„์ „ ์ด์ƒ์ผ ๊ฒฝ์šฐ execSQL(..., "set session transaction read only", ...)๊ฐ€ ํ˜ธ์ถœ๋˜์–ด, ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ๋ฅผ MySQL ์„œ๋ฒ„์— ์ „๋‹ฌํ•˜๋Š” SQL ๋ช…๋ น์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์ด ๊ณผ์ •์„ ํ†ตํ•ด JDBC ๋“œ๋ผ์ด๋ฒ„๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์˜ ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ๋ฅผ MySQL ์„œ๋ฒ„์— ์ „ํŒŒํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์€ JDBC ๋“œ๋ผ์ด๋ฒ„์™€ MySQL ์„œ๋ฒ„ ๊ฐ„์˜ ํ†ต์‹  ๊ณผ์ •์—์„œ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด MySQL ์„œ๋ฒ„๋„ ํ•ด๋‹น ์—ฐ๊ฒฐ์ด ์ฝ๊ธฐ ์ „์šฉ์ž„์„ ์ธ์ง€ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋งŒ์•ฝ MySQL ์„œ๋ฒ„์˜ ๋ฒ„์ „์ด 5.6.5 ๋ฏธ๋งŒ์ด๋ผ๋ฉด, 'READ ONLY'์™€ 'READ WRITE' ํŠธ๋žœ์žญ์…˜ ํŠน์„ฑ์ด ์ง€์›๋˜์ง€ ์•Š์•„ ์กฐ๊ฑด๋ฌธ์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•ด ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ๊ฐ€ MySQL ์„œ๋ฒ„์— ์ „ํŒŒ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. 

 

์„œ๋ฒ„ ๋ฒ„์ „์ด 5.6.5 ๋ฏธ๋งŒ์ธ ๊ฒฝ์šฐ, 'READ ONLY'์™€ 'READ WRITE' ํŠธ๋žœ์žญ์…˜ ํŠน์„ฑ์ด ์ง€์›๋˜์ง€ ์•Š๋Š”๋‹ค.

MySQL 5.6.5 ์ด์ „ ๋ฒ„์ „์—์„œ๋Š” ์„ธ์…˜ ๋ ˆ๋ฒจ์—์„œ๋งŒ ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ํŠธ๋žœ์žญ์…˜ ์ˆ˜์ค€์—์„œ 'READ ONLY'์™€ 'READ WRITE' ํŠน์„ฑ์„ ์ง€์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์€ MySQL 5.6.5 ๋ฒ„์ „๋ถ€ํ„ฐ ์ œ๊ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.(https://forums.mysql.com/read.php?3,524924)

์„ธ์…˜ ๋ ˆ๋ฒจ์—์„œ์˜ ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ์™€ ํŠธ๋žœ์žญ์…˜ ์ˆ˜์ค€์—์„œ์˜ ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฐจ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

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

์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด "set session transaction " + (readOnlyFlag ? "read only" : "read write") ๋ถ€๋ถ„์€ MySQL ์„œ๋ฒ„์— ์ „๋‹ฌ๋˜๋Š” ๋ช…๋ น์–ด๋กœ, 5.6.5 ์ด์ „ ๋ฒ„์ „์—์„œ๋Š” ํ•ด๋‹น ๋ช…๋ น์–ด๋ฅผ ์ง์ ‘์ ์œผ๋กœ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, MySQL ์„œ๋ฒ„์˜ ๋ฒ„์ „์ด 5.6.5 ๋ฏธ๋งŒ์ผ ๊ฒฝ์šฐ์—๋Š” JDBC ๋“œ๋ผ์ด๋ฒ„๊ฐ€ 'READ ONLY' ๋˜๋Š” 'READ WRITE' ํŠธ๋žœ์žญ์…˜ ํŠน์„ฑ์„ ์„œ๋ฒ„์— ์ „๋‹ฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ MySQL ์„œ๋ฒ„์˜ ๋ฒ„์ „์ด 5.6.5 ๋ฏธ๋งŒ์ธ ๊ฒฝ์šฐ์—๋„, ์ฝ”๋“œ ๋งˆ์ง€๋ง‰์˜ this.readOnly = readOnlyFlag๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ์„ค์ •ํ•˜๋„๋ก JDBC ๋“œ๋ผ์ด๋ฒ„์—๊ฒŒ ์ง€์‹œํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ JDBC ๋“œ๋ผ์ด๋ฒ„๋Š” ์ด ์—ฐ๊ฒฐ์„ ํ†ตํ•ด ๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  ์ฟผ๋ฆฌ๋ฅผ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ์ทจ๊ธ‰ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๋ ค๊ณ  ํ•˜๋ฉด JDBC ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์ด๋ฅผ ๊ฑฐ๋ถ€ํ•˜๊ณ  ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด MySQL ์„œ๋ฒ„์˜ ๋ฒ„์ „์— ์ƒ๊ด€์—†์ด ์ฝ๊ธฐ ์ „์šฉ ํŠธ๋žœ์žญ์…˜์˜ ์•ˆ์ „์„ฑ์€ ๋ณด์žฅ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

DataSourceTransactionManager์˜ prepareTransactionalConnection()

๊ทธ ํ›„ DataSourceTransactionManager ํด๋ž˜์Šค์˜ doBegin() ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ DataSourceTransactionManager.prepareTransactionalConnection() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ํŠธ๋žœ์žญ์…˜ ์ˆ˜์ค€์—์„œ ์ฝ๊ธฐ ์ „์šฉ ๋ชจ๋“œ๋ฅผ ๊ฐ•์ œํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ DataSourceUtils.prepareConnectionForTransaction(con, definition) ํ˜ธ์ถœ์— ์˜ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์ด ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ์„ค์ •๋˜์—ˆ์ง€๋งŒ, ์ด๋Š” ์—ฐ๊ฒฐ ์ˆ˜์ค€์˜ ์„ค์ •์ด๋ฏ€๋กœ ์ดํ›„์— ๊ฐ™์€ ์—ฐ๊ฒฐ์„ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ ์žฌ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์— ์ฝ๊ธฐ ์ „์šฉ ์„ค์ •์ด ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๊ธฐ๋ณธ ์„ค์ • ์ƒ์—์„œ DataSourceTransactionManager.prepareTransactionalConnection() ๋ฉ”์„œ๋“œ๊ฐ€ ์‹ค์ œ๋กœ ์ˆ˜ํ–‰๋  ๊ฐ€๋Šฅ์„ฑ์€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. isEnforceReadOnly()์˜ ๊ธฐ๋ณธ๊ฐ’์ด false์ด๊ณ , enforceReadOnly ์†์„ฑ์€ DataSourceTransactionManager์˜ ์„ค์ •์œผ๋กœ์จ @Transactional ์–ด๋…ธํ…Œ์ด์…˜์˜ readOnly ์†์„ฑ๊ณผ๋Š” ๋ณ„๊ฐœ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ enforceReadOnly๋ฅผ true๋กœ ์„ค์ •ํ•˜๋ ค๋ฉด, DataSourceTransactionManager์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ตฌ์„ฑํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    DataSourceTransactionManager txManager = new DataSourceTransactionManager();
    txManager.setDataSource(dataSource);
    txManager.setEnforceReadOnly(true);  // ์ฝ๊ธฐ ์ „์šฉ ํŠธ๋žœ์žญ์…˜ ๊ฐ•์ œ ์„ค์ •
    return txManager;
}

์ง€๊ธˆ๊นŒ์ง€์˜ ๊ณผ์ •์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. DataSourceTransactionManager์˜ doBegin() ๋ฉ”์„œ๋“œ๊ฐ€ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๋Š” ๊ณผ์ •์„ ๋‹ด๋‹นํ•œ๋‹ค.
  2. doBegin() ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ DataSourceUtils.prepareConnectionForTransaction(con, definition)์ด ํ˜ธ์ถœ๋˜๋Š”๋ฐ, ์ด ํ•จ์ˆ˜์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜ ์ •์˜์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ค€๋น„ํ•œ๋‹ค.
  3. ์ด ๊ณผ์ •์—์„œ ConnectionImpl์˜ setReadOnlyInternal() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉฐ, ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ฝ๊ธฐ ์ „์šฉ ์ƒํƒœ๋กœ ์„ค์ •ํ•œ๋‹ค.
  4. ๋งˆ์ง€๋ง‰์œผ๋กœ, DataSourceTransactionManager์˜ doBegin() ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ prepareTransactionalConnection()์ด ํ˜ธ์ถœ๋˜์–ด, ํŠธ๋žœ์žญ์…˜์„ ์œ„ํ•œ ์ตœ์ข…์ ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ค€๋น„๋ฅผ ์™„๋ฃŒํ•œ๋‹ค.

 

4. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์‹คํ–‰๋˜๋Š” ์ด ๋‹จ๊ณ„์—์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์ด ์ฝ๊ธฐ ์ „์šฉ ๋ชจ๋“œ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค๋ฉด, ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ(INSERT, UPDATE, DELETE ๋“ฑ)์„ ์‹œ๋„ํ•˜๋Š” SQL ๋ช…๋ น์ด ์‹คํ–‰๋˜๋ฉด SQLException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒ€์ฆ ๊ณผ์ •์€ JDBC ๋“œ๋ผ์ด๋ฒ„๊ฐ€ SQL ๋ช…๋ น ์‹คํ–‰ ์ „์— ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์—ฌ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

MySQL 5.6.5 ์ด์ƒ 

MySQL 5.6.5 ์ด์ƒ์—์„œ๋Š” JDBC ๋“œ๋ผ์ด๋ฒ„์™€ MySQL ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ 'READ ONLY' ํŠธ๋žœ์žญ์…˜์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ์˜ ๋™์ž‘ ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. JDBC ๋“œ๋ผ์ด๋ฒ„ ์ฐจ์›์˜ ๊ฒ€์ฆ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด SQL ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ•  ๋•Œ, JDBC ๋“œ๋ผ์ด๋ฒ„๋Š” ๋จผ์ € ์—ฐ๊ฒฐ์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์—ฐ๊ฒฐ์ด ์ฝ๊ธฐ ์ „์šฉ ๋ชจ๋“œ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค๋ฉด, JDBC ๋“œ๋ผ์ด๋ฒ„๋Š” ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ์‹œ๋„ํ•˜๋Š” SQL ๋ช…๋ น์„ ๊ฑฐ๋ถ€ํ•˜๊ณ  SQLException์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. 
  2. MySQL ์„œ๋ฒ„ ์ฐจ์›์˜ ๊ฒ€์ฆ: ๋งŒ์•ฝ JDBC ๋“œ๋ผ์ด๋ฒ„ ์ฐจ์›์—์„œ SQL ๋ช…๋ น์ด ๊ฑฐ๋ถ€๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด, SQL ๋ช…๋ น์€ MySQL ์„œ๋ฒ„๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. MySQL ์„œ๋ฒ„๋Š” 'READ ONLY' ํŠธ๋žœ์žญ์…˜ ์„ค์ •์„ ์ธ์‹ํ•˜๊ณ , ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ์‹œ๋„ํ•˜๋Š” SQL ๋ช…๋ น์„ ์ฐจ๋‹จํ•˜๊ณ  ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. 

MySQL 5.6.5 ๋ฏธ๋งŒ

MySQL 5.6.5 ๋ฏธ๋งŒ์—์„œ๋Š” 'READ ONLY'์™€ 'READ WRITE' ํŠธ๋žœ์žญ์…˜ ํŠน์„ฑ์ด ์„œ๋ฒ„ ์ฐจ์›์—์„œ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๊ฒ€์ฆ ๊ณผ์ •์€ JDBC ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์ „์ ์œผ๋กœ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ JDBC ๋“œ๋ผ์ด๋ฒ„๋Š” ๋จผ์ € ์—ฐ๊ฒฐ์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ์—ฐ๊ฒฐ์ด ์ฝ๊ธฐ ์ „์šฉ ๋ชจ๋“œ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”๋ฐ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ์‹œ๋„ํ•˜๋Š” SQL ๋ช…๋ น์ด ๋“ค์–ด์˜จ๋‹ค๋ฉด SQLException์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
 

5. ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ

ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ๋‹จ๊ณ„๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰์ด ์™„๋ฃŒ๋œ ํ›„์— ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์ด ๋‹จ๊ณ„์—์„œ ์ปค๋ฐ‹ ํ˜น์€ ๋กค๋ฐฑ์ด ์ผ์–ด๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

 

์ •๋ฆฌ

์ฝ๊ธฐ ์ „์šฉ ํŠธ๋žœ์žญ์…˜์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์—์„œ ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋ฉฐ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ์ค‘์š”ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ์ž‘์—…์ด ์ฃผ๋ฅผ ์ด๋ฃจ๋Š” ํ™˜๊ฒฝ์—์„œ๋Š” ์ฝ๊ธฐ ์ „์šฉ ํŠธ๋žœ์žญ์…˜์˜ ํ™œ์šฉ์ด ํฐ ์ด์ ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฝ๊ธฐ ์ „์šฉ ํŠธ๋žœ์žญ์…˜์˜ ์„ค์ •, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ์‹คํ–‰, ๊ทธ๋ฆฌ๊ณ  ํŠธ๋žœ์žญ์…˜์˜ ์ข…๋ฃŒ์˜ ์„ธ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๋ฉฐ ์ด ๊ณผ์ •์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ํ•„์š”์— ๋”ฐ๋ผ ์ปค๋ฐ‹ ํ˜น์€ ๋กค๋ฐฑ์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. MySQL 5.6.5 ์ด์ƒ๊ณผ ๋ฏธ๋งŒ์˜ ๋ฒ„์ „์—์„œ๋Š” 'READ ONLY' ํŠธ๋žœ์žญ์…˜์˜ ๊ด€๋ฆฌ ๋ฐฉ์‹์ด ์•ฝ๊ฐ„ ๋‹ค๋ฅด์ง€๋งŒ ํ•ต์‹ฌ์ ์ธ ๋ถ€๋ถ„์€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ ์–ด๋Š ๋ฒ„์ „์—์„œ๋“  ์ฝ๊ธฐ ์ „์šฉ ๋ชจ๋“œ๋กœ ์„ค์ •๋œ ์—ฐ๊ฒฐ์—์„œ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ์‹œ๋„ํ•˜๋ฉด SQLException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. 

java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

GitHub

LinkedIn

GitHub

LinkedIn