JavaRush /Java Blog /Random-IT /JDBC o dove tutto ha inizio
Viacheslav
Livello 3

JDBC o dove tutto ha inizio

Pubblicato nel gruppo Random-IT
Nel mondo moderno, non è possibile fare a meno dell'archiviazione dei dati. E la storia del lavoro con i database è iniziata molto tempo fa, con l'avvento di JDBC. Propongo di ricordare qualcosa di cui nessun framework moderno costruito su JDBC può fare a meno. Inoltre, anche quando lavori con loro, a volte potresti aver bisogno dell’opportunità di “tornare alle tue radici”. Spero che questa recensione ti sia d'aiuto come introduzione o ti aiuti a rinfrescarti la memoria.
JDBC o dove tutto ha inizio - 1

introduzione

Uno degli scopi principali di un linguaggio di programmazione è la memorizzazione e l'elaborazione delle informazioni. Per comprendere meglio come funziona l’archiviazione dei dati, vale la pena dedicare un po’ di tempo alla teoria e all’architettura delle applicazioni. Ad esempio, puoi leggere la letteratura, vale a dire il libro " Manuale dell'architetto del software: diventa un architetto del software di successo implementando un'architettura efficace... " di Joseph Ingeno. Come detto, esiste un certo livello dati o “livello dati”. Include un luogo in cui archiviare i dati (ad esempio un database SQL) e strumenti per lavorare con un archivio dati (ad esempio JDBC, di cui parleremo). C'è anche un articolo sul sito Web di Microsoft: " Progettare un livello di persistenza dell'infrastruttura ", che descrive la soluzione architetturale di separare un livello aggiuntivo dal livello dati: il livello di persistenza. In questo caso, il Data Tier è il livello di archiviazione dei dati stessi, mentre il Persistence Layer è un livello di astrazione per lavorare con i dati dallo storage dal livello Data Tier. Il livello di persistenza può includere il modello "DAO" o vari ORM. Ma l’ORM è un argomento per un’altra discussione. Come avrai già capito, il livello dati è apparso per primo. Dai tempi di JDK 1.1, JDBC (Java DataBase Connectivity - connessione ai database in Java) è apparso nel mondo Java. Questo è uno standard per l'interazione delle applicazioni Java con vari DBMS, implementato sotto forma di pacchetti java.sql e javax.sql inclusi in Java SE:
JDBC o dove tutto ha inizio - 2
Questo standard è descritto dalla specifica " JSR 221 JDBC 4.1 API ". Questa specifica ci dice che l'API JDBC fornisce l'accesso programmatico ai database relazionali da programmi scritti in Java. Indica inoltre che l'API JDBC fa parte della piattaforma Java ed è quindi inclusa in Java SE e Java EE. L'API JDBC è fornita in due pacchetti: java.sql e javax.sql. Conosciamoli allora.
JDBC o dove tutto ha inizio - 3

Inizio dei lavori

Per capire cos'è l'API JDBC in generale, abbiamo bisogno di un'applicazione Java. È più conveniente utilizzare uno dei sistemi di assemblaggio del progetto. Ad esempio, utilizziamo Gradle . Puoi leggere di più su Gradle in una breve recensione: " A Brief Introduction to Gradle ". Innanzitutto, inizializziamo un nuovo progetto Gradle. Poiché la funzionalità Gradle è implementata tramite plugin, dobbiamo utilizzare “ Gradle Build Init Plugin ” per l'inizializzazione:
gradle init --type java-application
Successivamente, apriamo lo script di build: il file build.gradle , che descrive il nostro progetto e come lavorarci. A noi interessa il blocco " dipendenze ", dove vengono descritte le dipendenze, cioè quelle librerie/framework/api, senza le quali non possiamo lavorare e dalle quali dipendiamo. Per impostazione predefinita vedremo qualcosa del tipo:
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'
}
Perché lo vediamo qui? Queste sono le dipendenze del nostro progetto che Gradle ha generato automaticamente per noi durante la creazione del progetto. E anche perché guava è una libreria separata che non è inclusa in Java SE. Inoltre, JUnit non è incluso in Java SE. Ma abbiamo JDBC pronto all'uso, ovvero fa parte di Java SE. Si scopre che abbiamo JDBC. Grande. Cos'altro ci serve? C'è un diagramma così meraviglioso:
JDBC o dove tutto ha inizio - 4
Come possiamo vedere, e questo è logico, il database è un componente esterno che non è nativo di Java SE. Questo è spiegato semplicemente: esiste un numero enorme di database e puoi lavorare con chiunque. Ad esempio, c'è PostgreSQL, Oracle, MySQL, H2. Ciascuno di questi database è fornito da una società separata denominata fornitori di database. Ogni database è scritto nel proprio linguaggio di programmazione (non necessariamente Java). Per poter lavorare con il database da un'applicazione Java, il provider del database scrive un driver speciale, che è il proprio adattatore immagine. Quelli compatibili con JDBC (ovvero quelli che dispongono di un driver JDBC) sono anche chiamati "Database compatibile con JDBC". Qui possiamo tracciare un'analogia con i dispositivi informatici. Ad esempio, in un blocco note c'è un pulsante "Stampa". Ogni volta che lo premi, il programma comunica al sistema operativo che l'applicazione Blocco note desidera stampare. E tu hai una stampante. Per insegnare al tuo sistema operativo a comunicare in modo uniforme con una stampante Canon o HP, avrai bisogno di driver diversi. Ma per te, come utente, non cambierà nulla. Continuerai a premere lo stesso pulsante. Lo stesso con JDBC. Stai eseguendo lo stesso codice, è solo che potrebbero essere in esecuzione database diversi sotto il cofano. Penso che questo sia un approccio molto chiaro. Ciascuno di questi driver JDBC è una sorta di artefatto, libreria, file jar. Questa è la dipendenza per il nostro progetto. Ad esempio, possiamo selezionare il database " H2 Database " e quindi dobbiamo aggiungere una dipendenza come questa:
dependencies {
    implementation 'com.h2database:h2:1.4.197'
Come trovare una dipendenza e come descriverla è indicato sui siti Web ufficiali del fornitore del database o su " Maven Central ". Il driver JDBC non è un database, come hai capito. Ma lui è solo una guida. Ma esiste qualcosa come " Database in memoria ". Si tratta di database che esistono in memoria per tutta la durata dell'applicazione. In genere, questo viene spesso utilizzato per scopi di test o formazione. Ciò consente di evitare l'installazione di un server database separato sulla macchina. Il che è molto adatto per noi per conoscere JDBC. Quindi la nostra sandbox è pronta e possiamo iniziare.
JDBC o dove tutto ha inizio - 5

Connessione

Quindi, abbiamo un driver JDBC, abbiamo un'API JDBC. Come ricordiamo, JDBC sta per Java DataBase Connectivity. Pertanto, tutto inizia con la connettività, la capacità di stabilire una connessione. E la connessione è Connessione. Torniamo di nuovo al testo della specifica JDBC e guardiamo il sommario. Nel capitolo " CAPITOLO 4 Panoramica " (panoramica) passiamo alla sezione " 4.1 Stabilire una connessione " (stabilire una connessione) si dice che ci sono due modi per connettersi al database:
  • Tramite DriverManager
  • Tramite DataSource
Affrontiamo DriverManager. Come detto, DriverManager ti consente di connetterti al database all'URL specificato e carica anche i driver JDBC che ha trovato nel CLASSPATH (e prima, prima di JDBC 4.0, dovevi caricare tu stesso la classe del driver). C'è un capitolo separato “CAPITOLO 9 Connessioni” sulla connessione al database. Siamo interessati a come ottenere una connessione tramite DriverManager, quindi siamo interessati alla sezione "9.3 La classe DriverManager". Indica come possiamo accedere al database:
Connection con = DriverManager.getConnection(url, user, passwd);
I parametri possono essere presi dal sito web del database che abbiamo scelto. Nel nostro caso, questo è H2 - " H2 Cheat Sheet ". Passiamo alla classe AppTest preparata da Gradle. Contiene test JUnit. Un test JUnit è un metodo contrassegnato da un'annotazione @Test. I test unitari non sono l'argomento di questa recensione, quindi ci limiteremo semplicemente a capire che si tratta di metodi descritti in un certo modo, il cui scopo è testare qualcosa. Secondo le specifiche JDBC e il sito Web H2, controlleremo di aver ricevuto una connessione al database. Scriviamo un metodo per ottenere una connessione:
private Connection getNewConnection() throws SQLException {
	String url = "jdbc:h2:mem:test";
	String user = "sa";
	String passwd = "sa";
	return DriverManager.getConnection(url, user, passwd);
}
Ora scriviamo un test per questo metodo che verificherà che la connessione venga effettivamente stabilita:
@Test
public void shouldGetJdbcConnection() throws SQLException {
	try(Connection connection = getNewConnection()) {
		assertTrue(connection.isValid(1));
		assertFalse(connection.isClosed());
	}
}
Questo test, una volta eseguito, verificherà che la connessione risultante sia valida (creata correttamente) e che non sia chiusa. Utilizzando try-with-resources rilasceremo le risorse una volta che non ne avremo più bisogno. Questo ci proteggerà da connessioni allentate e perdite di memoria. Poiché qualsiasi azione con il database richiede una connessione, forniamo ai restanti metodi di test contrassegnati con @Test una Connection all'inizio del test, che rilasceremo dopo il test. Per fare ciò, abbiamo bisogno di due annotazioni: @Before e @After Aggiungiamo un nuovo campo alla classe AppTest che memorizzerà la connessione JDBC per i test:
private static Connection connection;
E aggiungiamo nuovi metodi:
@Before
public void init() throws SQLException {
	connection = getNewConnection();
}
@After
public void close() throws SQLException {
	connection.close();
}
Ora, qualsiasi metodo di test avrà sicuramente una connessione JDBC e non dovrà crearla ogni volta.
JDBC o dove tutto ha inizio - 6

Dichiarazioni

Successivamente siamo interessati alle dichiarazioni o espressioni. Sono descritti nella documentazione al capitolo " CAPITOLO 13 Dichiarazioni ". In primo luogo, si dice che esistono diversi tipi o tipi di dichiarazioni:
  • Istruzione: espressione SQL che non contiene parametri
  • PreparedStatement: istruzione SQL preparata contenente parametri di input
  • CallableStatement: espressione SQL con la possibilità di ottenere un valore restituito dalle procedure memorizzate SQL.
Quindi, avendo una connessione, possiamo eseguire qualche richiesta nell'ambito di questa connessione. Pertanto è logico ottenere inizialmente un'istanza dell'espressione SQL da Connection. Devi iniziare creando una tabella. Descriviamo la richiesta di creazione della tabella come una variabile String. Come farlo? Usiamo qualche tutorial come " sqltutorial.org ", " sqlbolt.com ", " postgresqltutorial.com ", " codecademy.com ". Usiamo, ad esempio, un esempio dal corso SQL su khanacademy.org . Aggiungiamo un metodo per eseguire un'espressione nel database:
private int executeUpdate(String query) throws SQLException {
	Statement statement = connection.createStatement();
	// Для Insert, Update, Delete
	int result = statement.executeUpdate(query);
	return result;
}
Aggiungiamo un metodo per creare una tabella di test utilizzando il metodo precedente:
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);
}
Ora proviamo questo:
@Test
public void shouldCreateCustomerTable() throws SQLException {
	createCustomerTable();
	connection.createStatement().execute("SELECT * FROM customers");
}
Ora eseguiamo la richiesta, e anche con un parametro:
@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 non supporta parametri denominati per PreparedStatement, quindi i parametri stessi sono specificati dalle domande e specificando il valore indichiamo l'indice della domanda (a partire da 1, non zero). Nell'ultimo test abbiamo ricevuto vero come indicazione se c'è un risultato. Ma come viene rappresentato il risultato della query nell'API JDBC? Ed è presentato come ResultSet.
JDBC o dove tutto ha inizio - 7

Set di risultati

Il concetto di ResultSet è descritto nella specifica API JDBC nel capitolo "CAPITOLO 15 Result Set". Prima di tutto, si dice che ResultSet fornisce metodi per recuperare e manipolare i risultati delle query eseguite. Cioè, se il metodo di esecuzione ci restituisce true, allora possiamo ottenere un ResultSet. Spostiamo la chiamata al metodo createCustomerTable() nel metodo init, contrassegnato come @Before. Ora finalizziamo il nostro test 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);
}
Vale la pena notare qui che il prossimo è un metodo che sposta il cosiddetto “cursore”. Il cursore nel ResultSet punta a qualche riga. Pertanto, per leggere una riga, è necessario posizionare questo cursore su di essa. Quando il cursore viene spostato, il metodo di spostamento del cursore restituisce true se il cursore è valido (corretto, corretto), ovvero punta ai dati. Se restituisce false, non ci sono dati, ovvero il cursore non punta ai dati. Se proviamo a ottenere dati con un cursore non valido, otterremo l'errore: Nessun dato disponibile È anche interessante che tramite ResultSet sia possibile aggiornare o anche inserire righe:
@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();
}

RowSet

Oltre a ResultSet, JDBC introduce il concetto di RowSet. Puoi leggere ulteriori informazioni qui: " Nozioni di base su JDBC: utilizzo di oggetti RowSet ". Esistono varie varianti di utilizzo. Ad esempio, il caso più semplice potrebbe assomigliare a questo:
@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);
}
Come puoi vedere, RowSet è simile a una simbiosi tra istruzione (abbiamo specificato il comando tramite essa) e comando eseguito. Attraverso di esso controlliamo il cursore (chiamando il metodo successivo) e otteniamo dati da esso. Non solo questo approccio è interessante, ma anche le possibili implementazioni. Ad esempio, CachedRowSet. È "disconnesso" (ovvero non utilizza una connessione persistente al database) e richiede una sincronizzazione esplicita con il database:
CachedRowSet jdbcRsCached = new CachedRowSetImpl();
jdbcRsCached.acceptChanges(connection);
Puoi leggere di più nel tutorial sul sito web di Oracle: " Using CachedRowSetObjects ".
JDBC o dove tutto inizia - 8

Metadati

Oltre alle query, una connessione al database (ovvero un'istanza della classe Connection) fornisce l'accesso ai metadati: dati su come è configurato e organizzato il nostro database. Ma prima menzioniamo alcuni punti chiave: L’URL per la connessione al nostro database: “jdbc:h2:mem:test”. test è il nome del nostro database. Per l'API JDBC, questa è una directory. E il nome sarà in maiuscolo, cioè TEST. Lo schema predefinito per H2 è PUBLIC. Ora scriviamo un test che mostri tutte le tabelle utente. Perché personalizzato? Perché i database contengono non solo tabelle utente (quelle che noi stessi abbiamo creato utilizzando le espressioni create table), ma anche tabelle di sistema. Sono necessari per memorizzare le informazioni di sistema sulla struttura del database. Ciascun database può archiviare tali tabelle di sistema in modo diverso. Ad esempio, in H2 vengono memorizzati nello schema " INFORMAZIONI_SCHEMA ". È interessante notare che INFORMAZIONI SCHEMA è un approccio comune, ma Oracle ha seguito una strada diversa. Puoi leggere di più qui: " INFORMAZIONI_SCHEMA e Oracle ". Scriviamo un test che riceve metadati sulle tabelle utente:
@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 o dove tutto inizia - 9

Piscina di connessione

Il pool di connessioni nella specifica JDBC ha una sezione chiamata "Capitolo 11 Pool di connessioni". Fornisce inoltre la principale giustificazione per la necessità di un pool di connessioni. Ogni connessione è una connessione fisica al database. La sua creazione e chiusura è un lavoro piuttosto "costoso". JDBC fornisce solo un'API di pooling delle connessioni. Pertanto, la scelta dell'implementazione rimane nostra. Ad esempio, tali implementazioni includono HikariCP . Di conseguenza, dovremo aggiungere un pool alla dipendenza del nostro progetto:
dependencies {
    implementation 'com.h2database:h2:1.4.197'
    implementation 'com.zaxxer:HikariCP:3.3.1'
    testImplementation 'junit:junit:4.12'
}
Ora dobbiamo in qualche modo utilizzare questa piscina. Per fare ciò, è necessario inizializzare l'origine dati, nota anche come Datasource:
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;
}
E scriviamo un test per ricevere una connessione dal pool:
@Test
public void shouldGetConnectionFromDataSource() throws SQLException {
	DataSource datasource = getDatasource();
	try (Connection con = datasource.getConnection()) {
		assertTrue(con.isValid(1));
	}
}
JDBC o dove tutto inizia - 10

Transazioni

Una delle cose più interessanti di JDBC sono le transazioni. Nella specifica JDBC viene loro assegnato il capitolo "CAPITOLO 10 Transazioni". Prima di tutto, vale la pena capire cos'è una transazione. Una transazione è un insieme di operazioni sequenziali sui dati combinate logicamente, elaborate o cancellate nel loro insieme. Quando inizia una transazione quando si utilizza JDBC? Come afferma la specifica, questo viene gestito direttamente dal driver JDBC. Ma di solito, una nuova transazione inizia quando l'istruzione SQL corrente richiede una transazione e la transazione non è stata ancora creata. Quando termina la transazione? Questo è controllato dall'attributo auto-commit. Se il commit automatico è abilitato, la transazione verrà completata dopo il "completamento" dell'istruzione SQL. Il significato di "fatto" dipende dal tipo di espressione SQL:
  • Data Manipulation Language, noto anche come DML (Inserisci, Aggiorna, Elimina)
    La transazione viene completata non appena l'azione viene completata
  • Istruzioni Select
    La transazione viene completata quando il ResultSet viene chiuso ( ResultSet#close )
  • CallableStatement ed espressioni che restituiscono più risultati
    Quando tutti i ResultSet associati sono stati chiusi e tutto l'output è stato ricevuto (incluso il numero di aggiornamenti)
Questo è esattamente il modo in cui si comporta l'API JDBC. Come al solito, scriviamo un test per questo:
@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);
}
È semplice. Ma questo è vero finché abbiamo una sola transazione. Cosa fare quando ce ne sono molti? Hanno bisogno di essere isolati gli uni dagli altri. Parliamo quindi dei livelli di isolamento delle transazioni e di come JDBC li gestisce.
JDBC o dove tutto inizia - 11

Livelli di isolamento

Apriamo la sottosezione "10.2 Livelli di isolamento delle transazioni" della specifica JDBC. Qui, prima di proseguire, vorrei ricordare qualcosa come ACID. ACID descrive i requisiti per un sistema transazionale.
  • Atomicità:
    nessuna transazione verrà parzialmente impegnata nel sistema. Verranno eseguite tutte le operazioni secondarie oppure nessuna.
  • Coerenza:
    ogni transazione riuscita, per definizione, registra solo risultati validi.
  • Isolamento:
    mentre una transazione è in esecuzione, le transazioni simultanee non dovrebbero influenzarne l'esito.
  • Durabilità:
    se una transazione viene completata con successo, le modifiche apportate ad essa non verranno annullate a causa di eventuali errori.
Quando si parla di livelli di isolamento delle transazioni, si parla del requisito di “Isolamento”. L'isolamento è un requisito costoso, quindi nei database reali esistono modalità che non isolano completamente una transazione (livelli di isolamento di lettura ripetibile e inferiori). Wikipedia ha un'eccellente spiegazione dei problemi che possono sorgere quando si lavora con le transazioni. Vale la pena leggere di più qui: “ Problemi di accesso parallelo tramite transazioni ”. Prima di scrivere il nostro test, modifichiamo leggermente il nostro Gradle Build Script: aggiungiamo un blocco con proprietà, cioè con le impostazioni del nostro progetto:
ext {
    h2Version = '1.3.176' // 1.4.177
    hikariVersion = '3.3.1'
    junitVersion = '4.12'
}
Successivamente, lo usiamo nelle versioni:
dependencies {
    implementation "com.h2database:h2:${h2Version}"
    implementation "com.zaxxer:HikariCP:${hikariVersion}"
    testImplementation "junit:junit:${junitVersion}"
}
Potresti aver notato che la versione h2 è diventata inferiore. Vedremo perché più tardi. Quindi come si applicano i livelli di isolamento? Vediamo subito un piccolo esempio pratico:
@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);
}
È interessante notare che questo test potrebbe fallire su un fornitore che non supporta TRANSACTION_READ_UNCOMMITTED (ad esempio, sqlite o HSQL). E il livello delle transazioni potrebbe semplicemente non funzionare. Ricordi che abbiamo indicato la versione del driver del database H2? Se lo aumentiamo a h2Version = '1.4.177' e versioni successive, READ UNCOMMITTED smetterà di funzionare, sebbene non abbiamo modificato il codice. Ciò dimostra ancora una volta che la scelta del fornitore e della versione del driver non è composta solo da lettere, ma determinerà effettivamente il modo in cui verranno eseguite le tue richieste. Puoi leggere come risolvere questo comportamento nella versione 1.4.177 e come non funziona nelle versioni successive qui: " Supporta il livello di isolamento READ UNCOMMITTED in modalità MVStore ".
JDBC o dove tutto inizia - 12

Linea di fondo

Come possiamo vedere, JDBC è un potente strumento nelle mani di Java per lavorare con i database. Spero che questa breve recensione ti aiuti a darti un punto di partenza o ti aiuti a rinfrescarti la memoria. Bene, per uno spuntino, alcuni materiali aggiuntivi: #Viacheslav
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION