> For the complete documentation index, see [llms.txt](https://docs.tibero.com/tibero-manuals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tibero.com/tibero-manuals/7.2.6.manuals/jdbc-development-guide/tbjdbc-usage.md).

# tbJDBC의 사용

tbJDBC를 사용하여 애플리케이션 프로그램을 개발하는 과정을 순서대로 설명하고, 추가로 데이터 타입, Stream 등을 설명합니다.

## 개발 과정

다음은 tbJDBC를 사용하여 애플리케이션 프로그램을 개발하는 과정입니다.

1. 패키지 **import**

기본이 되는 Java 패키지와 tbJDBC 패키지를 import합니다.

```
/* 기본 Java 패키지 */ 
import java.sql.*;

/* tbJDBC 패키지 */
import com.tmax.tibero.jdbc.*; 
import com.tmax.tibero.jdbc.ext.*;
```

2. 데이터베이스 연결

데이터베이스에 연결하기 위해서 Connection 객체를 생성합니다. Connection 객체를 생성하기 위해서 tbJDBC를 로딩한 후 DriverManager를 이용합니다.

```
Class.forName("com.tmax.tibero.jdbc.TbDriver");
Connection conn = DriverManager.getConnection
                 ("jdbc:tibero:thin:@localhost:8629:dbsvr", 
                  "tibero", "tmax");
```

또는 다음과 같이 java.util.Properties를 이용하여 Connection 설정을 지정할 수 있습니다.

```
Class.forName("com.tmax.tibero.jdbc.TbDriver"); 
Properties prop = new Properties(); 
prop.setProperty("user", "tibero"); 
prop.setProperty("password", "tmax"); 
Connection conn =
    DriverManager.getConnection("jdbc:tibero:thin:@localhost:8629:dbsvr", prop);
```

또는 다음과 같이 DataSource 객체를 이용하여 Connection 객체를 생성합니다.

```
TbDataSource ds = new TbDataSource(); 
ds.setURL("jdbc:tibero:thin:@localhost:8629:dbsvr");
ds.setUser("tibero"); 
ds.setPassword("tmax");
Connection conn = ds.getConnection();
```

3. **Statement** 객체 생성

데이터베이스에 연결되면, Connection 객체를 이용하여 Statement 객체를 생성합니다.

```
Statement            stmt = conn.createStatement();
PreparedStatement    pstmt = conn.prepareStatement("SELECT empno, name FROM emp");
CallableStatement    cstmt = conn.prepareCall("BEGIN ... END;");
```

4. 질의문 수행과 **ResultSet** 객체 받기

질의문을 수행하고 ResultSet 객체를 받습니다.

```
ResultSet rs = stmt.executeQuery("SELECT * FROM ALL_OBJECTS;");
```

5. **ResultSet** 객체 처리

ResultSet 객체는 next() 메소드를 이용하여 모든 로우에 접근할 수 있습니다. 이때 getXXX() 메소드를 호출하면 원하는 타입별로 로우 데이터를 가져올 수 있습니다. 만약 next() 메소드가 false라면, 더는 결과 집합이 존재하지 않음을 의미합니다.

```
while (rs.next())
{
    System.out.println(rs.getString(1)); 
    System.out.println(rs.getInt(2));
}
```

6. 커밋 또는 롤백 수행

기본적으로 DML 문(INSERT, UPDATE, DELETE)을 수행한 다음에는 자동으로 커밋됩니다. 이러한 기능을 auto-commit이라고 합니다. 사용자는 Connection 클래스에서 제공하는 다음 메소드를 이용하여 이 기능을 임의로 해제할 수 있습니다.

```
void setAutoCommit(boolean flag)
```

이 메소드를 이용하여 auto-commit 기능을 비활성화하면 사용자는 반드시 DML 문을 사용한 다음 커밋이나 롤백을 수행해야 합니다.

```
conn.commit(); 
conn.rollback();
```

7. **ResultSet** 객체와 **Statement** 객체 소멸

ResultSet 객체와 Statement 객체는 반드시 소멸시켜야 합니다. tbJDBC 내부에는 별도의 finalizer 메소드가 없기 때문입니다. 그렇지 않으면 예상치 못한 시점에 심각한 메모리 누수 현상이 발생할 수 있고, 데이터베이스 내부 커서의 최대 허용 범위를 초과하여 에러가 발생할 수도 있습니다.

```
rs.close();
stmt.close();
```

8. 데이터베이스 연결 해제

마지막으로 모든 작업이 끝나면 반드시 데이터베이스 연결을 해제해야 합니다. 그렇지 않으면 현재 세션이 연결된 상태로 남아 있어, 다음에 연결할 때 최대 허용 세션 수를 초과할 수 있습니다. 따라서 데이터베이스 연결에 실패할 수도 있습니다.

```
conn.close();
```

## Connection Properties

다음은 DB를 연결할 때에 Properties로 설정할 수 있는 속성들입니다.

<table><thead><tr><th width="195">속성명</th><th width="91">타입</th><th>설명</th></tr></thead><tbody><tr><td>databaseName</td><td>String</td><td>서버에 존재하는 특정 데이터베이스의 이름</td></tr><tr><td>dataSourceName</td><td>String</td><td>데이터소스의 이름</td></tr><tr><td>description</td><td>String</td><td>데이터소스에 대한 설명</td></tr><tr><td>networkProtocol</td><td>String</td><td>서버와 통신하는 네트워크 프로토콜의 이름<br>(기본값: TCP)</td></tr><tr><td>password</td><td>String</td><td>서버 접속을 위한 패스워드</td></tr><tr><td>user</td><td>String</td><td>서버 접속을 위한 사용자 이름</td></tr><tr><td>portNumber</td><td>int</td><td>서버 리스너의 포트 번호</td></tr><tr><td>serverName</td><td>String</td><td>데이터베이스의 이름</td></tr><tr><td>login_timeout</td><td>int</td><td><ul><li>소켓의 read timeout을 지정</li><li>최소 소켓을 생성할 때부터 DB 연결 생성이 완료될 때까지 적용</li><li>DB 연결 생성이 완료된 뒤에는 read_timeout 속성이 적용된다. 만약 응답이 없이 지정된 시간이 지나면 timeout을 발생시키지만 설정값이 0일 경우에는 timeout을 발생시키지 않음<br>(단위: millisecond, 기본값: 0)</li></ul></td></tr><tr><td>read_timeout</td><td>int</td><td><ul><li>DB 연결 생성이 완료된 이후의 소켓의 read timeout을 지정</li><li>만약 응답이 없이 지정된 시간이 지나면 timeout을 발생시키지만 설정값이 0일 경우에는 timeout을 발생시키지 않음<br>(단위: millisecond, 기본값: 0)</li></ul></td></tr><tr><td>characterset</td><td>String</td><td>JDBC에서 인코딩, 디코딩 Character Set을 지정<br>(기본값: 서버의 문자 셋을 사용)</td></tr><tr><td>program_name</td><td>String</td><td>프로그램 이름(기본값: JDBC Thin Client)</td></tr><tr><td>includeSynonyms</td><td>String</td><td>DatabaseMetaData.getColumn()에서 synonyms 객체를 포함할지 여부를 지정(기본값: false)</td></tr><tr><td>mapDateToTimestamp</td><td>String</td><td>DATE 컬럼에 대해서 결과 타입으로 Timestamp(true)로 돌려줄지 Date(false)로 여부를 지정(기본값: true)</td></tr><tr><td>defaultNChar</td><td>String</td><td>PreparedStatement.setString() API로 설정한 문자열을 national charset 설정을 이용하여 서버로 전송하도록 강제함<br>(기본값: false)</td></tr><tr><td>self_keepalive</td><td>String</td><td><ul><li>SELF KEEP ALIVE 기능을 활성화할지 여부를 지정</li><li>활성화한 경우에는 self_keepidle, self_keepintvl, self_keepcnt 속성 설정에 따라 연결 대상에 대한 네트워크 접근에 문제가 없는지 확인</li><li>전체 확인 과정에 실패하면, 해당 네트워크 연결을 강제로 종료시킴<br>(기본값: false)</li></ul></td></tr><tr><td>self_keepidle</td><td>int</td><td><ul><li>접속 완료, DB 요청/응답 처리 등의 정상적인 네트워크 사용 이후, 다음 번의 정상 처리된 시간이 갱신되지 않을 때, 정상 상황으로 간주할 최대 시간을 지정</li><li>지정된 시간이 지나면 네트워크 접근에 대한 확인 절차가 시작됨</li><li><strong>self_keepalive</strong> 설정이 <strong>true</strong>인 경우에만 유효<br>(단위: 초, 기본값: 60)</li></ul></td></tr><tr><td>self_keepintvl</td><td>int</td><td><ul><li>매번 확인할 때의 간격 및 한 번 확인할 때 최대 대기 시간을 지정</li><li><strong>self_keepalive</strong> 설정이 <strong>true</strong>인 경우에만 유효<br>(단위: 초, 기본값: 10)</li></ul></td></tr><tr><td>self_keepcnt</td><td>int</td><td><ul><li>확인 절차를 총 몇 회까지 수행할 것인지를 지정</li><li>지정된 횟수만큼 연속으로 실패하여야만 전체 확인 과정이 실패한 것으로 처리됨</li><li><strong>self_keepalive</strong> 설정이 <strong>true</strong>인 경우에만 유효<br>(기본값: 3)</li></ul></td></tr><tr><td>failover_retry_count</td><td>int</td><td><ul><li>Failover 기능이 활성화되었을 때 연결 복원을 시도하는 최대 횟수를 지정(기본값: 3)</li><li>Failover 기능에 대한 자세한 설명은 “Failover 및 Load balancing”의 내용을 참고</li></ul></td></tr><tr><td>load_balance</td><td>String</td><td><ul><li>로드 밸런싱 기능의 동작 방식을 설정(기본값: OFF)</li><li>로드 밸런싱 기능에 대한 자세한 설명은 “<a href="/tibero-manuals/7.2.6.manuals/jdbc-development-guide/failover-load-balancing.md">제9장 Failover와 Load balancing</a>”의 내용을 참고</li></ul></td></tr></tbody></table>

## 데이터 타입

tbJDBC는 JDBC 타입을 지원할 뿐만 아니라 Interval 타입을 추가로 제공합니다.

다음은 JDBC 타입과 tbJDBC 타입과의 대응 관계를 나타내는 표입니다.

| JDBC 타입(표준)                                       | Java 타입(표준)          | tbJDBC 타입                          |
| ------------------------------------------------- | -------------------- | ---------------------------------- |
| java.sql.Types.CHAR                               | java.lang.String     | java.lang.String                   |
| java.sql.Types.VARCHAR                            | java.lang.String     | java.lang.String                   |
| java.sql.Types.LONGVARCHAR                        | java.lang.String     | java.lang.String                   |
| java.sql.Types.NUMERIC                            | java.math.BigDecimal | java.math.BigDecimal               |
| java.sql.Types.DECIMAL                            | java.math.BigDecimal | java.math.BigDecimal               |
| java.sql.Types.BIT                                | boolean              | boolean                            |
| java.sql.Types.TINYINT                            | byte                 | byte                               |
| java.sql.Types.SMALLINT                           | short                | short                              |
| java.sql.Types.INTEGER                            | int                  | int                                |
| java.sql.Types.BIGINT                             | long                 | long                               |
| java.sql.Types.REAL                               | float                | float                              |
| java.sql.Types.FLOAT                              | double               | double                             |
| java.sql.Types.DOUBLE                             | double               | double                             |
| java.sql.Types.BINARY                             | byte\[]              | byte\[]                            |
| java.sql.Types.VARBINARY                          | byte\[]              | byte\[]                            |
| java.sql.Types.LONGVARBINARY                      | byte\[]              | byte\[]                            |
| java.sql.Types.DATE                               | java.sql.Date        | java.sql.Date                      |
| java.sql.Types.TIME                               | java.sql.Time        | java.sql.Time                      |
| java.sql.Types.TIMESTAMP                          | java.sql.Timestamp   | java.sql.Timestamp                 |
| java.sql.Types.BLOB                               | java.sql.Blob        | com.tmax.tibero.jdbc.TbBlob        |
| java.sql.Types.CLOB                               | java.sql.Clob        | com.tmax.tibero.jdbc.TbClob        |
| java.sql.Types.NCLOB                              | java.sql.NClob       | com.tmax.tibero.jdbc.TbNClob       |
| java.sql.Types.NCHAR                              | java.sql.NCHAR       | java.lang.String                   |
| java.sql.Types.NVARCHAR                           | java.sql.NVARCHAR    | java.lang.String                   |
| java.sql.Types.SQLXML                             | java.sql.SQLXML      | com.tmax.tibero.jdbc.TbSQLXML      |
| com.tmax.tibero.jdbc.data.DataType.VARRAY         | java.sql.Array       | com.tmax.tibero.jdbc.TbArray       |
| com.tmax.tibero.jdbc.data.DataType.Struct         | java.sql.Struct      | com.tmax.tibero.jdbc.TbUpStruct    |
| com.tmax.tibero.jdbc.data.DataType.CURSOR         | -                    | com.tmax.tibero.jdbc.TbResultSet   |
| com.tmax.tibero.jdbc.data.DataType.ITV\_DTS       | -                    | com.tmax.tibero.jdbc.TbIntervalDts |
| com.tmax.tibero.jdbc.data.DataType.ITV\_YTM       | -                    | com.tmax.tibero.jdbc.TbIntervalYtm |
| com.tmax.tibero.jdbc.data.DataType.RowId          | -                    | com.tmax.tibero.jdbc.TbRowId       |
| com.tmax.tibero.jdbc.data.DataType.TIMESTAMP\_TZ  | java.sql.Timestamp   | com.tmax.tibero.jdbc.TbTimestampTZ |
| com.tmax.tibero.jdbc.data.DataType.TIMESTAMP\_LTZ | java.sql.Timestamp   | java.sql.Timestamp                 |

{% hint style="info" %}
**참고**

Tibero는 TIME 데이터 타입을 지원하지만 DATE 데이터 타입 사용을 권장합니다.

TIME 컬럼에는 PreparedStatement.setTime() 메소드로 값을 지정할 수 없습니다. PreparedStatement.setTime()은 DATE 데이터 타입 컬럼에서 지원합니다. TIME 컬럼 값을 지정하려면 문자열을 TIME 값으로 변환하는 TO\_TIME 함수를 통해 입력할 수 있습니다.
{% endhint %}

## tbJDBC Stream

tbJDBC에서는 특정 데이터 타입에 대해 다음과 같은 Stream 기능을 제공합니다.

* 컬럼에 대한 Stream 생성
  * CHAR, VARCHAR, RAW, NCHAR, NVARCHAR
  * LONG, LONG RAW
  * LOB
* Stream 소멸

tbJDBC에서는 다음과 같이 4종류의 Stream 메소드를 제공합니다.

<table><thead><tr><th width="183">메소드</th><th width="196">반환 값</th><th>설명</th></tr></thead><tbody><tr><td>getAsciiStream</td><td>Java.io.InputStream</td><td>ASCII 형태로 변환된 데이터 Stream</td></tr><tr><td>getBinaryStream</td><td>Java.io.InputStream</td><td>byte 형태로 변환된 데이터 Stream</td></tr><tr><td>getUnicodeStream</td><td>Java.io.InputStream</td><td><ul><li>Unicode 형태로 변환된 데이터 Stream</li><li>이 메소드는 JDBC 4.0에서 폐기(deprecated)되었으므로 호환성을 고려하여 가급적 사용하지 않는 것이 좋음</li></ul></td></tr><tr><td>getCharacterStream</td><td>Java.io.Reader</td><td>문자열 형태로 변환된 데이터 Reader</td></tr></tbody></table>

각각의 메소드를 이용하면 해당 데이터 타입에서 허용하는 최대 크기까지 점진적으로 데이터를 읽을 수 있습니다. 메소드에서 반환되는 객체는 InputStream과 Reader이고, read() 메소드를 사용하면 데이터를 읽을 수 있습니다.

### 컬럼에 대한 **Stream** 생성

CHAR, VARCHAR, RAW 컬럼에 Stream 메소드를 사용하는 경우 전체 데이터를 한꺼번에 받아 Stream 객체를 생성합니다. 반면 LONG, LONG RAW, LOB 컬럼은 최초 일부 데이터만 읽어온 후 Stream 객체를 생성합니다. 사용자가 추가로 요청하면 차례대로 데이터를 더 읽어온 후 Stream 객체를 생성합니다.

다음은 getBinaryStream()을 이용하여 데이터를 읽어오는 예입니다.

```
ResultSet rs = stmt.executeQuery("SELECT name, image FROM backgrounds");

while (rs.next()) {
    InputStream imageIS = rs.getBinaryStream(2); 
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(rs.getString(1)); 
        int chunk = 0;
        while ((chunk = imageIS.read()) != -1) 
            fos.write(chunk);
    }
    catch (Exception e) 
        { e.printStackTrace();
    }
    finally {
        if (fos != null) 
        fos.close();
    }
}
```

다음은 getBinaryStream() 대신 getBytes()를 이용하여 데이터를 읽어오는 예입니다. **getBytes()**&#xB294; 해당 컬럼에 저장된 모든 데이터를 한꺼번에 받아오기 때문에 그 크기만큼 메모리를 차지한다는 점에 유의해야 합니다.

```
ResultSet rs = stmt.executeQuery("SELECT name, image FROM backgrounds");
while (rs.next()) {
    bytes[] images = rs.getBytes(2); 
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(rs.getString(1)); 
        fos.write(images);
    }
    catch (Exception e) 
        { e.printStackTrace();
    }
    finally {
        if (fos != null) 
            fos.close();
    }
}
```

## 내장 함수 호출

### **PSM**의 사용

tbJDBC에서는 PSM을 사용할 수 있습니다.

다음과 같이 SQL-92의 ESCAPE 문법과 PSM 문법을 제공합니다.

```
// SQL-92 ESCAPE 문법
CallableStatement cstmt = conn.prepareCall("{call proc(?)}"); 
CallableStatement cstmt = conn.prepareCall("{? = call func(?)}");

// PSM 문법
CallableStatement cstmt = conn.prepareCall("begin proc(?); end;"); 
CallableStatement cstmt = conn.prepareCall("begin ? := func(?); end;");
```

다음은 PSM 문법을 사용하여 내장 함수를 호출하는 예입니다.

```
create or replace function concat(x varchar2, y varchar2)
return varchar2 is 
begin
    return x || y; 
end;
```

```
CallableStatement cstmt = conn.prepareCall("begin ? := concat(?, ?); end;");

cstmt.registerOutParameter(1, Types.VARCHAR); 
cstmt.setString(2, "Tibero"); 
cstmt.setString(3, "7");
cstmt.executeUpdate();

String result = cstmt.getString(1);
```

### Java Stored Procedure의 사용

이전 절에서 설명한 방법과 같이 Java Stored Procedure를 사용할 수 있습니다.

{% hint style="info" %}
**참고**

Java Stored Procedure에 대한 자세한 내용은 "[Tibero External Procedure 안내서](/tibero-manuals/7.2.6.manuals/external-procedure-guide.md)"를 참고합니다.
{% endhint %}

## 예외 처리

tbJDBC에서는 예외 상황을 처리하기 위해 java.sql.SQLException 클래스 객체를 정의할 수 있습니다. 예외 상황은 tbJDBC 내부 또는 데이터베이스 내부에서 발생할 수 있습니다. 예외 상황에는 에러 코드와 에러 발생 위치 등의 정보가 포함되어 있습니다.

예외 상황에 대한 정보를 얻기 위해 다음과 같은 메소드가 제공됩니다.

<table><thead><tr><th width="238">메소드</th><th>설명</th></tr></thead><tbody><tr><td>getMessage()</td><td>에러가 발생한 원인</td></tr><tr><td>getSQLState()</td><td>SQL 상태 정보</td></tr><tr><td>getErrorCode()</td><td>에러 코드</td></tr><tr><td>printStackTrace()</td><td>에러가 발생한 스택의 트레이스 정보(Stack trace)</td></tr><tr><td>getLocalizedMessage()</td><td>에러가 발생한 원인</td></tr></tbody></table>

다음은 위의 메소드를 사용하여 예외 상황을 처리하는 예입니다.

```
try {
    stmt.execute("drop table not_exist_table");
}
catch (SQLException e) {
    System.out.println("ERROR[" + e.getErrorCode() + "]" + e.getMessage()); 
    e.printStackTrace();
}
```

실행하면 다음과 같은 정보가 화면에 출력됩니다.

```
ERROR[-7071] Schema object'NOT_EXIST_TABLE' was not found or is invalid. 
java.sql.SQLException: Schema object 'NOT_EXIST_TABLE' was not found or is invalid.
    at com.tmax.tibero.jdbc.msg.common.TbMsgError.readErrorStackInfo(TbMsgError.java
:108)
    at com.tmax.tibero.jdbc.msg.TbMsgEreply.deserialize(TbMsgEreply.java:61)
    at com.tmax.tibero.jdbc.comm.TbStream.readMsgBody(TbStream.java:327)
    at com.tmax.tibero.jdbc.comm.TbCommType4.executeDirect(TbCommType4.java:460) 
    at com.tmax.tibero.jdbc.TbStatement.executeInternal(TbStatement.java:1051) 
    at com.tmax.tibero.jdbc.TbStatement.execute(TbStatement.java:560)
    at TestSimple.main(TestSimple.java:102)
```

## tbJDBC 로깅

Tibero JDBC 드라이버는 JDBC 호출을 파일로 기록하는 트레이스 로깅 기능을 제공합니다. JVM 시스템 프로퍼티(`-D` 옵션)로 활성화하며, 기본값은 비활성 상태입니다.

로그 파일은 사용자 홈 디렉터리에 `tbjdbc-날짜-시분초-밀리초-랜덤ID.log` 형식으로 생성됩니다. 프로퍼티를 제거하면 로깅이 비활성화되며, 성능에 영향을 주지 않습니다.

### 사용법

로깅을 활성화하려면 `-Dcom.tmax.tibero.jdbc.logging.enabled=true`를 추가하십시오.

```bash
java -Dcom.tmax.tibero.jdbc.logging.enabled=true \
     -cp tibero7-jdbc-8.jar:app.jar com.example.App
```

로그 파일은 사용자 홈 디렉터리에 `tbjdbc-날짜-시분초-밀리초-랜덤ID.log` 형식으로 생성됩니다. 프로퍼티를 제거하면 로깅이 비활성화되며, 성능에 영향을 주지 않습니다.

### 설정 프로퍼티(접두사: `com.tmax.tibero.jdbc.logging`)

| 프로퍼티               | 기본값     | 설명                                   |
| ------------------ | ------- | ------------------------------------ |
| `.enabled`         | `false` | `true`로 설정하면 로깅이 활성화됩니다              |
| `.basedir`         | 사용자 홈   | 로그 파일이 생성될 디렉터리를 지정합니다               |
| `.file.maxsize`    | `1G`    | 로그 파일의 최대 크기입니다(K/M/G)               |
| `.rotation.period` | 비활성     | 파일 교체 주기를 설정합니다(S/M/H/D, 최소 60초)     |
| `.compression`     | `false` | `true`로 설정하면 gzip 압축됩니다 (`.log.gz`)  |
| `.overwrite`       | `true`  | `false`로 설정하면 교체 시 새 파일이 생성됩니다       |
| `.flushPerCall`    | `false` | `true`로 설정하면 매 호출마다 즉시 기록됩니다(장애 분석용) |

**장애 분석 권장 설정:**

```bash
java -Dcom.tmax.tibero.jdbc.logging.enabled=true \
     -Dcom.tmax.tibero.jdbc.logging.basedir=/var/log/tibero \
     -Dcom.tmax.tibero.jdbc.logging.flushPerCall=true \
     -cp tibero7-jdbc-8.jar:app.jar com.example.App
```

WAS 환경에서는 JVM 시작 옵션(Tomcat: `CATALINA_OPTS`, JEUS: 도메인 JVM 옵션)에 동일하게 추가하십시오.

### 로그 출력 예시

```
14:30:52.456 [main] TbConnection.createStatement()
14:30:52.457 [main] TbStatement.executeQuery("SELECT 1 FROM DUAL")
14:30:52.460 [main] TbResultSet.next()
14:30:52.461 [main] TbResultSet.getString("1", "2")
14:30:52.461 [main] TbResultSet.close()
14:30:52.462 [main] TbStatement.close()
```

{% hint style="warning" %}
로깅 활성화 시 성능에 영향을 줄 수 있으므로, 분석이 완료되면 비활성화하시기 바랍니다. 설정은 JVM 시작 시 1회 읽으며, 변경 시 재시작이 필요합니다. 로그에 SQL 문 등 민감 정보가 포함될 수 있으므로 파일 접근 권한 관리에 유의하시기 바랍니다.
{% endhint %}
