1. Общ преглед
API на Java Database Connectivity (JDBC) осигурява достъп до базата данни от Java приложение. Можем да използваме JDBC, за да се свържем с всяка база данни, стига да е налице поддържаният JDBC драйвер.
В ResultSet е таблица с данни, генерирани от изпълнението заявки в базата данни. В този урок ще разгледаме по-задълбочено API на ResultSet .
2. Генериране на ResultSet
Първо, извличаме ResultSet, като извикваме executeQuery () за всеки обект, изпълняващ интерфейса Statement . Както PreparedStatement, така и CallableStatement са подинтерфейси на Statement :
PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees"); ResultSet rs = pstmt.executeQuery();
Обектът ResultSet поддържа курсор, който сочи към текущия ред на резултата. Ще използваме next () на нашия ResultSet, за да прегледаме записите.
След това ще използваме методите getX () , докато итерираме през резултатите, за да извлечем стойностите от колоните на базата данни , където X е типът данни на колоната. Всъщност ще предоставим имена на колони в базата данни на методите getX () :
while(rs.next()) { String name = rs.getString("name"); Integer empId = rs.getInt("emp_id"); Double salary = rs.getDouble("salary"); String position = rs.getString("position"); }
По същия начин индексният номер на колоната може да се използва с методите getX () вместо името на колоната. Индексният номер е последователността на колоните в оператора за избор на SQL.
Ако операторът select не съдържа имена на колони, номерът на индекса е последователността на колоните в таблицата. Номерирането на индекса на колони започва от едно:
Integer empId = rs.getInt(1); String name = rs.getString(2); String position = rs.getString(3); Double salary = rs.getDouble(4);
3. Извличане на метаданни от ResultSet
В този раздел ще видим как да извлечем информация за свойствата и типовете на колоните в ResultSet .
Първо, нека използваме метода getMetaData () в нашия ResultSet, за да получим ResultSetMetaData :
ResultSetMetaData metaData = rs.getMetaData();
След това нека вземем броя на колоните, които са в нашия ResultSet :
Integer columnCount = metaData.getColumnCount();
Освен това можем да използваме всеки от методите по-долу за нашия обект с метаданни, за да извлечем свойства на всяка колона:
- getColumnName (int columnNumber) - за да получите името на колоната
- getColumnLabel (int columnNumber) - за достъп до етикета на колоната, който е посочен след AS в SQL заявката
- getTableName (int columnNumber) - за да получите името на таблицата, към която принадлежи тази колона
- getColumnClassName (int columnNumber) - за придобиване на типа данни на Java на колоната
- getColumnTypeName (int columnNumber) - за да получите типа данни на колоната в базата данни
- getColumnType (int columnNumber) - за да получите типа данни на SQL на колоната
- isAutoIncrement (int columnNumber) - указва дали колоната е автоматично нарастване
- isCaseSensitive (int columnNumber) - указва дали случаят на колоната има значение
- isSearchable (int columnNumber) - предлага дали можем да използваме колоната в клаузата where на SQL заявката
- isCurrency (int columnNumber) - сигнализира дали колоната съдържа парична стойност
- isNullable (int columnNumber) - връща нула, ако колоната не може да бъде нула, една, ако колоната може да съдържа нулева стойност, и две, ако анулирането на колоната е неизвестно
- isSigned (int columnNumber) - връща true, ако стойностите в колоната са подписани, в противен случай връща false
Нека да прегледаме колоните, за да получим техните свойства:
for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++) { String catalogName = metaData.getCatalogName(columnNumber); String className = metaData.getColumnClassName(columnNumber); String label = metaData.getColumnLabel(columnNumber); String name = metaData.getColumnName(columnNumber); String typeName = metaData.getColumnTypeName(columnNumber); int type = metaData.getColumnType(columnNumber); String tableName = metaData.getTableName(columnNumber); String schemaName = metaData.getSchemaName(columnNumber); boolean isAutoIncrement = metaData.isAutoIncrement(columnNumber); boolean isCaseSensitive = metaData.isCaseSensitive(columnNumber); boolean isCurrency = metaData.isCurrency(columnNumber); boolean isDefiniteWritable = metaData.isDefinitelyWritable(columnNumber); boolean isReadOnly = metaData.isReadOnly(columnNumber); boolean isSearchable = metaData.isSearchable(columnNumber); boolean isReadable = metaData.isReadOnly(columnNumber); boolean isSigned = metaData.isSigned(columnNumber); boolean isWritable = metaData.isWritable(columnNumber); int nullable = metaData.isNullable(columnNumber); }
4. Навигиране в ResultSet
Когато получим ResultSet , позицията на курсора е преди първия ред. Освен това по подразбиране ResultSet се движи само в посока напред. Но можем да използваме превъртащ се ResultSet за други навигационни опции.
В този раздел ще обсъдим различните опции за навигация.
4.1. ResultSet Типове
Типът ResultSet показва как ще направляваме през набора от данни:
- TYPE_FORWARD_ONLY - опцията по подразбиране, при която курсорът се движи от началото до края
- TYPE_SCROLL_INSENSITIVE - нашият курсор може да се движи през набора от данни както в посока напред, така и назад; ако има промени в основните данни, докато се движите през набора от данни, те се игнорират; наборът от данни съдържа данните от момента, в който заявката за база данни връща резултата
- TYPE_SCROLL_SENSITIVE - подобно на нечувствителния към превъртане тип, но за този тип наборът данни веднага отразява всички промени в основните данни
Не всички бази данни поддържат всички типове ResultSet . И така, нека проверим дали типът се поддържа с помощта на supportResultSetType на нашия обект DatabaseMetaData :
DatabaseMetaData dbmd = dbConnection.getMetaData(); boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
4.2. Скролируем резултат
За да получим превъртаем ResultSet , трябва да предадем някои допълнителни параметри, докато подготвяме изявлението .
Например бихме могли да получим превъртаем ResultSet, като използваме TYPE_SCROLL_INSENSITIVE или TYPE_SCROLL_SENSITIVE като тип ResultSet :
PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery();
4.3. Опции за навигация
Можем да използваме някоя от опциите по-долу в превъртащ се ResultSet :
- next () - преминава към следващия ред от текущата позиция
- previous () - преминава към предишния ред
- first () - преминава към първия ред на ResultSet
- last () - прескача на последния ред
- beforeFirst () - преминава към старта; извикването на next () на нашия ResultSet след извикване на този метод връща първия ред от нашия ResultSet
- afterLast () - скача до края; извикването на previous () на нашия ResultSet след изпълнението на този метод връща последния ред от нашия ResultSet
- относително (int numOfRows) - преминете напред или назад от текущата позиция от numOfRows
- абсолютна (инт rowNumber) - скача до rowNumber посочено
Нека да видим няколко примера:
PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // iterate through the results from first to last } rs.beforeFirst(); // jumps back to the starting point, before the first row rs.afterLast(); // jumps to the end of resultset rs.first(); // navigates to the first row rs.last(); // goes to the last row rs.absolute(2); //jumps to 2nd row rs.relative(-1); // jumps to the previous row rs.relative(2); // jumps forward two rows while (rs.previous()) { // iterates from current row to the first row in backward direction }
4.4. Резултат Задайте брой редове
Нека използваме getRow (), за да получим номера на текущия ред на нашия ResultSet .
Първо ще преминем към последния ред на ResultSet и след това ще използваме getRow (), за да получим броя на записите:
rs.last(); int rowCount = rs.getRow();
5. Актуализиране на данни в ResultSet
По подразбиране ResultSet е само за четене. Въпреки това можем да използваме актуализируем ResultSet, за да вмъкнем, актуализираме и изтрием редовете.
5.1. ResultSet Concurrency
Режимът на паралелност показва дали нашият ResultSet може да актуализира данните.
Опцията CONCUR_READ_ONLY е по подразбиране и трябва да се използва, ако не е необходимо да актуализираме данните с помощта на нашия ResultSet .
Ако обаче трябва да актуализираме данните в нашия ResultSet , тогава трябва да се използва опцията CONCUR_UPDATABLE .
Не всички бази данни поддържат всички паралелни режими за всички типове ResultSet . Следователно трябва да проверим дали желаният тип и режим на паралелност се поддържат с помощта на метода supportsResultSetConcurrency () :
DatabaseMetaData dbmd = dbConnection.getMetaData(); boolean isSupported = dbmd.supportsResultSetConcurrency( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
5.2. Получаване на актуализируем набор от резултати
За да получим актуализируем ResultSet , трябва да предадем допълнителен параметър, когато подготвяме изявлението . За това нека използваме CONCUR_UPDATABLE като трети параметър, докато създаваме изявление:
PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery();
5.3. Актуализиране на ред
В този раздел ще актуализираме ред, използвайки актуализирания ResultSet, създаден в предишния раздел.
Можем да актуализираме данните подред, като извикаме методите updateX () , предавайки имената и стойностите на колоните за актуализиране. Можем да използваме всеки поддържан тип данни вместо X в метода updateX () .
Нека актуализираме колоната „заплата“ , която е от тип double :
rs.updateDouble("salary", 1100.0);
Имайте предвид, че това просто актуализира данните в ResultSet , но модификациите все още не са запазени обратно в базата данни.
И накрая, нека извикаме updateRow (), за да запазим актуализациите в базата данни :
rs.updateRow();
Вместо имената на колоните можем да предадем индекса на колоната на методите updateX () . Това е подобно на използването на индекса на колона за получаване на стойностите чрез методите getX () . Предаването на името на колоната или индекса на методите updateX () дава същия резултат:
rs.updateDouble(4, 1100.0); rs.updateRow();
5.4. Вмъкване на ред
Сега, нека вмъкнем нов ред с помощта на нашия актуализиран ResultSet .
Първо ще използваме moveToInsertRow (), за да преместим курсора, за да вмъкнем нов ред:
rs.moveToInsertRow();
След това трябва да извикаме методите updateX () , за да добавим информацията към реда. Трябва да предоставим данни на всички колони в таблицата на базата данни. Ако не предоставяме данни за всяка колона, тогава се използва стойността на колоната по подразбиране:
rs.updateString("name", "Venkat"); rs.updateString("position", "DBA"); rs.updateDouble("salary", 925.0);
След това нека извикаме insertRow (), за да вмъкнем нов ред в базата данни:
rs.insertRow();
И накрая, нека използваме moveToCurrentRow (). Това ще върне позицията на курсора към реда, в който бяхме, преди да започнем да вмъкваме нов ред, използвайки метода moveToInsertRow () :
rs.moveToCurrentRow();
5.5. Изтриване на ред
В този раздел ще изтрием ред с помощта на нашия обновяващ се ResultSet .
Първо ще преминем към реда, който искаме да изтрием. След това ще извикаме метода deleteRow () , за да изтрием текущия ред:
rs.absolute(2); rs.deleteRow();
6. Задържане
Задържането определя дали нашият ResultSet ще бъде отворен или затворен в края на транзакция с база данни.
6.1. Видове задържане
Use CLOSE_CURSORS_AT_COMMIT if the ResultSet is not required after the transaction is committed.
Use HOLD_CURSORS_OVER_COMMIT to create a holdable ResultSet. A holdable ResultSet is not closed even after the database transaction is committed.
Not all databases support all the holdability types.
So, let's check if the holdability type is supported using supportsResultSetHoldability() on our DatabaseMetaData object. Then, we'll get the default holdability of the database using getResultSetHoldability():
boolean isCloseCursorSupported = dbmd.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT); boolean isOpenCursorSupported = dbmd.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT); boolean defaultHoldability = dbmd.getResultSetHoldability();
6.2. Holdable ResultSet
To create a holdable ResultSet, we need to specify the holdability type as the last parameter while creating a Statement. This parameter is specified after the concurrency mode.
Note that if we're using Microsoft SQL Server (MSSQL), we have to set holdability on the database connection, rather than on the ResultSet:
dbConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
Let's see this in action. First, let's create a Statement, setting the holdability to HOLD_CURSORS_OVER_COMMIT:
Statement pstmt = dbConnection.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE, ResultSet.HOLD_CURSORS_OVER_COMMIT)
Now, let's update a row while retrieving the data. This is similar to the update example we discussed earlier, except that we'll continue to iterate through the ResultSet after committing the update transaction to the database. This works fine on both MySQL and MSSQL databases:
dbConnection.setAutoCommit(false); ResultSet rs = pstmt.executeQuery("select * from employees"); while (rs.next()) { if(rs.getString("name").equalsIgnoreCase("john")) { rs.updateString("name", "John Doe"); rs.updateRow(); dbConnection.commit(); } } rs.last();
It's worth noting that MySQL supports only HOLD_CURSORS_OVER_COMMIT. So, even if we use CLOSE_CURSORS_AT_COMMIT, it will be ignored.
The MSSQL database supports CLOSE_CURSORS_AT_COMMIT. This means that the ResultSet will be closed when we commit the transaction. As a result, an attempt to access the ResultSet after committing the transaction results in a ‘Cursor is not open error’. Therefore, we can’t retrieve further records from the ResultSet.
7. Fetch Size
Typically, when loading data into a ResultSet, the database drivers decide on the number of rows to fetch from the database. On a MySQL database, for example, the ResultSet normally loads all the records into memory at once.
Sometimes, however, we may need to deal with a large number of records that won't fit into our JVM memory. In this case, we can use the fetch size property either on our Statement or ResultSet objects to limit the number of records initially returned.
Whenever additional results are required, ResultSet fetches another batch of records from the database. Using the fetch size property, we can provide a suggestion to the database driver on the number of rows to fetch per database trip. The fetch size we specify will be applied to the subsequent database trips.
If we don't specify the fetch size for our ResultSet, then the fetch size of the Statement is used. If we don't specify fetch size for either the Statement or the ResultSet, then the database default is used.
7.1. Using Fetch Size on Statement
Now, let's see the fetch size on Statement in action. We'll set the fetch size of the Statement to 10 records. If our query returns 100 records, then there will be 10 database round trips, loading 10 records each time:
PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); pstmt.setFetchSize(10); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // iterate through the resultset }
7.2. Using Fetch Size on ResultSet
Now, let's change the fetch size in our previous example using the ResultSet.
First, we'll use the fetch size on our Statement. This allows our ResultSet to initially load 10 records after executing the query.
Then, we'll modify the fetch size on the ResultSet. This will override the fetch size we earlier specified on our Statement. So, all the subsequent trips will load 20 records until all the records are loaded.
As a result, there will be only 6 database trips to load all the records:
PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); pstmt.setFetchSize(10); ResultSet rs = pstmt.executeQuery(); rs.setFetchSize(20); while (rs.next()) { // iterate through the resultset }
Finally, we'll see how to modify the fetch size of the ResultSet while iterating the results.
Similar to the previous example, we'll first set the fetch size to 10 on our Statement. So, our first 3 database trips will load 10 records per each trip.
И тогава ще модифицираме размера на извличането на нашия ResultSet на 20, докато четем 30-ия запис. И така, следващите 4 пътувания ще заредят 20 записа на всяко пътуване.
Следователно ще са ни необходими 7 пътувания с база данни, за да заредим всичките 100 записа:
PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); pstmt.setFetchSize(10); ResultSet rs = pstmt.executeQuery(); int rowCount = 0; while (rs.next()) { // iterate through the resultset if (rowCount == 30) { rs.setFetchSize(20); } rowCount++; }
8. Заключение
В тази статия видяхме как да използваме ResultSet API за извличане и актуализиране на данни от база данни. Няколко от разширените функции, които обсъдихме, зависят от базата данни, която използваме. По този начин трябва да проверим поддръжката на тези функции, преди да ги използваме.
Както винаги, кодът е достъпен в GitHub.