JavaRush /Java Blog /Random-KO /JDBC 또는 모든 것이 시작되는 곳
Viacheslav
레벨 3

JDBC 또는 모든 것이 시작되는 곳

Random-KO 그룹에 게시되었습니다
현대 사회에서는 데이터 저장 없이는 방법이 없습니다. 그리고 데이터베이스 작업의 역사는 아주 오래 전 JDBC의 출현과 함께 시작되었습니다. 나는 JDBC 위에 구축된 최신 프레임워크 없이는 할 수 없는 일을 기억할 것을 제안합니다. 또한, 그들과 함께 일하더라도 때로는 “본래로 돌아갈” 기회가 필요할 수도 있습니다. 이 리뷰가 소개로서 도움이 되거나 기억을 되살리는 데 도움이 되기를 바랍니다.
JDBC 또는 모든 것이 시작되는 곳 - 1

소개

프로그래밍 언어의 주요 목적 중 하나는 정보를 저장하고 처리하는 것입니다. 데이터 스토리지의 작동 방식을 더 잘 이해하려면 애플리케이션의 이론과 아키텍처에 대해 약간의 시간을 투자할 가치가 있습니다. 예를 들어, Joseph Ingeno가 쓴 " 소프트웨어 아키텍트 핸드북: 효과적인 아치를 구현하여 성공적인 소프트웨어 아키텍트가 되십시오... " 라는 책을 읽을 수 있습니다 . 말했듯이 특정 데이터 계층 또는 "데이터 계층"이 있습니다. 여기에는 데이터를 저장하는 장소(예: SQL 데이터베이스)와 데이터 저장소 작업을 위한 도구(예: 설명할 JDBC)가 포함됩니다. 또한 Microsoft 웹 사이트에는 데이터 계층에서 추가 계층인 지속성 계층을 분리하는 아키텍처 솔루션을 설명하는 " 인프라 지속성 계층 설계 "라는 기사가 있습니다. 이 경우 데이터 계층은 데이터 자체의 저장소 수준인 반면, 지속성 계층은 데이터 계층 수준의 저장소에 있는 데이터 작업을 위한 추상화 수준입니다. 지속성 레이어에는 "DAO" 템플릿이나 다양한 ORM이 포함될 수 있습니다. 그러나 ORM은 또 다른 논의의 주제입니다. 이미 이해하셨겠지만 데이터 계층이 먼저 나타났습니다. JDK 1.1 이후로 JDBC(Java DataBase Connectivity - Java의 데이터베이스 연결)가 Java 세계에 등장했습니다. 이는 Java SE에 포함된 java.sql 및 javax.sql 패키지 형태로 구현된 다양한 DBMS와 Java 애플리케이션의 상호 작용을 위한 표준입니다.
JDBC 또는 모든 것이 시작되는 곳 - 2
이 표준은 " JSR 221 JDBC 4.1 API " 사양에 설명되어 있습니다 . 이 사양은 JDBC API가 Java로 작성된 프로그램에서 관계형 데이터베이스에 대한 프로그래밍 방식의 액세스를 제공한다는 것을 알려줍니다. 또한 JDBC API가 Java 플랫폼의 일부이므로 Java SE 및 Java EE에 포함되어 있음을 알려줍니다. JDBC API는 java.sql과 javax.sql의 두 가지 패키지로 제공됩니다. 그럼 그들을 알아봅시다.
JDBC 또는 모든 것이 시작되는 곳 - 3

일의 시작

JDBC API가 일반적으로 무엇인지 이해하려면 Java 애플리케이션이 필요합니다. 프로젝트 조립 시스템 중 하나를 사용하는 것이 가장 편리합니다. 예를 들어 Gradle을 사용해 보겠습니다 . 간단한 리뷰인 " A Brief Introduction to Gradle "에서 Gradle에 대해 자세히 알아볼 수 있습니다. 먼저 새로운 Gradle 프로젝트를 초기화해 보겠습니다. Gradle 기능은 플러그인을 통해 구현되므로 초기화를 위해 " Gradle Build Init Plugin "을 사용해야 합니다 .
gradle init --type java-application
그런 다음 프로젝트와 작업 방법을 설명하는 빌드 스크립트( build.gradle 파일) 를 열어보겠습니다 . 우리는 종속성이 설명되는 " 종속성 " 블록, 즉 우리가 작업할 수 없고 의존하는 라이브러리/프레임워크/API에 관심이 있습니다. 기본적으로 다음과 같은 내용이 표시됩니다.
dependencies {
    // This dependency is found on compile classpath of this component and consumers.
    implementation 'com.google.guava:guava:26.0-jre'
    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}
우리는 왜 이것을 여기서 보고 있는 걸까요? 이는 프로젝트를 생성할 때 Gradle이 자동으로 생성한 프로젝트의 종속성입니다. 또한 Guava는 Java SE에 포함되지 않은 별도의 라이브러리이기 때문입니다. JUnit은 Java SE에도 포함되어 있지 않습니다. 그러나 JDBC는 기본적으로 제공됩니다. 즉, Java SE의 일부입니다. JDBC가 있다는 것이 밝혀졌습니다. 엄청난. 또 무엇이 필요합니까? 다음과 같은 훌륭한 다이어그램이 있습니다.
JDBC 또는 모든 것이 시작되는 곳 - 4
보시다시피 이는 논리적이며 데이터베이스는 Java SE에 기본이 아닌 외부 구성 요소입니다. 이것은 간단하게 설명됩니다. 엄청난 수의 데이터베이스가 있으며 어떤 데이터베이스로도 작업할 수 있습니다. 예를 들어 PostgreSQL, Oracle, MySQL, H2가 있습니다. 이러한 각 데이터베이스는 데이터베이스 공급업체라고 하는 별도의 회사에서 제공됩니다. 각 데이터베이스는 자체 프로그래밍 언어(반드시 Java일 필요는 없음)로 작성됩니다. Java 애플리케이션에서 데이터베이스 작업을 수행할 수 있도록 데이터베이스 공급자는 자체 이미지 어댑터인 특수 드라이버를 작성합니다. 이러한 JDBC 호환 데이터베이스(즉, JDBC 드라이버가 있는 데이터베이스)를 "JDBC 호환 데이터베이스"라고도 합니다. 여기서 우리는 컴퓨터 장치에 비유할 수 있습니다. 예를 들어 메모장에는 "인쇄" 버튼이 있습니다. 이 버튼을 누를 때마다 프로그램은 메모장 응용 프로그램이 인쇄를 원한다고 운영 체제에 알려줍니다. 그리고 프린터도 있어요. Canon 또는 HP 프린터와 균일하게 통신하도록 운영 체제를 교육하려면 다른 드라이버가 필요합니다. 하지만 사용자로서 여러분에게는 아무것도 바뀌지 않습니다. 여전히 같은 버튼을 누르게 됩니다. JDBC도 마찬가지다. 동일한 코드를 실행하고 있지만 내부적으로는 다른 데이터베이스가 실행될 수 있습니다. 나는 이것이 매우 명확한 접근 방식이라고 생각합니다. 이러한 각 JDBC 드라이버는 일종의 아티팩트, 라이브러리, jar 파일입니다. 이것이 우리 프로젝트의 종속성입니다. 예를 들어 " H2 Database " 데이터베이스를 선택한 다음 다음과 같은 종속성을 추가해야 합니다.
dependencies {
    implementation 'com.h2database:h2:1.4.197'
종속성을 찾는 방법과 이를 설명하는 방법은 데이터베이스 공급자의 공식 웹사이트나 " Maven Central "에 나와 있습니다. 아시다시피 JDBC 드라이버는 데이터베이스가 아닙니다. 그러나 그는 단지 안내자일 뿐입니다. 그러나 " 메모리 데이터베이스 내 " 와 같은 것이 있습니다 . 이는 애플리케이션 수명 동안 메모리에 존재하는 데이터베이스입니다. 일반적으로 이는 테스트나 교육 목적으로 사용되는 경우가 많습니다. 이를 통해 머신에 별도의 데이터베이스 서버를 설치하지 않아도 됩니다. JDBC에 대해 알아가는 데 매우 적합합니다. 이제 샌드박스가 준비되었으며 시작합니다.
JDBC 또는 모든 것이 시작되는 곳 - 5

연결

따라서 우리에게는 JDBC 드라이버와 JDBC API가 있습니다. 우리가 기억하는 것처럼 JDBC는 Java DataBase Connectivity를 나타냅니다. 따라서 모든 것은 연결을 설정하는 기능인 연결성에서 시작됩니다. 그리고 연결은 연결입니다. 다시 JDBC 스펙의 본문으로 돌아가서 목차를 살펴보겠습니다. " 4장 개요 "(개요) 장에서 " 4.1 연결 설정 "(연결 설정) 섹션을 살펴보면 데이터베이스에 연결하는 두 가지 방법이 있다고 나와 있습니다.
  • DriverManager를 통해
  • 데이터 소스를 통해
DriverManager를 다루겠습니다. 말했듯이 DriverManager를 사용하면 지정된 URL에서 데이터베이스에 연결할 수 있으며 CLASSPATH에서 찾은 JDBC 드라이버도 로드할 수 있습니다(JDBC 4.0 이전에는 드라이버 클래스를 직접 로드해야 했습니다). 데이터베이스 연결에 대한 별도의 "9장 연결" 장이 있습니다. 우리는 DriverManager를 통해 연결하는 방법에 관심이 있으므로 "9.3 DriverManager 클래스" 섹션에 관심이 있습니다. 데이터베이스에 액세스하는 방법을 나타냅니다.
Connection con = DriverManager.getConnection(url, user, passwd);
매개변수는 우리가 선택한 데이터베이스의 웹사이트에서 가져올 수 있습니다. 우리의 경우 이것은 H2 - " H2 Cheat Sheet "입니다. Gradle이 준비한 AppTest 클래스로 넘어가겠습니다. 여기에는 JUnit 테스트가 포함되어 있습니다. JUnit 테스트는 주석으로 표시된 메소드입니다 @Test. 단위 테스트는 이 리뷰의 주제가 아니므로, 이는 특정 방식으로 설명된 메서드이며 그 목적은 무언가를 테스트하는 것이라는 점만 이해하도록 제한하겠습니다. JDBC 사양과 H2 웹 사이트에 따라 데이터베이스에 대한 연결이 수신되었는지 확인합니다. 연결을 얻는 방법을 작성해 보겠습니다.
private Connection getNewConnection() throws SQLException {
	String url = "jdbc:h2:mem:test";
	String user = "sa";
	String passwd = "sa";
	return DriverManager.getConnection(url, user, passwd);
}
이제 연결이 실제로 설정되었는지 확인하는 이 메서드에 대한 테스트를 작성해 보겠습니다.
@Test
public void shouldGetJdbcConnection() throws SQLException {
	try(Connection connection = getNewConnection()) {
		assertTrue(connection.isValid(1));
		assertFalse(connection.isClosed());
	}
}
이 테스트를 실행하면 결과 연결이 유효하고(올바르게 생성됨) 닫히지 않았는지 확인합니다. try-with-resources를 사용하면 더 이상 필요하지 않은 리소스를 해제할 수 있습니다. 이렇게 하면 연결 끊김과 메모리 누수로부터 우리를 보호할 수 있습니다. 데이터베이스에 대한 모든 작업에는 연결이 필요하므로 @Test로 표시된 나머지 테스트 메서드에 테스트 시작 시 연결을 제공하고 테스트 후에 릴리스할 예정입니다. 이를 위해서는 @Before 및 @After라는 두 개의 주석이 필요합니다. 테스트용 JDBC 연결을 저장할 AppTest 클래스에 새 필드를 추가해 보겠습니다.
private static Connection connection;
그리고 새로운 메소드를 추가해 보겠습니다.
@Before
public void init() throws SQLException {
	connection = getNewConnection();
}
@After
public void close() throws SQLException {
	connection.close();
}
이제 모든 테스트 메소드에는 JDBC 연결이 보장되며 매번 자체적으로 생성할 필요가 없습니다.
JDBC 또는 모든 것이 시작되는 곳 - 6

진술

다음으로 우리는 진술이나 표현에 관심이 있습니다. 이에 대한 내용은 문서의 " 13장 명령문 " 장에 설명되어 있습니다. 첫째, 진술에는 여러 유형 또는 유형이 있다고 나와 있습니다.
  • 명령문: 매개변수가 없는 SQL 표현식
  • ReadyStatement : 입력 매개변수를 포함하는 준비된 SQL 문
  • CallableStatement: SQL 저장 프로시저에서 반환 값을 얻는 기능이 있는 SQL 표현식입니다.
따라서 연결이 있으면 이 연결 프레임워크 내에서 일부 요청을 실행할 수 있습니다. 따라서 처음에 Connection에서 SQL 표현식의 인스턴스를 얻는 것이 논리적입니다. 테이블을 만드는 것부터 시작해야 합니다. 테이블 생성 요청을 String 변수로 설명하겠습니다. 어떻게 하나요? " sqltutorial.org ", " sqlbolt.com ", " postgresqltutorial.com ", " codecademy.com " 과 같은 튜토리얼을 사용해 보겠습니다 . 예를 들어 khanacademy.org 의 SQL 과정의 예를 사용해 보겠습니다 . 데이터베이스에서 표현식을 실행하기 위한 메소드를 추가해 보겠습니다.
private int executeUpdate(String query) throws SQLException {
	Statement statement = connection.createStatement();
	// Для Insert, Update, Delete
	int result = statement.executeUpdate(query);
	return result;
}
이전 방법을 사용하여 테스트 테이블을 생성하는 방법을 추가해 보겠습니다.
private void createCustomerTable() throws SQLException {
	String customerTableQuery = "CREATE TABLE customers " +
                "(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)";
	String customerEntryQuery = "INSERT INTO customers " +
                "VALUES (73, 'Brian', 33)";
	executeUpdate(customerTableQuery);
	executeUpdate(customerEntryQuery);
}
이제 이것을 테스트해보자:
@Test
public void shouldCreateCustomerTable() throws SQLException {
	createCustomerTable();
	connection.createStatement().execute("SELECT * FROM customers");
}
이제 매개변수를 사용하여 요청을 실행해 보겠습니다.
@Test
public void shouldSelectData() throws SQLException {
 	createCustomerTable();
 	String query = "SELECT * FROM customers WHERE name = ?";
	PreparedStatement statement = connection.prepareStatement(query);
	statement.setString(1, "Brian");
	boolean hasResult = statement.execute();
	assertTrue(hasResult);
}
JDBC는 preparedStatement에 대해 명명된 매개변수를 지원하지 않으므로 매개변수 자체는 질문으로 지정되며, 값을 지정하면 질문 인덱스(0이 아닌 1부터 시작)를 나타냅니다. 마지막 테스트에서 결과가 있는지 여부를 나타내는 true를 받았습니다. 그러면 JDBC API에서는 쿼리 결과가 어떻게 표현되나요? 그리고 이는 ResultSet으로 표시됩니다.
JDBC 또는 모든 것이 시작되는 곳 - 7

결과세트

ResultSet의 개념은 "15장 결과 세트" 장의 JDBC API 사양에 설명되어 있습니다. 우선, ResultSet은 실행된 쿼리의 결과를 검색하고 조작하는 방법을 제공한다고 말합니다. 즉, 실행 메소드가 우리에게 true를 반환하면 ResultSet을 얻을 수 있습니다. createCustomerTable() 메서드에 대한 호출을 @Before로 표시된 init 메서드로 이동해 보겠습니다. 이제 shouldSelectData 테스트를 마무리하겠습니다.
@Test
public void shouldSelectData() throws SQLException {
	String query = "SELECT * FROM customers WHERE name = ?";
	PreparedStatement statement = connection.prepareStatement(query);
	statement.setString(1, "Brian");
	boolean hasResult = statement.execute();
	assertTrue(hasResult);
	// Обработаем результат
	ResultSet resultSet = statement.getResultSet();
	resultSet.next();
	int age = resultSet.getInt("age");
	assertEquals(33, age);
}
다음은 소위 "커서"를 이동하는 방법이라는 점에 주목할 가치가 있습니다. ResultSet의 커서는 일부 행을 가리킵니다. 따라서 한 줄을 읽으려면 바로 이 커서를 그 위에 놓아야 합니다. 커서가 이동되면 커서 이동 메서드는 커서가 유효한(올바른, 올바른), 즉 데이터를 가리키는 경우 true를 반환합니다. false를 반환하면 데이터가 없는 것입니다. 즉, 커서가 데이터를 가리키고 있지 않은 것입니다. 유효하지 않은 커서로 데이터를 가져오려고 하면 다음 오류가 발생합니다. 데이터를 사용할 수 없습니다. ResultSet을 통해 행을 업데이트하거나 삽입할 수도 있다는 점도 흥미롭습니다.
@Test
public void shouldInsertInResultSet() throws SQLException {
	Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
	ResultSet resultSet = statement.executeQuery("SELECT * FROM customers");
	resultSet.moveToInsertRow();
	resultSet.updateLong("id", 3L);
	resultSet.updateString("name", "John");
	resultSet.updateInt("age", 18);
	resultSet.insertRow();
	resultSet.moveToCurrentRow();
}

행 집합

ResultSet 외에도 JDBC에는 RowSet이라는 개념이 도입되었습니다. 자세한 내용은 " JDBC 기본: RowSet 개체 사용 " 에서 확인할 수 있습니다 . 다양한 활용 변형이 있습니다. 예를 들어, 가장 간단한 경우는 다음과 같습니다.
@Test
public void shouldUseRowSet() throws SQLException {
 	JdbcRowSet jdbcRs = new JdbcRowSetImpl(connection);
 	jdbcRs.setCommand("SELECT * FROM customers");
	jdbcRs.execute();
	jdbcRs.next();
	String name = jdbcRs.getString("name");
	assertEquals("Brian", name);
}
보시다시피 RowSet은 명령문(우리는 이를 통해 명령을 지정함)과 명령을 실행하는 공생과 유사합니다. 이를 통해 커서를 제어하고(다음 메서드를 호출하여) 여기에서 데이터를 가져옵니다. 이 접근 방식은 흥미로울 뿐만 아니라 구현도 가능합니다. 예를 들어 CachedRowSet입니다. "연결이 끊어졌습니다"(즉, 데이터베이스에 대한 영구 연결을 사용하지 않음). 데이터베이스와의 명시적인 동기화가 필요합니다.
CachedRowSet jdbcRsCached = new CachedRowSetImpl();
jdbcRsCached.acceptChanges(connection);
Oracle 웹 사이트의 " CachedRowSetObjects 사용 " 튜토리얼에서 자세한 내용을 읽을 수 있습니다 .
JDBC 또는 모든 것이 시작되는 곳 - 8

메타데이터

쿼리 외에도 데이터베이스에 대한 연결(즉, Connection 클래스의 인스턴스)은 메타데이터(데이터베이스 구성 및 구성 방법에 대한 데이터)에 대한 액세스를 제공합니다. 하지만 먼저 몇 가지 핵심 사항을 언급하겠습니다. 데이터베이스에 연결하기 위한 URL: "jdbc:h2:mem:test". test는 데이터베이스의 이름입니다. JDBC API의 경우 이는 디렉터리입니다. 이름은 대문자, 즉 TEST입니다. H2 의 기본 스키마는 PUBLIC입니다. 이제 모든 사용자 테이블을 표시하는 테스트를 작성해 보겠습니다. 왜 맞춤인가? 데이터베이스에는 사용자 테이블(우리가 테이블 생성 표현식을 사용하여 직접 생성한 테이블)뿐만 아니라 시스템 테이블도 포함되어 있기 때문입니다. 데이터베이스 구조에 대한 시스템 정보를 저장하는 데 필요합니다. 각 데이터베이스는 이러한 시스템 테이블을 다르게 저장할 수 있습니다. 예를 들어 H2에서는 " INFORMATION_SCHEMA " 스키마 에 저장됩니다 . 흥미롭게도 INFORMATION SCHEMA는 일반적인 접근 방식이지만 Oracle은 다른 경로를 택했습니다. 자세한 내용은 " INFORMATION_SCHEMA 및 Oracle " 에서 확인할 수 있습니다 . 사용자 테이블에 대한 메타데이터를 수신하는 테스트를 작성해 보겠습니다.
@Test
public void shoudGetMetadata() throws SQLException {
	// У нас URL = "jdbc:h2:mem:test", где test - название БД
	// Название БД = catalog
	DatabaseMetaData metaData = connection.getMetaData();
	ResultSet result = metaData.getTables("TEST", "PUBLIC", "%", null);
	List<String> tables = new ArrayList<>();
	while(result.next()) {
		tables.add(result.getString(2) + "." + result.getString(3));
	}
	assertTrue(tables.contains("PUBLIC.CUSTOMERS"));
}
JDBC 또는 모든 것이 시작되는 곳 - 9

연결 풀

JDBC 사양의 연결 풀에는 "11장 연결 풀링"이라는 섹션이 있습니다. 또한 연결 풀의 필요성에 대한 주요 근거도 제공합니다. 각 연결은 데이터베이스에 대한 물리적 연결입니다. 생성 및 종료는 상당히 "비싼" 작업입니다. JDBC는 연결 풀링 API만 제공합니다. 따라서 구현 선택은 우리의 몫입니다. 예를 들어 이러한 구현에는 HikariCP 가 포함됩니다 . 따라서 프로젝트 종속성에 풀을 추가해야 합니다.
dependencies {
    implementation 'com.h2database:h2:1.4.197'
    implementation 'com.zaxxer:HikariCP:3.3.1'
    testImplementation 'junit:junit:4.12'
}
이제 어떻게든 이 풀을 사용해야 합니다. 이렇게 하려면 데이터 소스라고도 하는 데이터 소스를 초기화해야 합니다.
private DataSource getDatasource() {
	HikariConfig config = new HikariConfig();
	config.setUsername("sa");
	config.setPassword("sa");
	config.setJdbcUrl("jdbc:h2:mem:test");
	DataSource ds = new HikariDataSource(config);
	return ds;
}
그리고 풀에서 연결을 수신하는 테스트를 작성해 보겠습니다.
@Test
public void shouldGetConnectionFromDataSource() throws SQLException {
	DataSource datasource = getDatasource();
	try (Connection con = datasource.getConnection()) {
		assertTrue(con.isValid(1));
	}
}
JDBC 또는 모든 것이 시작되는 곳 - 10

업무

JDBC의 가장 흥미로운 점 중 하나는 트랜잭션입니다. JDBC 사양에서는 "CHAPTER 10 트랜잭션" 장에 할당됩니다. 우선, 거래가 무엇인지 이해하는 것이 좋습니다. 트랜잭션은 전체적으로 처리되거나 취소되는 데이터에 대해 논리적으로 결합된 순차적 작업 그룹입니다. JDBC를 사용할 때 트랜잭션은 언제 시작됩니까? 사양에 명시된 대로 이는 JDBC 드라이버에 의해 직접 처리됩니다. 그러나 일반적으로 현재 SQL 문에 트랜잭션이 필요하고 해당 트랜잭션이 아직 생성되지 않은 경우 새 트랜잭션이 시작됩니다. 거래는 언제 끝나나요? 이는 자동 커밋 속성에 의해 제어됩니다. 자동 커밋이 활성화된 경우 SQL 문이 "완료"된 후에 트랜잭션이 완료됩니다. "완료"의 의미는 SQL 표현식의 유형에 따라 다릅니다.
  • DML(삽입, 업데이트, 삭제)이라고도 하는 데이터 조작 언어
    작업이 완료되자마자 트랜잭션이 완료됩니다.
  • Select 문
    ResultSet이 닫히면 트랜잭션이 완료됩니다( ResultSet#close ).
  • 여러 결과를 반환하는 CallableStatement 및 표현식
    연결된 모든 ResultSet가 닫히고 모든 출력(업데이트 수 포함)이 수신된 경우
이것이 바로 JDBC API가 작동하는 방식입니다. 평소처럼 이에 대한 테스트를 작성해 보겠습니다.
@Test
public void shouldCommitTransaction() throws SQLException {
	connection.setAutoCommit(false);
	String query = "INSERT INTO customers VALUES (1, 'Max', 20)";
	connection.createStatement().executeUpdate(query);
	connection.commit();
	Statement statement = connection.createStatement();
 	statement.execute("SELECT * FROM customers");
	ResultSet resultSet = statement.getResultSet();
	int count = 0;
	while(resultSet.next()) {
		count++;
	}
	assertEquals(2, count);
}
간단 해. 그러나 이는 거래가 하나만 있는 한 사실입니다. 여러 개가 있을 때는 어떻게 해야 하나요? 그들은 서로 격리되어야 합니다. 따라서 트랜잭션 격리 수준과 JDBC가 이를 처리하는 방법에 대해 이야기해 보겠습니다.
JDBC 또는 모든 것이 시작되는 곳 - 11

절연 수준

JDBC 사양의 "10.2 트랜잭션 격리 수준" 하위 섹션을 열어 보겠습니다. 여기서 더 나아가기 전에 ACID와 같은 것에 대해 기억하고 싶습니다. ACID는 트랜잭션 시스템에 대한 요구 사항을 설명합니다.
  • 원자성:
    트랜잭션이 시스템에 부분적으로 커밋되지 않습니다. 모든 하위 작업이 수행되거나 아무것도 수행되지 않습니다.
  • 일관성:
    각각의 성공적인 트랜잭션은 정의에 따라 유효한 결과만 기록합니다.
  • 격리:
    트랜잭션이 실행되는 동안 동시 트랜잭션은 결과에 영향을 주어서는 안 됩니다.
  • 내구성:
    트랜잭션이 성공적으로 완료되면 변경 사항이 실패로 인해 취소되지 않습니다.
트랜잭션 격리 수준에 대해 이야기할 때 "격리" 요구 사항에 대해 이야기합니다. 격리는 비용이 많이 드는 요구 사항이므로 실제 데이터베이스에는 트랜잭션을 완전히 격리하지 않는 모드(반복 읽기 격리 수준 이하)가 있습니다. Wikipedia에는 ​​트랜잭션 작업 시 발생할 수 있는 문제에 대한 훌륭한 설명이 있습니다. 여기에서 더 읽어볼 가치가 있습니다: " 트랜잭션을 사용한 병렬 액세스 문제 ." 테스트를 작성하기 전에 Gradle 빌드 스크립트를 약간 변경해 보겠습니다. 속성이 있는 블록, 즉 프로젝트 설정이 있는 블록을 추가합니다.
ext {
    h2Version = '1.3.176' // 1.4.177
    hikariVersion = '3.3.1'
    junitVersion = '4.12'
}
다음으로 이를 버전에서 사용합니다.
dependencies {
    implementation "com.h2database:h2:${h2Version}"
    implementation "com.zaxxer:HikariCP:${hikariVersion}"
    testImplementation "junit:junit:${junitVersion}"
}
h2 버전이 낮아졌다는 것을 눈치채셨을 것입니다. 나중에 그 이유를 살펴보겠습니다. 그렇다면 격리 수준을 어떻게 적용합니까? 작은 실제 예를 바로 살펴보겠습니다.
@Test
public void shouldGetReadUncommited() throws SQLException {
	Connection first = getNewConnection();
	assertTrue(first.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED));
	first.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
	first.setAutoCommit(false);
	// Транзакиця на подключение. Поэтому первая транзакция с ReadUncommited вносит изменения
	String insertQuery = "INSERT INTO customers VALUES (5, 'Max', 15)";
	first.createStatement().executeUpdate(insertQuery);
	// Вторая транзакция пытается их увидеть
	int rowCount = 0;
	JdbcRowSet jdbcRs = new JdbcRowSetImpl(getNewConnection());
	jdbcRs.setCommand("SELECT * FROM customers");
	jdbcRs.execute();
	while (jdbcRs.next()) {
		rowCount++;
	}
	assertEquals(2, rowCount);
}
흥미롭게도 TRANSACTION_READ_UNCOMMITTED(예: sqlite 또는 HSQL)를 지원하지 않는 공급업체에서는 이 테스트가 실패할 수 있습니다. 그리고 거래 수준이 단순히 작동하지 않을 수도 있습니다. H2 데이터베이스 드라이버 버전을 표시한 것을 기억하시나요? h2Version = '1.4.177' 이상으로 올리면 코드를 변경하지 않았더라도 READ UNCOMMITTED 작동이 중지됩니다. 이는 공급업체 및 드라이버 버전 선택이 단순한 문자가 아니라 실제로 요청이 실행되는 방식을 결정한다는 것을 다시 한 번 입증합니다. 버전 1.4.177에서 이 동작을 수정하는 방법과 상위 버전에서 작동하지 않는 방법에 대한 자세한 내용은 " MVStore 모드에서 READ UNCOMMITTED 격리 수준 지원 "을 참조하세요.
JDBC 또는 모든 것이 시작되는 곳 - 12

결론

보시다시피 JDBC는 데이터베이스 작업을 위해 Java에서 사용되는 강력한 도구입니다. 이 짧은 리뷰가 출발점을 제공하거나 기억을 되살리는 데 도움이 되기를 바랍니다. 음, 간식으로 몇 가지 추가 자료가 있습니다. #비아체슬라프
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION