ํธ๋์ญ์ ์ปค๋ฅ์ ๋งบ๋ ๊ณผ์


- ์ฌ์ฉ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ ์ ์๋ค. ์ด๋ ์ฌ์ฉ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ์ ์ฐ๊ฒฐ์ ์์ฒญํ๊ณ ์ปค๋ฅ์ ์ ๋งบ๊ฒ ๋๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ๋ ๋ด๋ถ์ ์ผ๋ก ์ธ์ ์ ๋ง๋ ๋ค.
- ๊ทธ๋ฆฌ๊ณ ์ปค๋ฅ์ ์ ํตํ ๋ชจ๋ ์์ฒญ์ ์ด ์ธ์ ์ ํตํด ์คํํ๊ฒ ๋๋ค.
- ์ธ์ ์ ํธ๋์ญ์ ์ ์์ํ๊ณ , ์ปค๋ฐ ๋๋ ๋กค๋ฐฑ์ ํตํด ํธ๋์ญ์ ์ ์ข ๋ฃํ๋ค.
- ์ฌ์ฉ์๊ฐ ์ปค๋ฅ์ ์ ๋ซ๊ฑฐ๋ 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์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํด ์ฌ๋ฌ ์ฐ๊ฒฐ์ ๊ฐ์ง ์ ์์ต๋๋ค. ์ ์ฉ ์๋ฒ ์ฐ๊ฒฐ์์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๊ฐ ์ฐ๊ฒฐ์ ์ํด ์๋ฒ ํ๋ก์ธ์ค๋ฅผ ์์ฑํฉ๋๋ค. ์๋ฒ๋ฅผ ์์ฑํ ํด๋ผ์ด์ธํธ ํ๋ก์ธ์ค๋ง ํด๋น ์๋ฒ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๊ณต์ ์๋ฒ ์ฐ๊ฒฐ์์๋ ์ฌ๋ฌ ํด๋ผ์ด์ธํธ ํ๋ก์ธ์ค๊ฐ ๋จ์ผ ๊ณต์ ์๋ฒ ํ๋ก์ธ์ค์ ์ก์ธ์คํฉ๋๋ค.
๊ฐ์ ์ปค๋ฅ์ ์ ์ ์งํ๋ ๋ฐฉ๋ฒ
- ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ : ํ๋ผ๋ฏธํฐ๋ก ๋ค๊ณ ๋ค๋๋ค.
- ์ปค๋ฅ์ ์ ๋ณ์์ ๋ฃ๊ณ ๊ณต์ ํ๋ค.

ํธ๋์ญ์ ์ถ์ํ
- ํธ๋์ญ์ ๋๊ธฐํ ๋ฌธ์ ํด๊ฒฐ
- ์์ธ ๋์ (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");
}
}
}
๋ช ์์ ์ผ๋ก ํธ๋์ญ์ ๊ตฌํ ๋ฐฉ๋ฒ
- transactionManager.getTransaction()์ ํธ์ถํด ํธ๋์ญ์
์ ์์ํ๋ค.
- ํธ๋์ญ์ ์ ์์ํ๋ ค๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ด ํ์ํ๋ค. ๋ด๋ถ์ ์ผ๋ก DataSource๋ฅผ ์ด์ฉํด ์ปค๋ฅ์ ์ ์์ฑํ๋ค.
- ์ปค๋ฅ์ ์ ์๋ ์ปค๋ฐ ๋ชจ๋๋ก ๋ณ๊ฒฝํ๋ค.
- ์ปค๋ฅ์ ์ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ์ ๋ณด๊ดํ๋ค.
- ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ ์ฐ๋ ๋ ๋ก์ปฌ์ ์ปค๋ฅ์ ์ ๋ณด๊ดํ๋ค.
- SQL ์ํ ์ DataSourceUtils.getConnection์ ์ฌ์ฉํด์ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ์ ๋ณด๊ด๋ ์ปค๋ฅ์ ์ ๊บผ๋ด ์ฌ์ฉํ๋ค.
- ๋น์ฆ๋์ค ๋ก์ง์ด ๋๋๋ฉด ํธ๋์ญ์
๋๊ธฐํ ๋งค๋์ ๋ก๋ถํฐ ์ปค๋ฅ์
์ ์ป์ด ํธ๋์ญ์
์ ์ข
๋ฃํ๋ค. (์ปค๋ฐ or ๋กค๋ฐฑ)
- ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ๋ค
- ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ฅผ ์ ๋ฆฌํ๋ค.
- ์คํ ์ปค๋ฐ ๋ชจ๋๋ก ๋ณ๊ฒฝํ๋ค (์ปค๋ฅ์ ํ ๊ณ ๋ ค)
- ์ปค๋ฅ์ ์ ๋ฐํํ๋ค.
๊ด๋ จ ์ฝ๋
์์ธ ๋ก์ง์ ์ ๋ถ ๋ณด์ง ๋ชปํด์ ์ ํํ ์์น๊ฐ ์๋ ์ ์์ต๋๋ค. ์ฐธ๊ณ ๋ง ๋ถํ ๋๋ฆฝ๋๋ค.
// 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
https://docs.oracle.com/cd/E11882_01/server.112/e40540/process.htm#CNCPT1247
Process Architecture
In parallel execution or parallel processing, multiple processes work together simultaneously to run a single SQL statement. By dividing the work among multiple processes, Oracle Database can run the statement more quickly. For example, four processes hand
docs.oracle.com
ํธ๋์ญ์ ์ปค๋ฅ์ ๋งบ๋ ๊ณผ์


- ์ฌ์ฉ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ ์ ์๋ค. ์ด๋ ์ฌ์ฉ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ์ ์ฐ๊ฒฐ์ ์์ฒญํ๊ณ ์ปค๋ฅ์ ์ ๋งบ๊ฒ ๋๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ๋ ๋ด๋ถ์ ์ผ๋ก ์ธ์ ์ ๋ง๋ ๋ค.
- ๊ทธ๋ฆฌ๊ณ ์ปค๋ฅ์ ์ ํตํ ๋ชจ๋ ์์ฒญ์ ์ด ์ธ์ ์ ํตํด ์คํํ๊ฒ ๋๋ค.
- ์ธ์ ์ ํธ๋์ญ์ ์ ์์ํ๊ณ , ์ปค๋ฐ ๋๋ ๋กค๋ฐฑ์ ํตํด ํธ๋์ญ์ ์ ์ข ๋ฃํ๋ค.
- ์ฌ์ฉ์๊ฐ ์ปค๋ฅ์ ์ ๋ซ๊ฑฐ๋ 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์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํด ์ฌ๋ฌ ์ฐ๊ฒฐ์ ๊ฐ์ง ์ ์์ต๋๋ค. ์ ์ฉ ์๋ฒ ์ฐ๊ฒฐ์์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๊ฐ ์ฐ๊ฒฐ์ ์ํด ์๋ฒ ํ๋ก์ธ์ค๋ฅผ ์์ฑํฉ๋๋ค. ์๋ฒ๋ฅผ ์์ฑํ ํด๋ผ์ด์ธํธ ํ๋ก์ธ์ค๋ง ํด๋น ์๋ฒ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๊ณต์ ์๋ฒ ์ฐ๊ฒฐ์์๋ ์ฌ๋ฌ ํด๋ผ์ด์ธํธ ํ๋ก์ธ์ค๊ฐ ๋จ์ผ ๊ณต์ ์๋ฒ ํ๋ก์ธ์ค์ ์ก์ธ์คํฉ๋๋ค.
๊ฐ์ ์ปค๋ฅ์ ์ ์ ์งํ๋ ๋ฐฉ๋ฒ
- ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ : ํ๋ผ๋ฏธํฐ๋ก ๋ค๊ณ ๋ค๋๋ค.
- ์ปค๋ฅ์ ์ ๋ณ์์ ๋ฃ๊ณ ๊ณต์ ํ๋ค.

ํธ๋์ญ์ ์ถ์ํ
- ํธ๋์ญ์ ๋๊ธฐํ ๋ฌธ์ ํด๊ฒฐ
- ์์ธ ๋์ (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");
}
}
}
๋ช ์์ ์ผ๋ก ํธ๋์ญ์ ๊ตฌํ ๋ฐฉ๋ฒ
- transactionManager.getTransaction()์ ํธ์ถํด ํธ๋์ญ์
์ ์์ํ๋ค.
- ํธ๋์ญ์ ์ ์์ํ๋ ค๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ด ํ์ํ๋ค. ๋ด๋ถ์ ์ผ๋ก DataSource๋ฅผ ์ด์ฉํด ์ปค๋ฅ์ ์ ์์ฑํ๋ค.
- ์ปค๋ฅ์ ์ ์๋ ์ปค๋ฐ ๋ชจ๋๋ก ๋ณ๊ฒฝํ๋ค.
- ์ปค๋ฅ์ ์ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ์ ๋ณด๊ดํ๋ค.
- ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ ์ฐ๋ ๋ ๋ก์ปฌ์ ์ปค๋ฅ์ ์ ๋ณด๊ดํ๋ค.
- SQL ์ํ ์ DataSourceUtils.getConnection์ ์ฌ์ฉํด์ ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ์ ๋ณด๊ด๋ ์ปค๋ฅ์ ์ ๊บผ๋ด ์ฌ์ฉํ๋ค.
- ๋น์ฆ๋์ค ๋ก์ง์ด ๋๋๋ฉด ํธ๋์ญ์
๋๊ธฐํ ๋งค๋์ ๋ก๋ถํฐ ์ปค๋ฅ์
์ ์ป์ด ํธ๋์ญ์
์ ์ข
๋ฃํ๋ค. (์ปค๋ฐ or ๋กค๋ฐฑ)
- ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ๋ค
- ํธ๋์ญ์ ๋๊ธฐํ ๋งค๋์ ๋ฅผ ์ ๋ฆฌํ๋ค.
- ์คํ ์ปค๋ฐ ๋ชจ๋๋ก ๋ณ๊ฒฝํ๋ค (์ปค๋ฅ์ ํ ๊ณ ๋ ค)
- ์ปค๋ฅ์ ์ ๋ฐํํ๋ค.
๊ด๋ จ ์ฝ๋
์์ธ ๋ก์ง์ ์ ๋ถ ๋ณด์ง ๋ชปํด์ ์ ํํ ์์น๊ฐ ์๋ ์ ์์ต๋๋ค. ์ฐธ๊ณ ๋ง ๋ถํ ๋๋ฆฝ๋๋ค.
// 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
https://docs.oracle.com/cd/E11882_01/server.112/e40540/process.htm#CNCPT1247
Process Architecture
In parallel execution or parallel processing, multiple processes work together simultaneously to run a single SQL statement. By dividing the work among multiple processes, Oracle Database can run the statement more quickly. For example, four processes hand
docs.oracle.com