Study/Spring

ํŠธ๋žœ์žญ์…˜

YURI๐Ÿ•๐Ÿ“๐Ÿถ 2023. 12. 13. 20:56
๋ฐ˜์‘ํ˜•

ํŠธ๋žœ์žญ์…˜ ์ปค๋„ฅ์…˜ ๋งบ๋Š” ๊ณผ์ •

One Session for Each Connection ๊ฐ๊ฐ์˜ ์ปค๋„ฅ์…˜๋งˆ๋‹ค ์„ธ์…˜์ด ๋งŒ๋“ค์–ด์ง„ ๋ชจ์Šต
Two Sessions in One Connection

  1. ์‚ฌ์šฉ์ž๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ ์‚ฌ์šฉ์ž๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„์— ์—ฐ๊ฒฐ์„ ์š”์ฒญํ•˜๊ณ  ์ปค๋„ฅ์…˜์„ ๋งบ๊ฒŒ ๋œ๋‹ค.
  2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์„ธ์…˜์„ ๋งŒ๋“ ๋‹ค.
  3. ๊ทธ๋ฆฌ๊ณ  ์ปค๋„ฅ์…˜์„ ํ†ตํ•œ ๋ชจ๋“  ์š”์ฒญ์€ ์ด ์„ธ์…˜์„ ํ†ตํ•ด ์‹คํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค.
  4. ์„ธ์…˜์€ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ , ์ปค๋ฐ‹ ๋˜๋Š” ๋กค๋ฐฑ์„ ํ†ตํ•ด ํŠธ๋žœ์žญ์…˜์„ ์ข…๋ฃŒํ•œ๋‹ค.
  5. ์‚ฌ์šฉ์ž๊ฐ€ ์ปค๋„ฅ์…˜์„ ๋‹ซ๊ฑฐ๋‚˜ DBA๊ฐ€ ์„ธ์…˜์„ ๊ฐ•์ œ๋กœ ์ข…๋ฃŒํ•˜๋ฉด ์„ธ์…˜์ด ์ข…๋ฃŒ๋œ๋‹ค.

 

์ฐธ๊ณ 

  • DataSource์˜ getConnection์„ ํ†ตํ•ด ๊ฐ€์ ธ์˜จ ์ปค๋„ฅ์…˜์€ ๊ธฐ๋ณธ์ ์œผ๋กœ autoCommit ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•œ๋‹ค.
  • ํ•˜์ง€๋งŒ autoCommit ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•˜๋ฉด ์ฟผ๋ฆฌ๋ฅผ ํ•˜๋‚˜ ์‹คํ–‰ํ• ๋•Œ๋งˆ๋‹ค ๋ฐ”๋กœ ๋ฐ”๋กœ ์ปค๋ฐ‹์ด ๋˜์–ด ๋ฒ„๋ ค ํŠธ๋žœ์žญ์…˜์ด ์‚ฌ์‹ค์ƒ ์—†๋Š” ๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋‹ค.
  • ๊ทธ๋ž˜์„œ ์ž๋ฐ”์—์„œ๋Š” ์ž๋™ ์ปค๋ฐ‹ ๋ชจ๋“œ์—์„œ ์ˆ˜๋™ ์ปค๋ฐ‹ ๋ชจ๋“œ๋กœ ์ „ํ™˜ ํ•˜๋Š” ๊ฒƒ์„ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•œ๋‹ค๊ณ  ํ‘œํ˜„ํ•œ๋‹ค
  • ์ค‘์š” : ๊ฐ™์€ DB ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ์ž๋ฐ”์—์„œ ํŠธ๋žœ์žญ์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๋™์•ˆ ๊ฐ™์€ ์ปค๋„ฅ์…˜์„ ์œ ์ง€ํ•ด์•ผํ•œ๋‹ค.

 

์ปค๋„ฅ์…˜๊ณผ ์„ธ์…˜ ์ฐจ์ด
Connections and Sessions A connection is a physical communication pathway between a client process and a database instance. A communication pathway is established using available interprocess communication mechanisms or network software. Typically, a connection occurs between a client process and a server process or dispatcher, but it can also occur between a client process and Oracle Connection Manager (CMAN). A session is a logical entity in the database instance memory that represents the state of a current user login to a database. For example, when a user is authenticated by the database with a password, a session is established for this user. A session lasts from the time the user is authenticated by the database until the time the user disconnects or exits the database application. A single connection can have 0, 1, or more sessions established on it. The sessions are independent: a commit in one session does not affect transactions in other sessions. Note: If Oracle Net connection pooling is configured, then it is possible for a connection to drop but leave the sessions intact. Multiple sessions can exist concurrently for a single database user. As shown in Figure 15-2, user hr can have multiple connections to a database. In dedicated server connections, the database creates a server process on behalf of each connection. Only the client process that causes the dedicated server to be created uses it. In a shared server connection, many client processes access a single shared server



Connection์€ ํด๋ผ์ด์–ธํŠธ ํ”„๋กœ์„ธ์Šค์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ์Šคํ„ด์Šค ๊ฐ„์˜ ๋ฌผ๋ฆฌ์  ํ†ต์‹  ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค. ํ†ต์‹  ๊ฒฝ๋กœ๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ”„๋กœ์„ธ์Šค ๊ฐ„ ํ†ต์‹  ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๋˜๋Š” ๋„คํŠธ์›Œํฌ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.
์ผ๋ฐ˜์ ์œผ๋กœ Connection์€ ํด๋ผ์ด์–ธํŠธ ํ”„๋กœ์„ธ์Šค์™€ ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค ๋˜๋Š” ๋””์ŠคํŒจ์ฒ˜ ๊ฐ„์— ๋ฐœ์ƒํ•˜์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ ํ”„๋กœ์„ธ์Šค์™€ Oracle Connection Manager (CMAN) ๊ฐ„์—๋„ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์ฐธ๊ณ :
๋งŒ์•ฝ Oracle Net ์—ฐ๊ฒฐ ํ’€๋ง์ด ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค๋ฉด, ์—ฐ๊ฒฐ์€ ๋Š์–ด์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ ์„ธ์…˜์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹จ์ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ์„ธ์…˜์ด ๋™์‹œ์— ์กด์žฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆผ 15-2์—์„œ ๋‚˜ํƒ€๋‚œ ๊ฒƒ์ฒ˜๋Ÿผ, ์‚ฌ์šฉ์ž hr์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ์—ฐ๊ฒฐ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์šฉ ์„œ๋ฒ„ ์—ฐ๊ฒฐ์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๊ฐ ์—ฐ๊ฒฐ์„ ์œ„ํ•ด ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„๋ฅผ ์ƒ์„ฑํ•œ ํด๋ผ์ด์–ธํŠธ ํ”„๋กœ์„ธ์Šค๋งŒ ํ•ด๋‹น ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ณต์œ  ์„œ๋ฒ„ ์—ฐ๊ฒฐ์—์„œ๋Š” ์—ฌ๋Ÿฌ ํด๋ผ์ด์–ธํŠธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‹จ์ผ ๊ณต์œ  ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค์— ์•ก์„ธ์Šคํ•ฉ๋‹ˆ๋‹ค.

๊ฐ™์€ ์ปค๋„ฅ์…˜์„ ์œ ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ• : ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋“ค๊ณ  ๋‹ค๋‹Œ๋‹ค.
  • ์ปค๋„ฅ์…˜์„ ๋ณ€์ˆ˜์— ๋„ฃ๊ณ  ๊ณต์œ ํ•œ๋‹ค.

TransactionManager๋ฅผ ์ƒ์†ํ•˜๊ณ  ์žˆ๋‹ค,

ํŠธ๋žœ์žญ์…˜ ์ถ”์ƒํ™”

  • ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋ฌธ์ œ ํ•ด๊ฒฐ
  • ์˜ˆ์™ธ ๋ˆ„์ˆ˜ (JDBC ๊ตฌํ˜„ ๊ธฐ์ˆ  ์˜ˆ์™ธ๊ฐ€ ์„œ๋น„์Šค ๊ณ„์ธต์œผ๋กœ ์ „ํŒŒ)
  • ํŠธ๋žœ์žญ์…˜ ์ ์šฉ ๋ฐ˜๋ณต ๋ฌธ์ œ (try, catch, finally์˜ ๋ฌดํ•œ๋ฐ˜๋ณต)
  • ์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์„ ์ถ”์ƒํ™”๋ฅผ ํ†ตํ•ด ํ•ด๊ฒฐํ–ˆ๋‹ค.
  • ์Šคํ”„๋ง : PlatformTransactionManager (TransactionManager)
    • ๋ฉ”์„œ๋“œ
      • getTransaction
      • commit
      • rollback
    • ํŠธ๋žœ์žญ์…˜ ๋งค๋‹ˆ์ €๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋งค๋‹ˆ์ €๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
    • ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋งค๋‹ˆ์ €๋Š” ์“ฐ๋ ˆ๋“œ ๋กœ์ปฌ์„ ์‚ฌ์šฉํ•ด์„œ ์ปค๋„ฅ์…˜์„ ๋™๊ธฐํ™”ํ•ด์ค€๋‹ค. (๋งค๋ฒˆ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งˆ๋‹ค ๋“ค๊ณ ๋‹ค๋‹ˆ์ง€ ์•Š์•„๋„ ๋œ๋‹ค)
// TransactionManager
if (txObject.isNewConnectionHolder()) {
    TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
}

// ์ถ”์ƒ ํด๋ž˜์Šค๋กœ ๊ฐ์ฒด ์ƒ์„ฑ ๋ถˆ๊ฐ€๋Šฅ
public abstract class TransactionSynchronizationManager {
	// ๋‚ด๋ถ€์ ์œผ๋กœ ThreadLocal๋กœ multi thread safeํ•˜๊ฒŒ ์ €์žฅํ•˜๊ณ  ์žˆ๋‹ค,
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");

   
    @Nullable
    private static Object doGetResource(Object actualKey) {
        Map<Object, Object> map = (Map)resources.get();
        if (map == null) {
            return null;
        } else {
            Object value = map.get(actualKey);
            if (value instanceof ResourceHolder) {
                ResourceHolder resourceHolder = (ResourceHolder)value;
                if (resourceHolder.isVoid()) {
                    map.remove(actualKey);
                    if (map.isEmpty()) {
                        resources.remove();
                    }

                    value = null;
                }
            }

            return value;
        }
    }

    public static void bindResource(Object key, Object value) throws IllegalStateException {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Assert.notNull(value, "Value must not be null");
        Map<Object, Object> map = (Map)resources.get();
        if (map == null) {
            map = new HashMap();
            resources.set(map);
        }

        Object oldValue = ((Map)map).put(actualKey, value);
        if (oldValue instanceof ResourceHolder resourceHolder) {
            if (resourceHolder.isVoid()) {
                oldValue = null;
            }
        }

        if (oldValue != null) {
            throw new IllegalStateException("Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
        }
    }

}

๋ช…์‹œ์ ์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

  1. transactionManager.getTransaction()์„ ํ˜ธ์ถœํ•ด ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•œ๋‹ค.
    1. ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜์ด ํ•„์š”ํ•˜๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ DataSource๋ฅผ ์ด์šฉํ•ด ์ปค๋„ฅ์…˜์„ ์ƒ์„ฑํ•œ๋‹ค.
    2. ์ปค๋„ฅ์…˜์„ ์ˆ˜๋™ ์ปค๋ฐ‹ ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
    3. ์ปค๋„ฅ์…˜์„ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋งค๋‹ˆ์ €์— ๋ณด๊ด€ํ•œ๋‹ค.
    4. ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋งค๋‹ˆ์ €๋Š” ์“ฐ๋ ˆ๋“œ ๋กœ์ปฌ์— ์ปค๋„ฅ์…˜์„ ๋ณด๊ด€ํ•œ๋‹ค.
  2. SQL ์ˆ˜ํ–‰ ์‹œ DataSourceUtils.getConnection์„ ์‚ฌ์šฉํ•ด์„œ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋งค๋‹ˆ์ €์— ๋ณด๊ด€๋œ ์ปค๋„ฅ์…˜์„ ๊บผ๋‚ด ์‚ฌ์šฉํ•œ๋‹ค.
  3. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋๋‚˜๋ฉด ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋งค๋‹ˆ์ €๋กœ๋ถ€ํ„ฐ ์ปค๋„ฅ์…˜์„ ์–ป์–ด ํŠธ๋žœ์žญ์…˜์„ ์ข…๋ฃŒํ•œ๋‹ค. (์ปค๋ฐ‹ or ๋กค๋ฐฑ)
    1. ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•œ๋‹ค
    2. ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋งค๋‹ˆ์ €๋ฅผ ์ •๋ฆฌํ•œ๋‹ค.
    3. ์˜คํ†  ์ปค๋ฐ‹ ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค (์ปค๋„ฅ์…˜ ํ’€ ๊ณ ๋ ค)
    4. ์ปค๋„ฅ์…˜์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

๊ด€๋ จ ์ฝ”๋“œ

์ƒ์„ธ ๋กœ์ง์„ ์ „๋ถ€ ๋ณด์ง€ ๋ชปํ•ด์„œ ์ •ํ™•ํ•œ ์œ„์น˜๊ฐ€ ์•„๋‹ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฐธ๊ณ ๋งŒ ๋ถ€ํƒ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
// 1. ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ getTransaction์„ ํ˜ธ์ถœํ•ด ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•œ๋‹ค.

// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
    TransactionDefinition def = definition != null ? definition : TransactionDefinition.withDefaults();
    
    // 2. ์ปค๋„ฅ์…˜์„ ์‹œ์ž‘ํ•œ๋‹ค. (๋‚ด๋ถ€์ ์œผ๋กœ doBegin์—์„œ ํš๋“)
    Object transaction = this.doGetTransaction();
    boolean debugEnabled = this.logger.isDebugEnabled();
    if (this.isExistingTransaction(transaction)) {
        return this.handleExistingTransaction(def, transaction, debugEnabled);
    } else if (def.getTimeout() < -1) {
        throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
    } else if (def.getPropagationBehavior() == 2) {
        throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
    } else if (def.getPropagationBehavior() != 0 && def.getPropagationBehavior() != 3 && def.getPropagationBehavior() != 6) {
        if (def.getIsolationLevel() != -1 && this.logger.isWarnEnabled()) {
            this.logger.warn("Custom isolation level specified but no actual transaction initiated; isolation level will effectively be ignored: " + def);
        }

        boolean newSynchronization = this.getTransactionSynchronization() == 0;
        return this.prepareTransactionStatus(def, (Object)null, true, newSynchronization, debugEnabled, (Object)null);
    } else {
        SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
        if (debugEnabled) {
            Log var10000 = this.logger;
            String var10001 = def.getName();
            var10000.debug("Creating new transaction with name [" + var10001 + "]: " + def);
        }

        try {
            return this.startTransaction(def, transaction, false, debugEnabled, suspendedResources);
        } catch (Error | RuntimeException var7) {
            this.resume((Object)null, suspendedResources);
            throw var7;
        }
    }
}


// 2. ์ปค๋„ฅ์…˜์„ ์‹œ์ž‘ํ•œ๋‹ค. (๋‚ด๋ถ€์ ์œผ๋กœ doBegin์—์„œ ํš๋“)
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject)transaction;
    Connection con = null;

    try {
        if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            Connection newCon = this.obtainDataSource().getConnection();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }

            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        txObject.setReadOnly(definition.isReadOnly());
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
			// 3. ์ปค๋„ฅ์…˜์„ ์ˆ˜๋™ ์ปค๋ฐ‹ ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
            con.setAutoCommit(false);
        }

        this.prepareTransactionalConnection(con, definition);
        txObject.getConnectionHolder().setTransactionActive(true);
        int timeout = this.determineTimeout(definition);
        if (timeout != -1) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }

        if (txObject.isNewConnectionHolder()) {
        	// 4.์ปค๋„ฅ์…˜์„ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋งค๋‹ˆ์ €์— ๋ณด๊ด€ํ•œ๋‹ค.
            TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
        }

    } catch (Throwable var7) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, this.obtainDataSource());
            txObject.setConnectionHolder((ConnectionHolder)null, false);
        }

        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
    }
}
// DataSourceUtils.getConnection()
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");\
    
    // TransactionSynchronizationManager ํŠธ๋žœ์žญ์…˜๋™๊ธฐํ™” ๋งค๋‹ˆ์ €์—์„œ ์ปค๋„ฅ์…˜์„ ๊ฐ€์ ธ์˜จ๋‹ค
    ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
    if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
        logger.debug("Fetching JDBC Connection from DataSource");
        Connection con = fetchConnection(dataSource);
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            try {
                ConnectionHolder holderToUse = conHolder;
                if (conHolder == null) {
                    holderToUse = new ConnectionHolder(con);
                } else {
                    conHolder.setConnection(con);
                }

                holderToUse.requested();
                TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(holderToUse, dataSource));
                holderToUse.setSynchronizedWithTransaction(true);
                if (holderToUse != conHolder) {
                    TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                }
            } catch (RuntimeException var4) {
                releaseConnection(con, dataSource);
                throw var4;
            }
        }

        return con;
    } else {
        conHolder.requested();
        if (!conHolder.hasConnection()) {
            logger.debug("Fetching resumed JDBC Connection from DataSource");
            conHolder.setConnection(fetchConnection(dataSource));
        }

        return conHolder.getConnection();
    }
}

 

TransactionalTemplate

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager)this.transactionManager).execute(this, action);
    } else {
        TransactionStatus status = this.transactionManager.getTransaction(this);

        Object result;
        try {
            result = action.doInTransaction(status);
        } catch (RuntimeException var5) {
            this.rollbackOnException(status, var5);
            throw var5;
        } catch (Error var6) {
            this.rollbackOnException(status, var6);
            throw var6;
        } catch (Exception var7) {
            this.rollbackOnException(status, var7);
            throw new UndeclaredThrowableException(var7, "TransactionCallback threw undeclared checked exception");
        }

        this.transactionManager.commit(status);
        return result;
    }
}

 

PlatformTransactionManager์„ ํ†ตํ•ด ๋ช…์‹œ์ ์œผ๋กœ(ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹) ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด๋„ ๋˜์ง€๋งŒ, ์ข€ ๋” ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•œ๋‹ค.

  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•
  • ์Šคํ”„๋ง์ด ํ…œํ”Œ๋ฆฟ ์ฝœ๋ฐฑ ํŒจํ„ด์„ ์ด์šฉํ•ด ๋งŒ๋“  ํŠธ๋žœ์žญ์…˜ ํ”„๋ก์‹œ ๊ฐ์ฒด
  • UnChecked Exception์ด ๋ฐœ์ƒํ•˜๋ฉด ๋กค๋ฐฑํ•˜๊ณ , ๊ทธ ์ด์™ธ์—๋Š” ์ปค๋ฐ‹ํ•œ๋‹ค.
  • ์„ ์–ธ์   ๋ฐฉ๋ฒ• : @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ”„๋ก์‹œ ๋กœ์ง์„ ๋ถ™์ธ๋‹ค.
    • ์–ด๋“œ๋ฐ”์ด์ €: BeanFactoryTransactionAttributeSourceAdvisor
    • ํฌ์ธํŠธ์ปท: TransactionAttributeSourcePointcut
    • ์–ด๋“œ๋ฐ”์ด์Šค: TransactionInterceptor

 

SQL ์—๋Ÿฌ ๋ณ€ํ™˜๊ธฐ

  • SQLExceptionTranslator
  • DB๋ณ„๋กœ ์—๋Ÿฌ์ฝ”๋“œ๋ฅผ ๋ฏธ๋ฆฌ ์ง€์ •ํ•ด๋†“๊ณ , ๊ฐ ์—๋Ÿฌ์ฝ”๋“œ ์œ ํ˜•๋งˆ๋‹ค String[]๋กœ ์ €์žฅํ•ด๋†“๋Š”๋‹ค.
  • ๋‚ด๋ถ€์—์„œ BinarySearch ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ์กฐํšŒํ•œ๋‹ค.
if (Arrays.binarySearch(sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new BadSqlGrammarException(task, sql != null ? sql : "", sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new InvalidResultSetAccessException(task, sql != null ? sql : "", sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new DuplicateKeyException(this.buildMessage(task, sql, sqlEx), sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new DataIntegrityViolationException(this.buildMessage(task, sql, sqlEx), sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new PermissionDeniedDataAccessException(this.buildMessage(task, sql, sqlEx), sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new DataAccessResourceFailureException(this.buildMessage(task, sql, sqlEx), sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new TransientDataAccessResourceException(this.buildMessage(task, sql, sqlEx), sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new CannotAcquireLockException(this.buildMessage(task, sql, sqlEx), sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new DeadlockLoserDataAccessException(this.buildMessage(task, sql, sqlEx), sqlEx);
}

if (Arrays.binarySearch(sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {
    this.logTranslation(task, sql, sqlEx, false);
    return new CannotSerializeTransactionException(this.buildMessage(task, sql, sqlEx), sqlEx);
}

// sql-error-codes.xml
<bean id="DB2" name="Db2" class="org.springframework.jdbc.support.SQLErrorCodes">
    <property name="databaseProductName">
        <value>DB2*</value>
    </property>
    <property name="badSqlGrammarCodes">
        <value>-007,-029,-097,-104,-109,-115,-128,-199,-204,-206,-301,-408,-441,-491</value>
    </property>
    <property name="duplicateKeyCodes">
        <value>-803</value>
    </property>
    <property name="dataIntegrityViolationCodes">
        <value>-407,-530,-531,-532,-543,-544,-545,-603,-667</value>
    </property>
    <property name="dataAccessResourceFailureCodes">
        <value>-904,-971</value>
    </property>
    <property name="transientDataAccessResourceCodes">
        <value>-1035,-1218,-30080,-30081</value>
    </property>
    <property name="deadlockLoserCodes">
        <value>-911,-913</value>
    </property>
</bean>

 

ํŠธ๋žœ์žญ์…˜ ์šฐ์„  ์ˆœ์œ„

  • ๊ตฌ์ฒด์ ์ด๊ณ  ์ž์„ธํ•œ ๊ฒƒ์ด ๋” ๋†’์€ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฐ€์ง„๋‹ค.
  • ํด๋ž˜์Šค < ๋ฉ”์„œ๋“œ
  • ์ธํ„ฐํŽ˜์ด์Šค < ๊ตฌํ˜„ ํด๋ž˜์Šค

์ฃผ์˜์‚ฌํ•ญ

  • ํ”„๋ก์‹œ ๋‚ด๋ถ€ ํ˜ธ์ถœ์€ ํŠธ๋žœ์žญ์…˜ ์ ์šฉ ์•ˆ๋จ.
  • public ๋ฉ”์„œ๋“œ์—๋งŒ ํŠธ๋žœ์žญ์…˜ ์ ์šฉ ๊ฐ€๋Šฅ (์˜ˆ์™ธ ๋ฐœ์ƒํ•˜์ง€๋Š” ์•Š๊ณ  ๊ทธ๋ƒฅ ๋ฌด์‹œ)
    • ์ฐธ๊ณ ๋กœ ๋ถ€ํŠธ 3.0๋ถ€ํ„ฐ๋Š” protected, package-private๋„ ์ ์šฉ๋จ
  • @PostContruct๊ณผ ๋™์‹œ ์‚ฌ์šฉ ๋ถˆ๊ฐ€ (์ดˆ๊ธฐํ™” ์ฝ”๋“œ๊ฐ€ ๋จผ์ € ํ˜ธ์ถœ -> ํŠธ๋žœ์žญ์…˜ AOP๊ฐ€ ์ ์šฉ๋จ)

ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ

  • ๋ชจ๋“  ๋…ผ๋ฆฌ ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹ ๋˜์–ด์•ผ ๋ฌผ๋ฆฌ ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋œ๋‹ค.
  • ํ•˜๋‚˜์˜ ๋…ผ๋ฆฌ ํŠธ๋žœ์žญ์…˜์ด๋ผ๋„ ๋กค๋ฐฑ๋˜๋ฉด ๋ฌผ๋ฆฌ ํŠธ๋žœ์žญ์…˜์€ ๋กค๋ฐฑ๋œ๋‹ค.

 

๐Ÿ‘ Reference

๋ฐ˜์‘ํ˜•