Το κείμενο προυποθέτει την ύπαρξη πρόσφατων εκδόσεων του JDK και της MySQL. Προετοιμασία για την εκτέλεση των προγραμμάτων:
Εγκατάσταση του κατάλληλου JDBC driver: για τη MySQL, εγκαθιστούμε το Connector/J. Για άλλα DBMS's συμβουλευτείτε το κατασκευαστή ή τις ιστοσελίδες της Java.
Εγκατάσταση Apache Ant: πρόκειται για ένα Java-based εργαλείο για την εκτέλεση των εφαρμογών που παρουσιάζονται εδώ. Ο σύνδεσμος είναι
http://ant.apache.org/
. Βεβαιωθείτε οτι το ονομα διαδρομής του εκτελέσιμου του Apache Ant βρίσκεται στη μεταβλητή περιβάλλοντοςPATH
του συστήματός σας.Μεταφορτώστε το κώδικα των εφαρμογών: ο κώδικας
JDBCTutorial.zip
, περιλαμβάνει τα παρακάτω αρχεία:
properties
javadb-build-properties.xml
javadb-sample-properties.xml
mysql-build-properties.xml
mysql-sample-properties.xml
sql
javadb
create-procedure-show-suppliers.sql
create-tables.sql
drop-tables.sql
populate-tables.sql
mysql
create-procedure-show-suppliers.sql
create-tables.sql
drop-tables.sql
populate-tables.sql
src/com/oracle/tutorial/jdbc
CoffeesTable.java
JDBCTutorialUtilities.java
JoinSample.java
StoredProcedureJavaDBSample.java
StoredProcedureMySQLSample.java
SuppliersTable.java
build.xml
Δημιουργούμε ένα κατάλογο που από εδώ και στο εξής θα τον ονομάζουμε
<JDBC tutorial directory>
. Αποσυμπιέζουμε τοJDBCTutorial.zip
στο<JDBC tutorial directory>
. Τα αρχεία που σχετίζονται με τη JavaDB δεν μας είναι απαραίτητα για το παρόν κείμενο.
Τροποποίηση του αρχείου
build.xml
.Το αρχείο
build.xml
χρησιμοποιείται από το Apache Ant για τη μεταγλώττιση κα εκτέλεση των εφαρμογών JDBC. Επίσης χρησιμοποιούνται τ αρχείαproperties/mysql-build-properties.xml
καιproperties/mysql-sample-properties.xml
.
Στο αρχείο
build.xml
, τροποποιούμε την επιλογήANTPROPERTIES
ώστε να αναφέρεται στοproperties/mysql-build-properties.xml
:<property name="ANTPROPERTIES" value="properties/mysql-build-properties.xml"/>
<import file="${ANTPROPERTIES}"/>Στο αρχείο
properties/mysql-build-properties.xml
τροποποιύμε τα παρακάτω:
Ιδιότητα
Περιγραφή JAVAC
Όνομα διαδρομής του εκτελέσιμου javac
(εκτελούμε $ which javac)JAVA
Όνομα διαδρομής του εκτελέσιμο java
(εκτελούμε $ which java)PROPERTIESFILE
Το όνομα του αρχείου properties/mysql-sample-properties.xml
MYSQLDRIVER
Όνομα διαδρομής του MySQL driver. Τυπικά είναι <Connector/J installation directory>/mysql-connector-java-version-number.jar
JAVADBDRIVER
Όνομα διαδριμης του Derby driver (εκτελούμε $ locate derby.jar.. Αν δεν υπαρχει το μεταφορτώνουμε από το http://db.apache.org/derby/)
DB.VENDOR
Θέτουμε mysql
DB.DRIVER
Θέτουμε com.mysql.jdbc.Driver
DB.HOST
hostname του συστήματος που βρίσκεται το DBMS (εκτελούμε $ cat /etc/hosts)
DB.PORT
port number που συνδέεται το DBMS (εκτελούμε $ cat /etc/services | grep mysql)
DB.SID
Όνομα της βάσης δεδομένων που δημιουργούν και χρησιμοποιούν οι εφαρμογές
DB.URL
URL σύνδεσης με το DBMS. Δεν είναι απαραίτητη η αλλαγή
DB.USER
Όνομα του χρήστη που έχει δικαίωμα δημιουργίας βάσης στο DBMS DB.PASSWORD
password του χρήστη του DB.USER
DB.DELIMITER
χαρακτήρας διαχωρισμού SQL εντολών. Μη το αλλάξετε. Πρέπει να είναι το ελληνικό ερωτηματικό " ;
"Τροποποίηση του αρχείου ιδιοτήτων εφαρμογής.
Πρόκειται για το αρχείο
properties/mysql-sample-properties.xml
file. Οι αλλαγές είναι οι παρακάτω:
Ιδιότητα Περιγραφή
dbms
Θέτουμε mysql
jar_file
Όνομα διαδρομής για τα class files των εφαρμογών - υποκατάλογος του <JDBC tutorial directory>
driver
Θέτουμε com.mysql.jdbc.Driver
.database_name
Όνομα της βάσης δεδομένων που δημιουργούν και χρησιμοποιούν οι εφαρμογές user_name
Όνομα του χρήστη που έχει δικαίωμα δημιουργίας βάσης στο DBMS password
password του χρήστη του user_name
.server_name
hostname του συστήματος που βρίσκεται το DBMS (εκτελούμε $ cat /etc/hosts) port_number
port number που συνδέεται το DBMS (εκτελούμε $ cat /etc/services | grep mysql) Μεταγλώττιση των εφαρμογών.
Μετακινούμαστε στο κατάλογο
<JDBC tutorial directory>
και στη προτροπή εισάγουμε την εντολή:
ant compile
Εκτέλεση των εφαρμογών.
Κάθε αρχείο στόχου στο αρχείο
build.xml
αντιστοιχεί σε ένα αρχείο Java class ή SQL script εφαρμογής. Ο παρακάτω πίνακας συνδέει το όνομα του αρχείου στόχου τουbuild.xml
με το αντίστοιχο Java class ή SQL script αρχείο καθώς και με άλλα εκτελέσιμα που απαιτούνται:
Αρχείο Στόχου
Java Class ή SQL Script Απαιτούμενα Αρχεία
drop-tables
drop-tables.sql
Κανένα build-tables
build-tables.sql
drop-tables
populate-tables
populate-tables.sql
build-tables
javadb-create-procedure
javadb-create-procedure.sql
Κανένα mysql-create-procedure
Στο build.xml
φαίνονται οι SQL που εκτελούνται
Κανένα run
JDBCTutorialUtilities
Κανένα runct
CoffeesTable
JDBCTutorialUtilities
runst
SuppliersTable
JDBCTutorialUtilities
runjs
JoinSample
JDBCTutorialUtilities
,CoffeesTable
,SuppliersTable
runspjavadb
StoredProcedureJavaDBSample
Σημείωση: Απαιτείται ιδιαίτερη διαμόρφωση.
JDBCTutorialUtilities
,SuppliersTable
,CoffeesTable
runspmysql
StoredProcedureMySQLSample
JDBCTutorialUtilities
,SuppliersTable
,CoffeesTable
Για παράδειγμα, για την εκτέλεση της εφαρμογής
CoffeesTable
, μετακονούμαστε στο κατάλογο<JDBC tutorial directory>
, και στη προτροπή εισάγουμε την εντολή:ant runct
Γενικά η επεξεργασία SQL εντολών με JDBC, έχει τα εξής βήματα:
- Άνοιγμα σύνδεσης με τη βάση.
- Δημιουργία εντολής SQL.
- Εκτέλεση εντολής SQL.
- Επεξεργασία αντικειμένου
ResultSet
.- Κλείσιμο σύνδεσης με τη βάση.
Ως παράδειγμα ας δούμε τη μέθοδο
CoffeesTable.viewTable
, από τις εφαρμογήCoffeesTable.java
. H μέθοδος αυτή εμφανίζει τα περιεχόμενα του πίνακαCOFFEES
. Η μέθοδος θα εξηγηθεί αναλυτικότερα αργότερα:public static void viewTable(Connection con, String dbName) throws SQLException {
Statement stmt = null;
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from " + dbName + ".COFFEES";
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + "\t" + supplierID + "\t" + price + "\t" + sales + "\t" + total);
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}Άνοιγμα Σύνδεσης με τη Βάση
Η σύνδεση εξαρτάται από το τύπο της βάσης δεδομένων και τον αντίστοιχο JDBC driver. Η επιτυχής σύνδεση αντιπροσωπεύεται από ένα αντικείμενο
Connection
. Η σύνδεση αναλύεται στην επόμενη ενότητα.Δημιουργία Εντολής SQL
To
Statement
είναι μια διεπιφάνεια που αντιπροσωπεύει μια εντολή SQL. Εκτελούμε αντικείμεναStatement
, και αυτά δημιουργούν αντικείμεναResultSet
, τα οποία είναι πίνακες δεδομένων που προκύπτουν από τα δεδομένα της βάσης. Για τη δημιουργία του αντικειμένουStatement
προαπαιτείται ένα αντικείμενοConnection
.
Για παράδειγμα, η μέθοδος
CoffeesTable.viewTable
δημιουργεί ένα αντικείμενοStatement
με το παρακάτω κώδικα:stmt = con.createStatement();Υπάρχουν τρείς διαφορετικοί τύποι εντολών SQL:
Statement
: Απλές εντολές SQL χωρίς παραμέτρους.PreparedStatement
: (ExtendsStatement
.) Προετοιμασμένες εντολές SQL με παραμέτρους εισόδου. Εξηγούνται στην αντίστοιχη ενότητα.CallableStatement:
(ExtendsPreparedStatement
.) Αποθηκευμένες διδακασίες SQL με παραμέτρους εισόδου και εξόδου. Εξηγούνται στην αντίστοιχη ενότητα.Εκτέλεση Εντολής SQL
Για την εκτέλεση ενός ερωτήματος, καλούμε τη μέθοδο
execute
από τη μέθοδοStatement
ως εξής:
execute
: Επιστρέφειtrue
αν το πρώτο αντικείμενο που επιστρέφει το ερώτημα είναι ένα αντικείμενοResultSet
object. Με αυτή τη μέθοδο ανακαλούμε ένα ή πολλά αντικείμεναResultSet
. Για την ανάκληση πολλών αντικειμένωνResultSet
θέτουμε τη κλήσηStatement.getResutSet
μέσα σε επανάληψη.
executeQuery
: Επιστρέφει μόνο ένα αντικείμενοResultSet
.executeUpdate
: Επιστρέφει έναν ακέραιο που δηλώνει πόσες γραμμές του πίνακα ενημερώθηκαν από την εντολή SQL. Χρησιμοποιείται στις εντολές SQLINSERT
,DELETE
, ήUPDATE
.Για παράδειγμα, η μέθοδος
CoffeesTable.viewTable
εκτελεί ένα αντικείμενοStatement
με το κώδικα:ResultSet rs = stmt.executeQuery(query);Επεξεργασία Αντικειμένου ResultSet
Η προσπέλαση των δεδομένων στο αντικείμενο
ResultSet
επιτυγχάνεται μέσω ενός δρομέα, δηλαδή ενός δείκτη που δείχνει σε μια γραμμή δεδομένων στο αντικείμενοResultSet
. Αρχικά, ο δρομέας δείχνει πριν τη πρώτη γραμμή. Υπάρχουν διάφορες μέθοδοι για τη κίνηση του δρομέα που ορίζονται μέσω του αντικειμένουResultSet
.Για παράδειγμα, η μέθοδος
CoffeesTable.viewTable
καλεί τη μέθοδοResultSet.next
επαναληπτικά για τη κίνηση του δρομέα από γραμμή σε γραμμή και την εμφάνιση των δεδομένων της γραμμής που ορίζει η μέθοδοςnext
:try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + "\t" + supplierID + "\t" + price + "\t" + sales + "\t" + total);
}Η ανάκτηση και επεξεργασία δεδομένων αναλύεται σε ξεχωριστή ενότητα.
Κλείσιμο Σύνδεσης με τη Βάση
Όταν τελειώσουμε με την επεξεργασία του αντικειμένου
Statement
, καλούμε τη μέθοδοStatement.close
για να αποδεσμεύσουμε τους πόρους που έχουν δεσμευτεί. Με τη κλήση αυτής της μεθόδου τα αντικείμεναResultSet
κλείνουν.Για παράδειγμα, η μέθοδος
CoffeesTable.viewTable
διασφαλίζει οτι το αντικείμενοStatement
κλείνει, ανεξάρτητα από την ύπαρξη αντικειμένωνSQLException
, περιτυλίγοντάς τη σε ένα blockfinally
:} finally {
stmt.close();
}To JDBC παράγει
SQLException
όταν συναντήσει σφάλμα κατά την επικοινωνία με τη βάση. Η διαχείριση των σφαλμάτων συζητείται παρακάτω.
Κατ' αρχή πρέπειο να συνδεθούμε με τη βάση δεδομένων. Τυπικά μια εφαρμογή JDBC συνδέεται με τη βάση μέσω δύο κλάσεων:
DriverManager
: Η κλάση αυτή συνδέει την εφαρμογή με μια βάση δεδομένων που βρίσκεται στο URL που δίνεται. Η κλάση χρησιμοποιεί τον JDBC 4.0 driver που θα βρεί στο class path.
DataSource
: H διεπαφή αυτή επιτρέπει μεγαλύτερη ευελιξία στη δήλωση στοιχείων σχετικών με τη βάση δεδομένων.Σημείωση: Εδώ χρησιμοποιείται κυρίως η κλάση
DriverManager
επειδή είναι πιο εύκολη στη χρήση.Χρήση Κλάσης DriverManager
Η σύνδεση στο DBMS με τη κλάση
DriverManager
απαιτεί κλήση της μεθόδουDriverManager.getConnection
. Η μέθοδοςJDBCTutorialUtilities.getConnection
ανοίγει μια σύνδεση με τη βάση δεδομένων:public Connection getConnection() throws SQLException {
Connection conn = null;
Properties connectionProps = new Properties();
connectionProps.put("user", this.userName);
connectionProps.put("password", this.password);
conn = DriverManager.
getConnection("jdbc:" + this.dbms + "://" + this.serverName +
":" + this.portNumber + "/", connectionProps);
System.out.println("Connected to database");
return conn;
}Η μέθοδος απαιτεί ένα URL της μορφής:
jdbc:mysql://localhost:3306/
, όπουlocalhost
είναι το hostname του συστήματος που βρίσκεται το DBMS, και3306
είναι το port number. Ακόμη ορίζονται και όνομα χρήστη και password, όλα με βάση το αρχείοProperties
.
Τυπικά, στο URL προσθέτουμε και το όνομα
μιας υπάρχουσας βάσης με την οποία θέλουμε να συνδεθούμε. Για
παράδειγμα, το URL jdbc:mysql://localhost:3306/my_data
είναι ένα URL για μια βάση δεδομένων MySQL με όνομα my_data
.
Στο
παράδειγμα
παραπάνω
δεν
ορίζουμε
όνομα
επειδή
θα
δημιουργήσουμε
νέα
βάση.
Η μέθοδος επιστρέφει ένα αντικείμενο
Connection
, που αναπαριστά τη σύνδεσή μας με τη βάση. Επικοινωνούμε με τη βάση μέσω αυτού του αντικειμένου.Σύνταξη ενός URL βάσης δεδομένων MySQL
H ακριβής σύνταξη του πλήρους URL για το MySQL Connector/J είναι η εξής:jdbc:mysql://[host][,failoverhost...][:port]/[database][?propertyName1][=propertyValue1][&propertyName2][=propertyValue2]...
host:port
είναι τα host name και port number του διακομιστή της βάσης δεδομένων. Αν δεν ορίζονται, οι προκαθορισμένες τιμές για ταhost
καιport
είναι 127.0.0.1 και 3306, αντίστοιχα.database
είναι το όνομα της βάσης. Αν δεν ορίζεται, δεν υπάρχει προκαθορισμένη τιμή.failoverhost
είναι λίστα ονομάτων ομοιότυπων (replicated) hosts (το MySQL Connector/J υποστηρίζει failover).propertyName=propertyValue
μέσω της λίστας ιδιοτήτων το MySQL Connector/J επιτρέπει την εκτέλεση διαφόρων εργασιών στη βάση.Το εγχειρίδιο MySQL Reference Manual έχει περισσότερες πληροφορίες.
Χρήση Αντικειμένου DataSource
Η μέθοδος
JDBCTutorialUtilities.getConnectionWithDataSource
δημιουργεί ένα αντικείμενοDataSource
και στη συνέχεια έμα αντικείμενοConnection
, που αναπαριστά μια σύνδεση με το αντικείμενιDataSource
:public Connection getConnectionWithDataSource(String dbmsName, String dbNameArg, String userName, String password,
String serverName, int portNumber) throws SQLException {
Connection conn = null;
com.mysql.jdbc.jdbc2.optional.MysqlDataSource ds = null;
ds = new com.mysql.jdbc.jdbc2.optional.MysqlDataSource();
ds.setUser(userName);
ds.setPassword(password);
ds.setServerName(serverName);
ds.setPortNumber(portNumber);
conn = ds.getConnection();
System.out.println("Connected to database");
return conn;
}Σε αντίθεση με το αντικείμενο
DriverManager
, στο αντικείμενοDataSource
ο χρήστης μπορεί να ορίσει μέσω μεταβλητών διάφορα στοιχεία όπως το host name και το port number. Έτσι η εφαρμογή είναι πιο ευέλικτη.
Όταν το JDBC συναντά ένα σφάλμα κατά την επικοινωνία με τη βάση δεδομένων, τότε παράγει ένα στιγμιότυπο της κλάσης
SQLException
σε αντίθεση με τηException
. Το στιγμιότυποSQLException
περιέχει τις παρακάτω πληροφορίες που χαρακτηρίζουν το σφάλμα:
Περιγραφή του σφάλματος. Βρίσκεται σε ένα αντικείμενο
String
που μπορεί να ανακληθεί με τη μέθοδοSQLException.getMessage
.Κωδικός SQLState. Οι κωδικοί αυτοί και η σημασία τους έχουν τυποποιηθεί σχεδόν στο σύνολό τους από τους οργανισμούς ISO/ANSI και Open Group (X/Open). Αυτό το αντικείμενο
String
περιλαμβάνει 5 αλφαριθμητικούς χαρακτήρες και μπορεί να ανακληθεί με τη μέθοδοSQLException.getSQLState
.Κωδικός σφάλματος. Είναι ένας ακέραιος που καθορίζει το σφάλμα που προκάλεσε τη
SQLException
. Οι κωδικοί εξαρτώνται από την υλοποίηση και συνήθως παράγεται από τη βάση δεδομένων. Μπορεί να ανακληθεί με τη μέθοδοSQLException.getErrorCode
.Το αίτιο. Το στιγμιότυπο της
SQLException
συνήθως παράγεται εξ'αιτίας ενός ή περισσοτέρωνThrowable
αντικειμένων. Η ανάκληση αυτών των αντικειμένων επιτυγχάνεται με αναδρομική κλήση της μεθόδουSQLException.getCause
μέχρι να ανακληθεί μια τιμήnull
.Αναφορά σε αλυσιδωτά σφάλματα. Αν εμφανιστούν περισσότερα σφάλματα, τότε με τη βοήθεια της μεθόδου
SQLException.getNextException
μπορούμε να ανακαλέσουμε τις εξαιρέσεις.Εμφάνιση Εξαιρέσεων
Η μέθοδος
JDBCTutorialUtilities.printSQLException
εμφανίζει τις παραπάνω πληροφορίες:public static void printSQLException(SQLException ex) {Μπορούμε να επεξεργαστούμε την
for (Throwable e : ex) {
if (e instanceof SQLException) {
if (ignoreSQLException(((SQLException)e).getSQLState()) == false) {
e.printStackTrace(System.err);
System.err.println("SQLState: " + ((SQLException)e).getSQLState());
System.err.println("Error Code: " + ((SQLException)e).getErrorCode());
System.err.println("Message: " + ((SQLException)e).getMessage());
Throwable t = ex.getCause();
while(t != null) {
System.out.println("Cause: " + t);
t = t.getCause();
}
}
}
}
}SQLException
, αν πρώτα ανακαλέσουμε το κωδικόSQLState
και ενεργήσουμε ανάλογα. Για παράδειγμα, η μέθοδοςJDBCTutorialUtilities.ignoreSQLException
επιστρέφειtrue
αν ο κωδικός είναι ένας από δύο συγκεκριμένους:public static boolean ignoreSQLException(String sqlState) {
// X0Y32: Jar file already exists in schema
if (sqlState.equalsIgnoreCase("X0Y32")) return true;
// 42Y55: Table already exists in schema
if (sqlState.equalsIgnoreCase("42Y55")) return true;
return false;
}Εμφάνιση Ειδοποιήσεων
Τα αντικείμενα
SQLWarning
είναι υποκλάση τηςSQLException
. Οι ειδοποιήσεις δε σταματούν την εκτέλεση της εφαρμογής, όπως οι εξαιρέσεις, απλά ενημερώνουν το χρήστη οτι κατι δεν εκτελέστηκε ακριβώς όπως είχε σχεδιαστεί.
Ειδοποιήσεις μπορεί να εμφανιστούν στα αντικείμενα
Connection
,Statement
(PreparedStatement
καιCallableStatement
), ήResultSet
. Κάθε μια από αυτές τις κλάσεις έχει μια μέθοδοgetWarnings
, μέσω της οποίας εμφανίζεται η πρώτη σχετική ειδοποίηση. Αν ηgetWarnings
επιστρέψει μια ειδοποίηση, τότε η μέθοδοςSQLWarning
μεgetNextWarning
εμφανίζει άλλες ειδοποιήσεις, αν υπάρχουν. Αν προχωρήσουμε στην εκτέλεση νέας εντολής SQL οι πιθανές ειδοποιήσεις της τρέχουσας εκτέλεσης διαγράφονται.
Οι παρακάτω μέθοδοι από τη βιβλιοθήκη
JDBCTutorialUtilities
εξηγούν την εμφάνιση ειδοποιήσεων που προκύπτουν σε αντικείμεναStatement
ήResultSet
:public static void getWarningsFromResultSet(ResultSet rs) throws SQLException {
JDBCTutorialUtilities.printWarnings(rs.getWarnings());
}
public static void getWarningsFromStatement(Statement stmt) throws SQLException {
JDBCTutorialUtilities.printWarnings(stmt.getWarnings());
}
public static void printWarnings(SQLWarning warning) throws SQLException {
if (warning != null) {
System.out.println("\n---Warning---\n");
while (warning != null) {
System.out.println("Message: " + warning.getMessage());
System.out.println("SQLState: " + warning.getSQLState());
System.out.print("Vendor error code: ");
System.out.println(warning.getErrorCode());
System.out.println("");
warning = warning.getNextWarning();
}
}
}Η συνηθέστερη ειδοποίηση είναι η
DataTruncation
, υποκκάση τηςSQLWarning
. 'Ολα τα αντικείμεναDataTruncation
έχουν κωδικό SQLState01004
, που σημαίνει ύπαρξη προβλήματος στην ανάγνωση ή εγγραφή δεδομένων. Οι μέθοδοιDataTruncation
μας επιτρέπουν να δούμε σε ποιά δεδομένα έγινε το σφάλμα, αν ήταν σφάλμα εγγραφής ή ανάγνωσης, πόσα bytes έπρεπε να μεταφερθούν και πόσα τελικά μεταφέρθηκαν.Υποκλάσεις SQLExceptions
SQLNonTransientException
SQLTransientException
SQLRecoverableException
BatchUpdateException
SQLClientInfoException
Το πρόγραμμα
CoffeesTable.java
δημιουργεί το παρακάτω πίνακα και εισάγει τα αντίστοιχα δεδομένα:
COF_NAME SUP_ID PRICE SALES TOTAL Colombian 101 7.99 0 0 French_Roast 49 8.99 0 0 Espresso 150 9.99 0 0 Colombian_Decaf 101 8.99 0 0 French_Roast_Decaf 49 9.99 0 0 Περιγραφή των στηλών του πίνακα
COFFEES
:
COF_NAME
: Όνομα, τύπου SQLVARCHAR
με μέγιστο μήκος 32 χαρακτήρων. Ορίζεται ως κύριο κλειδί του πίνακα.SUP_ID
: Κωδικός προμηθευτή, τύπου SQLINTEGER
. Ορίζεται ως ξένο κλειδί αναφοράς στη στήληSUP_ID
του πίνακαSUPPLIERS
. Το DBMS επιβάλει κάθε τιμή σε αυτή τη στήλη να ταυτίζεται με κάποια τιμή της αντίστοιχης στήλης του πίνακαSUPPLIERS
.PRICE
: Τιμή ανά κιλό σε ευρώ, τύπου SQLFLOAT
λόγω δεκαδικών. (Θα μπορούσε να είναι και τύπου SQLDECIMAL
ήNUMERIC
, αλλά τοFLOAT
είναι πιο καθολικό).SALES
: Πωλήσεις σε κιλά τη τρέχουσα εβδομάδα, τύπου SQLINTEGER
.TOTAL
: Πωλήσεις σε κιλά συνολικά, τύπου SQLINTEGER
.
Ο δεύτερος πίνακας της βάσης,
SUPPLIERS
, αποθηκεύει πληροφορίες προμηθευτών:
SUP_ID SUP_NAME STREET CITY STATE ZIP 101 Acme, Inc. 99 Market Street Groundsville CA 95199 49 Superior Coffee 1 Party Place Mendocino CA 95460 150 The High Ground 100 Coffee Lane Meadows CA 93966 Περιγραφή των στηλών του πίνακα
SUPPLIERS
:
SUP_ID
: Κωδικός προμηθευτή, τύπου SQLINTEGER
. Ορίζεται ως κύριο κλειδί του πίνακα.SUP_NAME
,STREET
,CITY
,STATE
, andZIP
: Όνομα και διεύθυνση προμηθευτή.Δημιουργία Πινάκων
Δημιουργία με Apache Ant
Μετακινηθείτε στο κατάλογο
<JDBC tutorial directory>
και εκτελέστε την εντολή:
ant create-tablesΗ εντολή εκτελεί το παρακάτω σενάριο Ant (από το αρχείο
build.xml
):<target name="build-tables" description="Create database tables">
<sql
driver="${DB.DRIVER}"
url="${DB.URL}"
userid="${DB.USER}"
password="${DB.PASSWORD}"
classpathref="CLASSPATH"
delimiter="${DB.DELIMITER}" autocommit="false" onerror="abort">
<transaction src="./sql/${DB.VENDOR}/create-tables.sql"/>
</sql>
</target>Το σενάριο Ant ορίζει τις παραμέτρους
sql
, όπως έχουν οριστεί στο αρχείοproperties/mysql-build-properties.xml
με επιπλέον τις:
Παράμετρος
Περιγραφή autocommit
Boolean; στη τιμή false
, όλες οι εντολές SQL εκτελούνται σε μια συναλλαγή.onerror
Ενέργεια σε περίπτωση σφάλματος.Επιτρεπόμενες τιμές: continue
,stop
, καιabort
.Η εισαγωγή των παραμέτρων στο αρχείο
build.xml
γίνεται με την εντολήimport
:<import file="${ANTPROPERTIES}"/>Το τμήμα
transaction
ορίζει ένα αρχείο όπου βρίσκονται οι προς εκτέλεση εντολές SQL. Το αρχείοcreate-tables.sql
περιέχει δύο εντολές SQL για τη δημιουργία πινάκωνSUPPLIERS
καιCOFFEES
:create table SUPPLIERS
(SUP_ID integer NOT NULL,
SUP_NAME varchar(40) NOT NULL,
STREET varchar(40) NOT NULL,
CITY varchar(20) NOT NULL,
STATE char(2) NOT NULL,
ZIP char(5),
PRIMARY KEY (SUP_ID));
create table COFFEES
(COF_NAME varchar(32) NOT NULL,
SUP_ID int NOT NULL,
PRICE numeric(10,2) NOT NULL,
SALES integer NOT NULL,
TOTAL integer NOT NULL,
PRIMARY KEY (COF_NAME),
FOREIGN KEY (SUP_ID) REFERENCES SUPPLIERS (SUP_ID));Σημείωση: Το αρχείο
build.xml
περιέχει και ένα σενάριο Ant με όνομαdrop-tables
που διαγράφει τους πίνακεςSUPPLIERS
καιCOFFEES
. Το εκτελούμε αν θέλουμε να διαγράψουμε τους πίνακες.
Δημιουργία πινάκων με JDBC API
Η μέθοδος
SuppliersTable.createTable
δημιουργεί το πίνακαSUPPLIERS
:public void createTable() throws SQLException {
String createString = "create table " + dbName + ".SUPPLIERS " +
"(SUP_ID integer NOT NULL, " +
"SUP_NAME varchar(40) NOT NULL, " +
"STREET varchar(40) NOT NULL, " +
"CITY varchar(20) NOT NULL, " +
"STATE char(2) NOT NULL, " +
"ZIP char(5), " +
"PRIMARY KEY (SUP_ID))";
Statement stmt = null;
try {
stmt = con.createStatement();
stmt.executeUpdate(createString);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}Η μέθοδος
CoffeesTable.createTable
δημιουργεί το πίνακαCOFFEES
:public void createTable() throws SQLException {
String createString = "create table " + dbName + ".COFFEES " +
"(COF_NAME varchar(32) NOT NULL, " +
"SUP_ID int NOT NULL, " +
"PRICE float NOT NULL, " +
"SALES integer NOT NULL, " +
"TOTAL integer NOT NULL, " +
"PRIMARY KEY (COF_NAME), " +
"FOREIGN KEY (SUP_ID) REFERENCES " + dbName + ".SUPPLIERS (SUP_ID))";
Statement stmt = null;
try {
stmt = con.createStatement();
stmt.executeUpdate(createString);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}Και στις δυο μεθόδους, το
con
είναι αντικείμενοConnection
και τοdbName
είναι το όνομα της βάσης όπου δημιουργούμε το πίνακα.Για να εκτελέσουμε την εντολή SQL, που περιέχεται στο
String
createString
, χρησιμοποιόυμε ένα αντικείμενοStatement
. Η δημιουργία του αντικειμένουStatement
γίνεται με κλήση της μεθόδουConnection.createStatement
μέσα από ένα υπάρχον αντικείμενοConnection
. Η εκτέλεση της εντολής SQL γίνεται μέσω της μεθόδουStatement.executeUpdate
.Όλα τα αντικείμενα
Statement
κλείνουν μαζί με τη σύνδεση που τα δημιούργησε. Παρ' όλα αυτά είναι καλή τακτική να κλείνουμε ρητά τα αντικείμεναStatement
μόλις ολοκληρώσουμε τη χρήση τους. Με αυτό το τρόπο αποδεσμεύονται άμεσα οι πόροι του συστήματος. Η εντολή κλείνει με τη μέθοδοStatement.close
. Θέτουμε τη κλήση σε δομήfinally
για να βεβαιωθούμε οτι η εντολή θα κλείσει ακόμη και αν η κανονική ροή του προγράμματος διακοπεί από μια εξαίρεση (όπως ηSQLException
).Σημείωση: Ο πίνακας
SUPPLIERS
πρέπει να δημιουργηθεί πριν το πίνακαCOFFEES
γιατί οCOFFEES
περιέχει το ξένο κλειδίSUP_ID
που αναφέρεται στοSUPPLIERS
.Εισαωγή Δεδομένων
Εισαγωγή Δεδομένων με Apache Ant
Για την εισαγωγή δεδομένων στους πίνακες
SUPPLIERS
καιCOFFEES
μετακινούμαστε στο κατάλογο<JDBC tutorial directory>
και στη προτροπή εισάγουμε την εντολή:
ant populate-tables
Το σενάριο Ant target εκτελεί τις εντολές SQL του αρχείου
populate-tables.sql
:insert into SUPPLIERS values(49, 'Superior Coffee', '1 Party Place', 'Mendocino', 'CA', '95460');
insert into SUPPLIERS values(101, 'Acme, Inc.', '99 Market Street', 'Groundsville', 'CA', '95199');
insert into SUPPLIERS values(150, 'The High Ground', '100 Coffee Lane', 'Meadows', 'CA', '93966');
insert into COFFEES values('Colombian', 00101, 7.99, 0, 0);
insert into COFFEES values('French_Roast', 00049, 8.99, 0, 0);
insert into COFFEES values('Espresso', 00150, 9.99, 0, 0);
insert into COFFEES values('Colombian_Decaf', 00101, 8.99, 0, 0);
insert into COFFEES values('French_Roast_Decaf', 00049, 9.99, 0, 0);Εισαγωγή Δεδομένων με JDBC API
Η μέθοδος
SuppliersTable.populateTable
εισάγει στοιχεία στο πίνακαSUPPLIERS
:public void populateTable() throws SQLException {
Statement stmt = null;
try {
stmt = con.createStatement();
stmt.executeUpdate("insert into " + dbName + ".SUPPLIERS " +
"values(49, 'Superior Coffee', '1 Party Place', " +
"'Mendocino', 'CA', '95460')");
stmt.executeUpdate("insert into " + dbName + ".SUPPLIERS " +
"values(101, 'Acme, Inc.', '99 Market Street', " +
"'Groundsville', 'CA', '95199')");
stmt.executeUpdate("insert into " + dbName + ".SUPPLIERS " +
"values(150, 'The High Ground', '100 Coffee Lane', " +
"'Meadows', 'CA', '93966')");
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}Η μέθοδος
CoffeesTable.populateTable
, εισάγει στοιχεία στο πίνακαCOFFEES
:public void populateTable() throws SQLException {
Statement stmt = null;
try {
stmt = con.createStatement();
stmt.executeUpdate("insert into " + dbName + ".COFFEES " +
"values('Colombian', 00101, 7.99, 0, 0)");
stmt.executeUpdate("insert into " + dbName + ".COFFEES " +
"values('French_Roast', 00049, 8.99, 0, 0)");
stmt.executeUpdate("insert into " + dbName + ".COFFEES " +
"values('Espresso', 00150, 9.99, 0, 0)");
stmt.executeUpdate("insert into " + dbName + ".COFFEES " +
"values('Colombian_Decaf', 00101, 8.99, 0, 0)");
stmt.executeUpdate("insert into " + dbName + ".COFFEES " +
"values('French_Roast_Decaf', 00049, 9.99, 0, 0)");
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}
Η μέθοδος CoffeesTable.viewTable
εμφανίζει τα περιεχόμενα του πίνακα COFFEES
, και δείχνει
τη χρήση των αντικειμένων ResultSet
και των δρομέων:
public static void viewTable(Connection con) throws SQLException {
Statement stmt = null;
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from " + dbName + ".COFFEES";
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + "\t" + supplierID + "\t" + price + "\t" + sales + "\t" + total);
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}
Ένα αντικείμενο ResultSet
είνα ένας πίνακας δεδομένων που περιέχει το αποτέλεσμα της εκτέλεσης
του αντίστοιχου ερωτήματος SQL προς τη βάση. Για παράδειγμα, η μέθοδος CoffeeTables.viewTable
δημιουργεί το αντικείμενο ResultSet
rs
,
όταν εκτελεί την εντολή SQL μέσω του αντικειμένου Statement
stmt
. Σημειώστε οτι ένα αντικείμενο ResultSet
μπορεί να δημιουργηθεί μέσω οποιουδήποτε αντικειμένου υλοποιεί τη
διεπιφάνεια Statement
, όπως PreparedStatement
,
CallableStatement
και RowSet
.
Η προσπελαση στα δεδομένα του ResultSet
επιτυγχάνεται με ένα δρομέα. Σημειώστε οτι αυτός ο δρομέας δεν είναι
ίδιος με αυτόν της βάσης δεδομένων. Ο δρομέας αυτός δείχνει σε μια
γραμμή δεδομένων του ResultSet
. Αρχικά ο δρομέας δείχνει
στη πρώτη γραμμή. Η μέθοδος ResultSet.next
μετακινεί το
δρομέα στην επόμενη γραμμή. Η μέθοδος επιστρέφει false
αν
ο δρομέας ξεπεράσει τη τελευταία γραμμή. Στο παραπάνω πρόγραμμα η
ResultSet.next
καλείται μέσα σε ένα βρόχο while
ώστε να προσπελάσει όλες τις γραμμές του ResultSet
.
Η διεπιφάνεια ResultSet
παρέχει μεθόδους διαφορετικής λειτουργικότητας που επενεργούν στα
δεδομένα των αντικειμένων ResultSet
.
TYPE_FORWARD_ONLY
: Επιτρέπεται η κίνηση του δρομέα
μόνο από τη πρώτη προς τη τελευταία γραμμή.TYPE_SCROLL_INSENSITIVE
: Επιτρέπεται κίνηση του
δρομέα και προς τις δύο κατευθύνσεις. Τα δεδομένα δεν ενημερώνονται σε
αλλαγές της βάσης. TYPE_SCROLL_SENSITIVE
: Επιτρέπεται κίνηση του δρομέα
και προς τις δύο κατευθύνσεις. Τα δεδομένα ενημερώνονται σε αλλαγές της
βάσης που συμβαίνουν όσο το αντικείμενο είναι σε λειτουργία. Προεπιλεγμένο: TYPE_FORWARD_ONLY
.
CONCUR_READ_ONLY
: Το αντικείμενο ResultSet
δε μπορεί να ενημερωθεί με τη διεπιφάνεια ResultSet
.CONCUR_UPDATABLE
: Το αντικείμενο ResultSet
μπορεί να ενημερωθεί με τη διεπιφάνεια ResultSet
.Προεπιλεγμένο: CONCUR_READ_ONLY
.
Η μέθοδος CoffeesTable.modifyPrices
που σχολιάζεται παρακάτω επιδεινύει τη χρήση ενός ResultSet
με CONCUR_UPDATABLE
.
Η κλήση της μεθόδου Connection.commit
μπορεί να κλείσει τα αντικείμενα ResultSet
που
δημιουργούνται σε μια συναλλαγή. Η ιδιότητα της διεπιφάνειας ResultSet
holdability
δίνει στην εφαρμογή τη δυνατότητα επιλογής για το αν ένα αντικείμενο ResultSet
θα κλείσει και ο δρομέας του θα χαθεί.
Οι παρακάτω σταθερές ResultSet
μπορούν να περάσουν στις μεθόδους Connection
(createStatement
,
prepareStatement
, και prepareCall
):
HOLD_CURSORS_OVER_COMMIT
: ο δρομέας ResultSet
κρατείται ανοικτός (holdable) όταν καλείται η μέθοδος commit
.
Συνήθως
χρησιμοποιούνται
σε
read-only
ResultSet
αντικείμενα.CLOSE_CURSORS_AT_COMMIT
: ο δρομέας ResultSet
κλείνει όταν καλείται η μέθδοςο commit
. Η προεπιλεγμένη τιμή εξαρτάται από το
DBMS. Η μέθοδος JDBCTutorialUtilities.cursorHoldabilitySupport
,
εμφανίζει
τη
προεπιλεγμένη
τιμή
για
τα
αντικείμενα
ResultSet
και διευκρινίζει να υποστηρίζονται οι επιλογές HOLD_CURSORS_OVER_COMMIT
και CLOSE_CURSORS_AT_COMMIT
:
public static void cursorHoldabilitySupport(Connection conn) throws SQLException {
DatabaseMetaData dbMetaData = conn.getMetaData();
System.out.println("ResultSet.HOLD_CURSORS_OVER_COMMIT = " + ResultSet.HOLD_CURSORS_OVER_COMMIT);
System.out.println("ResultSet.CLOSE_CURSORS_AT_COMMIT = " + ResultSet.CLOSE_CURSORS_AT_COMMIT);
System.out.println("Default cursor holdability: " +
dbMetaData.getResultSetHoldability());
System.out.println("Supports HOLD_CURSORS_OVER_COMMIT? " +
dbMetaData.supportsResultSetHoldability(
ResultSet.HOLD_CURSORS_OVER_COMMIT));
System.out.println("Supports CLOSE_CURSORS_AT_COMMIT? " +
dbMetaData.supportsResultSetHoldability(
ResultSet.CLOSE_CURSORS_AT_COMMIT));
}
Η διεπιφάνεια ResultSet
ορίζει μεθόδους (για παράδειγμα getBoolean
και getLong
)
για
την
ανάκτηση
τιμών
από
τη
τρέχουσα
γραμμή.
Οι
στήλες
(πεδία)
μπορούν να
προσπελαστούν είτε με το όνομά τους (όπως στη μέθοδο που ήδη
παρουσιάστηκε) ή με αριθμό στήλης. Ο αριθμός στήλης συνήθως είναι πιο
αποδοτικός. Η αρίθμηση ξεκινά από το 1. Για λόγους φορητότητας, οι
στήλες είναι καλό να διαβάζονται από αριστερά προς τα δεξιά και μόνο
μια φορά.
Η μέθοδος CoffeesTable.alternateViewTable
χρησιμοποιεί αριθμούς στηλών:
public static void alternateViewTable(Connection con) throws SQLException {
Statement stmt = null;
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString(1);
int supplierID = rs.getInt(2);
float price = rs.getFloat(3);
int sales = rs.getInt(4);
int total = rs.getInt(5);
System.out.println(coffeeName + "\t" + supplierID + "\t" + price + "\t" + sales + "\t" + total);
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}
Τα ονόματα των στηλών είναι
case-insensitive. Αν πολλές στήλες έχουν το ίδιο όνομα τότε
επιστρέφεται η πρώτη ταύτιση. Γενικά, αν τα ονόματα δεν επιβάλονται από την εφαρμογή,
είναι καλύτερα να χρησιμοποιούμε αριθμούς. Αν απαιτείται χρήση ονομάτων
η φράση AS
της SQL μπορεί να λύσει πιθανές
αμφισημίες.
Υπαρρχουν μέθοδοι ανάκτησης τιμών για
όλους του τύπους δεδομένων SQL. Επίσης, η μέθδος getString
είναι ιδιαίτερα χρήσιμη αφού μπορεί να χρησιμοποιηθεί για την ανάκτηση
χαρακτήρων, strings αλλά και αριθμητικών τιμών, τις οποίες μετατρέπει
αυτόματα σε αντικείμενα String
της Java.
Παρακάτω αναφέρονται μέθοδοι που
σχετίζονται με το χειρισμός του δρομεά για το συγκεκριμένο αντικείμενο ResultSet
:
next
: Κίνηση του δρομέα στην επόμενη γραμμή.
Επιστρέφει true
αν ο δρομέας μετακινηθεί στην επόμενη
γραμμή και false
αν ο δρομέας περάσει τη τελευταία γραμμή.previous
: Κίνηση του δρομέα στην προηγούμενη γραμμή.
Επιστρέφει true
αν ο δρομέας μετακινηθεί στην προηγούμενη
γραμμή και false
αν ο δρομέας περάσει τη πρώτη γραμμή.first
: Κίνηση του δρομέα στην πρώτη γραμμή.
Επιστρέφει true
αν ο δρομέας μετακινηθεί στην πρώτη
γραμμή και false
το αντικείμενο δεν έχει καμμία γραμμή. last:
: Κίνηση του δρομέα στην τελευταία γραμμή.
Επιστρέφει true
αν ο δρομέας μετακινηθεί στην τελευταία
γραμμή και false
το αντικείμενο δεν έχει καμμία γραμμή.beforeFirst
: Μετακινεί το δρομέα πριν τη πρώτη
γραμμή. Αν το αντικείμενο δεν έχει γραμμές τότε δε συμβαίνει τίποτε.afterLast
: Μετακινεί το δρομέα μετά τη τελευταία
γραμμή. Αν το αντικείμενο δεν έχει γραμμές τότε δε συμβαίνει τίποτε.relative(int rows)
: Μετακινεί το δρομέα σχετικά με
τη τρέχουσα θέση του.absolute(int row)
: Μετακινεί το δρομέα σε απόλυτο
αριθμό γραμμής.
Η μέθοδος CoffeesTable.modifyPrices
,
που
παρουσιάζεται
παρακάτω,
δείχνει
τη
χρήση
μερικών
μεθόδων
χειρισμού
δρομέα.
Στις προεπιλεγμένες (default) συνθήκες,
ένα αντικείμενο ResultSet
, δεν τροποποιείται και ο
δρομέας του προχωρά μόνο μπροστά. Όμως, όπως ειδαμε, μπορούμε να
δημιουργήσουμε αντικείμενα ResultSet
που μπορούν να
προσπελαστούν τυχαία και να τροποποιηθούν.
Η μέθοδος CoffeesTable.modifyPrices
πολλαπλασιάζει τη στήλη PRICE
κάθε γραμμής με το όρισμα percentage
:
public void modifyPrices(float percentage) throws SQLException {
Statement stmt = null;
try {
stmt = con.createStatement();
stmt = con.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet uprs = stmt.executeQuery(
"SELECT * FROM " + dbName + ".COFFEES");
while (uprs.next()) {
float f = uprs.getFloat("PRICE");
uprs.updateFloat("PRICE", f * percentage);
uprs.updateRow();
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}
Οι επιλογές ResultSet.TYPE_SCROLL_SENSITIVE
και ResultSet.CONCUR_UPDATABLE
έχουν εξηγηθεί παραπάνω. Στο
ResultSet
Javadoc μπορείτε να δείτε και άλλες επιλογές που επηρεάζουν τη
συμπεριφορά των αντικειμένων ResultSet
.
Η μέθοδος ResultSet.updateFloat
ενημερώνει τη στήλη που δείχνει ο δρομέας (στο παράδειγμα, η στήλη PRICE
περιέχει τιμή float
). Υπάρχουν αντίστοιχες μέθοδοι για
άλλους τύπους δεδομένων. Σημειώστε οτι η ενημέρωση της βάσης
πραγματοποιείται μόνο μετά τη κλήση της μεθόδου
ResultSet.updateRow
.
Τα αντικείμενα Statement
, PreparedStatement
και CallableStatement
σχετίζονται με μια λίστα εντολών. Η
λίστα αυτή μπορεί να περιέχει εντολές ενημέρωσης, εισαγωγής ή διαγραφής
γραμμής (πχ INSERT
INTO
, UPDATE
, DELETE
). Επίσης
εντολές όπως CREATE TABLE
, DROP TABLE
, ALTER
TABLE
.
Δε μπορεί όμως να περιέχει εντολές που δημιουργούν ένα νέο αντικείμενο ResultSet
(SQL queries).
Η λίστα εντολών του αντικειμένου Statement
είναι αρχικά κενή. Προσθέτουμε εντολές με τη μέθοδο addBatch
και αδειάζουμε τη λίστα με τη μέθοδο clearBatch
. Όταν
προσθέσουμε όλες τις εντολές στη λίστα, καλούμε τη μέθοδο executeBatch
που εκτελεί τις εντολές ως μια δέσμη.
Η μέθοδος CoffeesTable.batchUpdate
προσθέτει τέσσερις γραμμές στο πίνακα COFFEES
με δέσμη
εντολών:
public void batchUpdate() throws SQLException {
Statement stmt = null;
try {
this.con.setAutoCommit(false);
stmt = this.con.createStatement();
stmt.addBatch("INSERT INTO COFFEES " +
"VALUES('Amaretto', 49, 9.99, 0, 0)");
stmt.addBatch("INSERT INTO COFFEES " +
"VALUES('Hazelnut', 49, 9.99, 0, 0)");
stmt.addBatch("INSERT INTO COFFEES " +
"VALUES('Amaretto_decaf', 49, 10.99, 0, 0)");
stmt.addBatch("INSERT INTO COFFEES " +
"VALUES('Hazelnut_decaf', 49, 10.99, 0, 0)");
int [] updateCounts = stmt.executeBatch();
this.con.commit();
} catch(BatchUpdateException b) {
JDBCTutorialUtilities.printBatchUpdateException(b);
} catch(SQLException ex) {
JDBCTutorialUtilities.printSQLException(ex);
} finally {
this.con.setAutoCommit(true);
stmt.close();
}
}
Στις ενημερώσεις δέσμης, για σωστή
διαχείριση σφαλμάτων, πρέπει να αναστέλεται το auto-commit
mode για το αντικείμενο Connection
. Η παρακάτω γραμμή
πραγματοποιεί αυτή την αναστολή:
this.con.setAutoCommit(false);
Η μέθοδος Statement.addBatch
προσθέτει εντολές στη λίστα που σχετίζεται με το αντικείμενο Statement
stmt
. Στο παράδειγμα, οι εντολές είναι όλες INSERT
INTO
, όπου κάθε μια προσθέτει μια γραμμή με πέντε τιμές για τις
στήλες COF_NAME
, SUP_ID, PRICE
, SALES
και TOTAL
.
Η γραμμή που ακολουθεί εκτελεί τις εντολές SQL που έχουν προστεθεί στη λίστα ως δέσμη εντολών:
int [] updateCounts = stmt.executeBatch();
Η μέθοδος executeBatch
εκτελεί ολόκληρη δέσμη εντολών, ενώ η μέθοδος executeUpdate
θα εκτελούσε μόνο μια εντολή από τη λίστα. Οι εντολές εκτελούνται με τη
σειρά εισαγωγής στη λίστα. Σε κάθε επιτυχή ενημέρωση DBMS επιστρέφει
ένα μετρητή που δείχνει πόσες γραμμές τροποποιήθηκαν. Οι μετρητές
αποθηκεύονται στο πίνακα updateCounts
.
Αν όλες οι εντολές εκτελεστούν με
επιτυχία ο πίνακας updateCounts
θα περιέχει 4 τιμές, όλες
ίσες με 1 γιατί κάθε εισαγωγή τροποποιεί μια γραμμή. Η λίστα των
εντολών που σχετίζονται με τη stmt
θα είναι πλέον άδεια
αφού οι 4 ενυολές στάλθηκαν προς εκτέλεση όταν η stmt
κάλεσε τη μέθοδο executeBatch
. Αν θέλουμε μπορούμε να
αδειάσουμε τη λίστα των εντολών με τη μέθοδο clearBatch
.
Η μ'έθοδος Connection.commit
επιβεβαιώνει τις αλλαγές στο πίνακα COFFEES
. Η
μέθοδος αυτή πρέπει να κληθεί ρητά γιατί προηγούμενα είχαμε αναστείλει
το auto-commit mode.
Στο τέλος επαναφέρουμε το auto-commit στο
αντικείμενο Connection
.
this.con.setAutoCommit(true);
Το ίδιο παράδειγμα με παραμετροποιημένη
δέσμη εντολών, όπου con
είναι ένα αντικείμενο Connection
:
con.setAutoCommit(false);
PreparedStatement pstmt = con.prepareStatement(
"INSERT INTO COFFEES VALUES(
?, ?, ?, ?, ?)");
pstmt.setString(1, "Amaretto");
pstmt.setInt(2, 49);
pstmt.setFloat(3, 9.99);
pstmt.setInt(4, 0);
pstmt.setInt(5, 0);
pstmt.addBatch();
pstmt.setString(1, "Hazelnut");
pstmt.setInt(2, 49);
pstmt.setFloat(3, 9.99);
pstmt.setInt(4, 0);
pstmt.setInt(5, 0);
pstmt.addBatch();
// ... and so on for each new type of coffee
int [] updateCounts = pstmt.executeBatch();
con.commit();
con.setAutoCommit(true);
Μια εξαίρεση BatchUpdateException
προκύπτει όταν καλέσουμε μια μέθοδο executeBatch
και (1)
μια από τις εντολές SQL είναι ερώτημα (SQL query) ή (2) μια από
τις SQL εντολές αποτύχει για κάποιο λόγο.
Μια εξαίρεση BatchUpdateException
περιέχει ένα πίνακα μετρητών όμοιο με αυτό της μεθόδου executeBatch
.
Έτσι
φαίνεται
πόσες
εντολές
εκτελέστηκαν
με
επιτυχία
και
σε ποιά εντολή
παρουσιάστηκε πρόβλημα.
Η κλάση BatchUpdateException
παράγεται από την SQLException
, επομένως μπορεί να
χρησιμοποιήσει τις μεθόδους που διατίθενται σε ένα αντικείμενο SQLException
.
Η
μέθοδος
JDBCTutorialUtilities.printBatchUpdateException
εμφανίζει όλες τις πληροφορίες ενός αντικειμένου SQLException
και το πίνακα μετρητών του αντικειμένου BatchUpdateException
:
public static void printBatchUpdateException(BatchUpdateException b) {
System.err.println("----BatchUpdateException----");
System.err.println("SQLState: " + b.getSQLState());
System.err.println("Message: " + b.getMessage());
System.err.println("Vendor: " + b.getErrorCode());
System.err.print("Update counts: ");
int [] updateCounts = b.getUpdateCounts();
for (int i = 0; i < updateCounts.length; i++) {
System.err.print(updateCounts[i] + " ");
}
}
Σημείωση: Δεν
υποστηρίζεται από όλους τους JDBC
drivers. Μπορεί να προκαλέσει εξαίρεση SQLFeatureNotSupportedException
.
Η μέθοδος CoffeesTable.insertRow
εισάγει μια γραμμή στο πίνακα COFFEES
μέσω ενός
αντικειμένου ResultSet
:
public void insertRow(String coffeeName, int supplierID, float price, int sales, int total)
throws SQLException {
Statement stmt = null;
try {
stmt = con.createStatement();
stmt = con.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet uprs = stmt.executeQuery(
"SELECT * FROM " + dbName + ".COFFEES");
uprs.moveToInsertRow();
uprs.updateString("COF_NAME", coffeeName);
uprs.updateInt("SUP_ID", supplierID);
uprs.updateFloat("PRICE", price);
uprs.updateInt("SALES", sales);
uprs.updateInt("TOTAL", total);
uprs.insertRow();
uprs.beforeFirst();
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}
Η μέθοδος ResultSet.moveToInsertRow
μετακινεί το δρομέα σε ένα ειδικό buffer
όπου εισάγονται τα δεδομένα της νέας γραμμής με χρήση των μεθόδων
ενημέρωσης. Για παράδειγμα, η μέθοδος ResultSet.updateString
ενημερώνει τη στήλη COF_NAME
με τη τιμή της παραμέτρου cofeeName
.
Η μέθοδος ResultSet.insertRow
εισάγει τη νέα γραμμή στο αντικείμενο ResultSet
και στη
βάση δεδομένων.
Ένωση είναι μια λειτουργία βάσεων δεδομένων που συσχετίζει δεδμένα δύο ή περισσότερων πινάκων με βάση κοινά κλειδιά. Στο παράδειγμά μας, οι πίνακες
COFFEES
καιSUPPLIERS
έχουν κοινό κλειδί τη στήληSUP_ID
. Για να ξεχωρίσουμε σε ποιά στήληSUP_ID
αναφερόμαστε βάζουμε μπροστά το όνομα του πίνακα, δηλαδήCOFFEES.SUP_ID
ήSUPPLIERS.SUP_ID
.
Η μέθοδος
JoinSample.getCoffeesBoughtBySupplier
επιλέγει τους καφέδες που προμηθεύει συγκεκριμένος προμηθευτής:public static void getCoffeesBoughtBySupplier (String supplierName, Connection con, String dbName)
throws SQLException {
Statement stmt = null;
String query = "SELECT COFFEES.COF_NAME " +
"FROM COFFEES, SUPPLIERS " +
"WHERE SUPPLIERS.SUP_NAME LIKE '" + supplierName + "' " +
"and SUPPLIERS.SUP_ID = COFFEES.SUP_ID";
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
System.out.println("Coffees bought from " + supplierName + ": ");
while (rs.next()) {
String coffeeName = rs.getString(1);
System.out.println(" " + coffeeName);
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}Αν η μέθοδος κληθεί με όρισμα
supplierName
ίσο μεAcme, Inc.
, θα παράγει το παρακάτω αποτέλεσμα:Coffees bought from Acme, Inc.:
Colombian
Colombian_DecafΗ ακόλουθη γραμμή ανακτά τη τιμή της πρώτης στήλης του αντικειμένου
ResultSet
:String coffeeName = rs.getString(1
);Σημειώστε οτι το αντικείμενο
ResultSet
στο παράδειγμα έχει μόνο μια στήλη.
Η κλάση
PreparedStatement
είναι παράγωγη της γενικότερης κλάσηςStatement
, που έχουμε ήδη συναντήσει. Αν πρέπει να εκτελέσουμε ένα αντικείμενοStatement
πολλές φορές, η χρήση ενός αντικειμένουPreparedStatement
βελτιώνει την απόδοση.Το κύριο χαρακτηριστικό ενός αντικειμένου
PreparedStatement
είναι, οτι αντίθετα με ένα αντικείμενοStatement
, εκτελεί συγκεκριμένη precompiled εντολή SQL. Έτσι η εντολή SQL στέλνεται απ'εθυείας στο DBMS για εκτέλεση. Τα αντικείμεναPreparedStatement
χρησιμοποιούνται τόσο για εντολές SQL χωρίς παραμέτρους όσο και για εντολές με παραμέτρους.
Η μέθοδος
CoffeesTable.updateCoffeeSales
ενημερώνει τη ποσότητα καφέ που πωλήθηκε τη τρέχουσα εβδομάδα (στήληSALES
) και ενημερώνει το σύνολο της ποσότητας (στήληTOTAL
) για κάθε τύπο καφέ:public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException {
PreparedStatement updateSales = null;
PreparedStatement updateTotal = null;
String updateString = "update " + dbName + ".COFFEES " +
"set SALES = ? where COF_NAME = ?";
String updateStatement = "update " + dbName + ".COFFEES " +
"set TOTAL = TOTAL + ? where COF_NAME = ?";
try {
con.setAutoCommit(false);
updateSales = con.prepareStatement(updateString);
updateTotal = con.prepareStatement(updateStatement);
for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
updateSales.setInt(1, e.getValue().intValue());
updateSales.setString(2, e.getKey());
updateSales.executeUpdate();
updateTotal.setInt(1, e.getValue().intValue());
updateTotal.setString(2, e.getKey());
updateTotal.executeUpdate();
con.commit();
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
if (con != null) {
try {
System.err.print("Transaction is being rolled back");
con.rollback();
} catch(SQLException excep) {
JDBCTutorialUtilities.printSQLException(excep);
}
}
} finally {
updateSales.close();
updateTotal.close();
con.setAutoCommit(true);
}
}Δημιουργία Προετοιμασμένων Εντολών
Οι παρακάτω γραμμές δημιουργούν ένα αντικείμενο
PreparedStatement
με δύο παραμέτρους (ορίζονται με ερωτηματικά):String updateString = "update " + dbName + ".COFFEES " +
"set SALES = ? where COF_NAME = ?";
updateSales = con.prepareStatement(updateString);Ανάθεση Τιμών
Πριν την εκτέλεση του αντικειμένου
PreparedStatement
πρέπει να δώσουμε τιμές στις παραμέτρους. Γι' αυτό το λόγο η κλάσηPreparedStatement
παρέχει μεθόδους καθορισμού τιμών παραμέτρων, όπως οι παρακάτω που δίνουν τιμές στις παραμέτρους του αντικειμένουupdateSales
:updateSales.setInt(1, e.getValue().intValue());
updateSales.setString(2, e.getKey());Στο παράδειγμα, η μέθοδος
setInt
καθορίζει τη τιμή της πρώτης παραμέτρου (γι' αυτό και το όρισμα 1) ενώ η μέθοδοςsetString
δίνει τιμή στη δεύτερη παράμετρο (όρισμα 2).Μια παράμευτος κρατά τη τιμή της μέχρι να της αποδωθεί νέα τιμή ή να κληθεί η μέθοδος
clearParameters
. Για παράδειγμα στο κώδικα που ακολουθεί το αντικείμενοPreparedStatement
updateSales
εκτελείται δύο φορές. Τη δεύτερη φορά τροποποιούμε μόνο τη δεύτερη παράμετρο:// changes SALES column of French Roast row to 100
updateSales.setInt(1, 100);
updateSales.setString(2, "French_Roast");
updateSales.executeUpdate();
// changes SALES column of Espresso row to 100 (the first
// parameter stayed 100, and the second parameter was reset
// to "Espresso")
updateSales.setString(2, "Espresso");
updateSales.executeUpdate();Επαναληπτκή Ανάθεση Τιμών
Η ανάθεση τιμών στις παραμέτρους και η εκτέλεση των προετοιμασμένων εντολών πολλές φορές είναι ευχερέστερη με τη χρήση επαναλήψεων
for
ήwhile
.Στο παράδειγμα, η μέθοδος
CoffeesTable.updateCoffeeSales
χρησιμοποιεί βρόχο for-each για την ανάθεση τιμών στις παραμέτρους και την εκτέλεση τωνPreparedStatement
αντικειμένωνupdateSales
καιupdateTotal
:for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
updateSales.setInt(1, e.getValue().intValue());
updateSales.setString(2, e.getKey());
// ...
}Η μέθοδος
CoffeesTable.updateCoffeeSales
δέχεται ως όριμσμα έναHashMap
. Κάθε στοιχείο τουHashMap
περιέχει ένα όνομα καφέ και τη ποσότητα που πωλήθηκε τη τρέχουσα εβδομάδα. Ο βρόχος for-each loop διατρέχει τα στοιχεία τουHashMap
και αναθέτει τις τιμές τους στις παραμέτρους των αντικειμένωνupdateSales
καιupdateTotal
.Εκτέλεση Προετοιμασμένων Εντολών
Όπως και με τα αντικείμενα της κλάσης
Statement
, για να εκτελέσουμε ένα αντικείμενο της κλάσηςPreparedStatement
, καλούμε την αντίστοιχη μέθοδο εκτέλεσης:executeQuery
αν η εντολή επιστρέφει μόνο ένα αντικείμενοResultSet
(όπως μια εντολή SQLSELECT
),executeUpdate
αν η εντολή δεν επιστρέφει αντικείμενοResultSet
(όπως μια εντολή SQLUPDATE
), ήexecute
αν η εντολή επιστρέφει πολλαπλά αντικείμεναResultSet
. Και τα δύο αντικείμεναPreparedStatement
στη μέθοδοCoffeesTable.updateCoffeeSales
περιέχουν εντολές SQLUPDATE
, επομένως εκτελούνται με κλήση μεθόδωνexecuteUpdate
:updateSales.setInt(1, e.getValue().intValue());
updateSales.setString(2, e.getKey());
updateSales.executeUpdate();
updateTotal.setInt(1, e.getValue().intValue());
updateTotal.setString(2, e.getKey());
updateTotal.executeUpdate();
con.commit();Δεν απαιτούνται ορίσματα στις μεθόδους
executeUpdate
όταν εκτελούν τις εντολέςupdateSales
καιupdateTotals
αφού και τα δύοPreparedStatement
αντικείμενα περιέχουν τις προετοιμασμένες SQL εντολές με τις παραμέτρους τους.Σημείωση: Στην αρχή του κώδικα της μεθόδου
CoffeesTable.updateCoffeeSales
, ο auto-commit mode αναιρείται:con.setAutoCommit(false);Επομένως, οι εντολές SQL δεν οριστικοποιούνται μέχρι να κληθεί μια μέθδος
commit
, στο τέλος του παραπάνω κώδικα. Περισσότερα στη συζήτηση των συναλλαγών.
Τιμές Επιστροφής της executeUpdate
Ενώ η
executeQuery
επιστρέφει ένα αντικείμενοResultSet
που περιέχει τα αποτελέσματα της εκτέλεσης της εντολής SQL στο DBMS, η τιμή επιστροφής τηεexecuteUpdate
είναι μια τιμήint
που δείχνει πόσες γραμμές του πίνακα ενημερώθηκαν. Για παράδειγμα, στο κώδικα που ακολουθεί δείχνει η τιμή επιστροφής τηςexecuteUpdate
ανατίθεται στη μεταβλητήn
:updateSales.setInt(1, 50);
updateSales.setString(2, "Espresso");
int n = updateSales.executeUpdate();
// n = 1 because one row had a change in itΟ πίνακας
COFFEES
ενημερώνεται. Η τιμή 50 αντικαθιστά την υπάρχουσα τιμή στη στήληSALES
της γραμμήςEspresso
. Η ενημέρωση τροποποεί μόνο μια γραμμής του πίνακα, άρα τοn
είναι ίσο με 1.Όταν η μέθοδος
executeUpdate
χρησιμοποιείται για εντολές δημιουργίας, τροποποίησης ή διαγραφής πίνακα, επιστρέφει τηνint
τιμή 0. Επομένως ο κώδικας που ακολουθεί επιστρέφει στοn
τιμή 0:int n = executeUpdate(createTableCoffees); // n = 0
Στο προηγούμενο παράδειγμα ενημερώνουμε τις τιμές σε δύο πίνακες: στον ένα κρατώνται ποσότητες καφέ που πουλήθηκαν ανά βδομάδα, ενώ στον άλλο οι ποσότητες καφέ που πωλήθηκαν συνολικά. Η ενημέρωση μόνο ενός από τους δύο πίνακες θα δημιουργούσε ασυνέπεια δεδομένων (data inconsistency). Οι συναλλαγές χρησιμοποιούνται για τη διασφάλιση της συνέπειας δεδομένων (data consistency). Μια συναλλαγή είναι ένα σύνολο δύο ή περισσότερων εντολών SQL που εκτελούνται σαν μια εντολή, έτσι ή εκτελούνται όλες οι εντολές ή καμμία.
Ακύρωση Auto-Commit Mode
Όταν δημιουργείται μια σύνδεση, βρίσκεται σε auto-commit mode. Αυτό σημαίνει οτι κάθε εντολή SQL εντιμετωπίζεται ως αυτοδύναμη συναλλαγή και οριστικοποιείται (committed) αμέσως μόλις εκτελεστεί.
Για να ομαδοποιήσουμε δύο ή περισσότερες εντολές σε μια συνολική συναλλαγή πρέπει να ακυρώσουμε το auto-commit mode. Για μια σύνδεση
con
η ακύρωση γίνεται ως εξής:con.setAutoCommit(false);Οριστικοποίηση Συναλλαγών (Commit)
Μετά την ακύρωση του auto-commit mode, δεν οριστικοποιούνται εντολές SQL μέχρι να κληθεί ρητά η μέθοδος
commit
. Όλες οι εντολές που έχουν εκτελεστεί ενδιάμεσα οριστικοποιούνται ως σύνολο. Η μέθοδοςCoffeesTable.updateCoffeeSales
όπου η ενεργή σύνδεση είναι ηcon
, φαίνεται η συναλλαγή:public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException {
PreparedStatement updateSales = null;
PreparedStatement updateTotal = null;
String updateString = "update " + dbName + ".COFFEES " +
"set SALES = ? where COF_NAME = ?";
String updateStatement = "update " + dbName + ".COFFEES " +
"set TOTAL = TOTAL + ? where COF_NAME = ?";
try {
con.setAutoCommit(false);
updateSales = con.prepareStatement(updateString);
updateTotal = con.prepareStatement(updateStatement);
for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
updateSales.setInt(1, e.getValue().intValue());
updateSales.setString(2, e.getKey());
updateSales.executeUpdate();
updateTotal.setInt(1, e.getValue().intValue());
updateTotal.setString(2, e.getKey());
updateTotal.executeUpdate();
con.commit();
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
if (con != null) {
try {
System.err.print("Transaction is being rolled back");
con.rollback();
} catch(SQLException excep) {
JDBCTutorialUtilities.printSQLException(excep);
}
}
} finally {
updateSales.close();
updateTotal.close();
con.setAutoCommit(true);
}
}Ο auto-commit mode ακυρώνεται στην αρχή της δομής
try-cath
, ενώ στο τέλος κάθε επανάληψηςfor-each
το ζεύγος των εντολώνupdateSales
καιupdateTotal
οριστικοποιούνται με τη κλήση της μεθόδουcommit
.Εν τέλει, στη δομή
finally
καλείται η μέθοδοςcon.setAutoCommit(true);
που ενεργοποιεί πάλι το auto-commit mode, ώστε να επανέλθει η σύνδεση στη κανονική της κατάσταση.Συναλλαγές και Ακεραιότητα Δεδομένων
Εκτος από τη συνέπεια δεδομένων, οι συναλλαγές είναι χρσήσιμες και στην ακεραιότητα δεδομένων (data integrity). Για παράδειγμα, ενώ ένας χρήστης εισάγει νέες τιμές στα προιόντα προς πώληση, την ίδια στιγμή ένας άλλος χρήστης παράγει ταμειακά σύνολα για τις πωλήσεις που έχουν πραγματοποιηθεί. Θα πρέπει να είναι καθαρό οτι τα ταμειακά σύνολα θα παραχθούν με τις παλιές τιμές. Επομένως (α) αν οι λειτουργίες εκτελούνται ταυτόχρονα θα πρέπει να δωθεί προτεραιότητα στη δεύτερη ενέργεια και (β) αν για κάποιο λόγο εκτελέστηκε πρώτα η πρώτη ενέργεια θα πρέπει να μπορούμε να επιστρέψουμε στις παλιές τιμές (με
rollback
) ώστε να μη παραχθούν λάθος αποτελέσματα.
Γενικά τέτοιοι τύποι προβλημάτων ανακύπτουν όταν υπάρχει ταυτόχρονη χρήση (ανάγνωση και εγγραφή) δεδομένων από δύο ή περισσότερες μεθόδους. Για την αποφυγή συγκρούσεων (conflicts) κατα τη διάρκεια μιας συναλλαγής, το DBMS χρησιμοποιεί κλειδώματα (locks), που διασφαλίζουν οτι μόνο μια συναλλαγή έχει πρόσβαση σε συγκεκριμένα δεδομένα σε κάθε χρονική στιγμή. Στο auto-commit mode, όπου κάθε εντολή είναι μια συναλλαγή, τα κλειδώματα διαρκούν για μια εντολή. Τα κλειδώματα μένουν σε ισχύ μέχρι μια συναλλαγή να ολοκληρωθεί (committed) ή να ακυρωθεί (rolled back). Συνήθως τα κλειδώματα αναφέρονται σε μια γραμμή.
Συνηθισμένοι τύποι συγκρούσεων:
Dirty read: η συναλλαγή A τροποποιεί δεδομένα μιας γραμμής ενώ η συναλλαγή B χρησιμοποιεί αυτά τα δεδομένα πριν ολοκληρωθεί η συναλλαγή Α.
Non-repeatable read: η συναλλαγή Α διαβάζει δεδομένα μιας γραμμής. Η συναλλαγή Β κατόπιν τροποποιεί τα δεδομένα αυτά ή διαγράφει τη γραμμή. Στη συνέχεια η Α προσπαθεί να ξαναδιαβάσει τα δεδομένα αλλά αυτά έχουν πια αλλάξει η δεν υπάρχουν.
Phantom read: η συναλλαγή Α διαβάζει τα δεδομένα μια γραμμής με βάση μια συνθήκη. Κατόπιν η συναλλαγή Β τροποποιεί τα δεδομένα άλων γραμμών ή εισάγει γραμμή ώστε τώρα και αυτές ικανοποιούν τη συνθήκη. Η Α όταν προσπαθέι να ξαναεφαρμόσει τη συνθήκη βρίσκει περισσότερες γραμμές.
Η συμπεριφορά των κλειδωμάτων στους παραπάνω τύπους συγκρούσεων καθορίζεται από το επίπεδο απομόνωσης (isolation level) της συναλλαγής. Η διεπιφάνεια
Connection
ορίζει πέντε επίπεδα απομόνωσης για το JDBC:
Isolation Level Transactions Dirty Reads Non-Repeatable Reads Phantom Reads TRANSACTION_NONE
Not supported Not applicable Not applicable Not applicable TRANSACTION_READ_COMMITTED
Supported Prevented Allowed Allowed TRANSACTION_READ_UNCOMMITTED
Supported Allowed Allowed Allowed TRANSACTION_REPEATABLE_READ
Supported Prevented Prevented Allowed TRANSACTION_SERIALIZABLE
Supported Prevented Prevented Prevented
Ένκάθε DBMS μπορεί να διαφέρει, ή κάποια επίπ The default transaction isolation level depends on your DBMS. For example, for Java DB, it is
TRANSACTION_READ_COMMITTED
. JDBC allows you to find out what transaction isolation level your DBMS is set to (using theConnection
methodgetTransactionIsolation
) and also allows you to set it to another level (using theConnection
methodsetTransactionIsolation
).Σημείωση: Ένας JDBC driver μπορεί να μην υποστηρίζει όλα τα επίπεδα απομόνωσης. Επίσης κάθε DBMS μπορεί να έχει διαφορετική προεπιλογή. Η διεπιφάνεια
Connection
παρέχει τις μεθόδουςgetTransactionIsolation,
setTransactionIsolation
για να χειριστούμε τα επίπεδα απομόνωσης. Επίσης η μεθοδοςDatabaseMetaData.supportsTransactionIsolationLevel
ελέγχει αν το DBMS υποστηρίζει συγκεκριμένο επίπεδο.
Ορισμός Savepoints και Χρήση Rollback
Η μέθοδος
Connection.setSavepoint
, ορίζει ένα αντικείμενοSavepoint
μέσα στη τρέχουσα συναλλαγή. Η μέθοδοςConnection.rollback
δέχεται ως όρισμα το αντικείμενοSavepoint
.Η μέθοδος
CoffeesTable.modifyPricesByPercentage
αυξάνει τη τιμή ενός καφέ κατά ένα ποσοστό,priceModifier
. Όμως, αν η νέα τιμή είναι μεγαλύτερη από μια καθορισμένη τιμή,maximumPrice
, τότε η αύξηση της τιμής αναιρείται:public void modifyPricesByPercentage(String coffeeName, float priceModifier,
float maximumPrice) throws SQLException {
con.setAutoCommit(false);
Statement getPrice = null;
Statement updatePrice = null;
ResultSet rs = null;
String query =
"SELECT COF_NAME, PRICE FROM COFFEES " + "WHERE COF_NAME = '" +
coffeeName + "'";
try {
Savepoint save1 = con.setSavepoint();
getPrice =
con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
updatePrice = con.createStatement();
if (!getPrice.execute(query)) {
System.out.println("Could not find entry for coffee named " +
coffeeName);
} else {
rs = getPrice.getResultSet();
rs.first();
float oldPrice = rs.getFloat("PRICE");
float newPrice = oldPrice + (oldPrice * priceModifier);
System.out.println("Old price of " + coffeeName + " is " + oldPrice);
System.out.println("New price of " + coffeeName + " is " + newPrice);
System.out.println("Performing update...");
updatePrice.executeUpdate("UPDATE COFFEES SET PRICE = " + newPrice +
" WHERE COF_NAME = '" + coffeeName + "'");
System.out.println("\nCOFFEES table after update:");
CoffeesTable.viewTable(con);
if (newPrice > maximumPrice) {
System.out.println("\nThe new price, " + newPrice +
", is greater than the maximum " + "price, " +
maximumPrice +
". Rolling back the transaction...");
con.rollback(save1);
System.out.println("\nCOFFEES table after rollback:");
CoffeesTable.viewTable(con);
}
con.commit();
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
getPrice.close();
updatePrice.close();
con.setAutoCommit(true);
}
}Στην αρχή η μέθοδος δημιουργεί ένα
Savepoint
:
Savepoint save1 = con.setSavepoint();Η μέθοδος ελέγχει αν η νέα τιμή είναι μεγαλύτερη από τη
maximumPrice
. Αν ναι, τότε ακυρώνει τη συναλλαγή:con.rollback(save1);Επομένως, όταν η μέθοδος οριστικοποιεί τη συναλλαγή με τη κλήση
Connection.commit
, δεν θα περιλάβει τις γραμμές που έχουν υποστεί roll back, ενώ θα προχωρήσει κανονικά με τις υπόλοιπες γραμμές.Ελευθέρωση Savepoints
Η μέθοδος
Connection.releaseSavepoint
αφαιρεί ένα αντικείμενοSavepoint
από τη τρέχουσα συναλλαγή. Αν κατόπιν γίνει προσπάθεια χρήσης του προκαλείταιSQLException
. Τα savepoints της συναλλαγής ελευθερώνονται αυτόματα μόλις η συναλλαγή οριστικοποιηθεί (commit) ή εφαρμοστεί roll back στι σύνολο της συναλλαγής.
Πότε καλούμε rollback
Αν εκτελείτε μια συναλλαγή που περιλαμβάνει πολλές εντολές SQL και εμφανιστεί
SQLException
, η κλήση της μεθόδουrollback
είναι ο ασφαλέστερος τρόπος τερματισμού της συναλλαγής. Η παραγωγήSQLException
μας λέει οτι κάτι πήγε στραβά αλλα δεν μπορεί να μας πεί για το ποιό τμήμα της συναλλαγής ήταν επιτυχές και ποιό όχι. Επομένως, είναι επικίνδυνο να οριστικοποιήσουμε μια τέτοια συναλλαγή, αφού μπορεί να επηρεαστεί η ακεραιότητα των δεδομένων μας. Η κλήση της μεθόδουrollback
μας διασφαλίζει οτι επιστρέφουμε στη τελευταία σίγουρη κατάσταση απί πλευράς ακεραιότητας δεδομένων.Η μέθοδος
CoffeesTable.updateCoffeeSales
που συζητήθηκε παραπάνω περιλαμβάνει και μια δομήcatch
όπου, όταν εμφανιστείSQLException
καλείται η μέθοδοςrollback
.
Αποθηκευμένη διαδικασία ονομάζεται μια ομάδα εντολών SQL που αποτελούν μια λογική ενότητα και εκτελούν μια ολοκληρωμένη λειτουργία. Για παράδειγμα, έστω μια βάση δεδομένων διαχείρισης προσωπικού: τυποποιημένες λειτουργίες όπως διάφορες καταστάσεις, μισθοδοσία, άδειες, προσλήψεις, προαγωγές, απολύσεις κλπ, αποτελούν καλούς υποψηφίους για αποθηκευμένες διαδικασίες. Οι αποθηκευμένες διαδικασίες μπορεί να είναι παραμετρικές και να δέχονται ορίσματα εισόδου/εξόδου.
Αν και οι αποθηκευμένες διαδικασίες υποστηρίζονται από τα περισσότερα DBMSs, υπάρχουν αρκετές διαφοροποιήσεις. Εδώ παρουσιάζονται αποθηκευμένες διαδικασίες για τη MySQL με τη βοήθεια της κλάσης
StoredProcedureMySQLSample
.Αποθηκευμένες Διαδικασίες στη MySQL
Το ακόλουθο SQL script δημιουργεί μια αποθηκευμένη διαδικασία με όνομα
SHOW_SUPPLIERS
που επιστρέφει το όνομα τπυ προμηθευτή για κάθε τύπο καφέ στο πίνακαCOFFEES
:SELECT 'Dropping procedure SHOW_SUPPLIERS' AS ' ';
drop procedure if exists SHOW_SUPPLIERS;
SELECT 'Changing delimiter to pipe' AS ' ';
delimiter |
SELECT 'Creating procedure SHOW_SUPPLIERS' AS ' '|
create procedure SHOW_SUPPLIERS()
begin
select SUPPLIERS.SUP_NAME, COFFEES.COF_NAME
from SUPPLIERS, COFFEES
where SUPPLIERS.SUP_ID = COFFEES.SUP_ID
order by SUP_NAME;
end|
delimiter ;Η εντολή
drop procedure
διαγράψει τυχόν παλιότερη διαδικασία με όνομαSHOW_SUPPLIERS
. Η εντολήdelimiter |
αλλάζει το χαρακτήρα που χωρίζει εντολές από semicolon (;
) σε pipe (|
). Στη MySQL, οι εντολές χωρίζονται με semicolon. Όμως πρέπει να επιλογή άλλος χαρακτηρας τερματισμού για το τέλος της εντολήςcreate procedure
. Εδώ επιλέγεται ο χαρακτήρας pipe (|
). Θα μπορούσατε να επιλέξετε άλλο χαρακτήρα.Η εντολή
create procedure
περιλαμβάνει το όνομα της διαδικασίας, λίστα ορισμάτων που χωρίζονται με κόμμα και τις εντολές SQL μεταξύ των λέξεωνbegin
καιend
. Η αποθηκευμένη διαδικασίαSHOW_SUPPLIERS
δεν έχει ορίσματα; για πληροφορίες σχετικές με τα ορίσματα δείτε το εγχειρίδιο της MySQL.Η κλήση μιας αποθηκευμένης διαδικασίας γίνεται με την εντολή
CALL
:CALL SHOW_SUPPLIERS();Το αποτέλεσμα της κλήσης θα είναι το εξής:
Acme, Inc.: Colombian_Decaf
Acme, Inc.: Colombian
Superior Coffee: French_Roast_Decaf
Superior Coffee: French_Roast
The High Ground: EspressoΔημιουργία Αποθηκευμένης Διαδικασίας με JDBC API
Η μέθοδος
StoredProcedureMySQLSample
δημουργεί μια αποθηκευμένη διαδικασία με όνομαSHOW_SUPPLIERS
:public void createProcedureShowSuppliers() throws SQLException {
String createProcedure = null;
createProcedure = "create procedure SHOW_SUPPLIERS() " +
"begin " +
"select SUPPLIERS.SUP_NAME, COFFEES.COF_NAME " +
"from SUPPLIERS, COFFEES " +
"where SUPPLIERS.SUP_ID = COFFEES.SUP_ID " +
"order by SUP_NAME; " +
"end";
Statement stmt = null;
try {
stmt = con.createStatement();
stmt.executeUpdate(createProcedure);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
stmt.close();
}
}Η κλήση
stmt.xecuteUpdate(createProcedure)
δημιουργεί την αποθηκευμένη διαδικασία:create procedure SHOW_SUPPLIERS()
begin
select SUPPLIERS.SUP_NAME, COFFEES.COF_NAME
from SUPPLIERS, COFFEES
where SUPPLIERS.SUP_ID = COFFEES.SUP_ID
order by SUP_NAME;
endΣημειώστε οτι το αντικείμενο
Connection
con
πρέπει να έχει συνδεθεί στη συγκεκριμένη βάση.Κλήση Αποθηκευμένης Διαδικασίας με JDBC API
Η μέθοδος
StoredProcedureMySQLample.runStoredProcedure
καλεί την αποθηκευμένη διαδικασία:public void runStoredProcedure() throws SQLException {
CallableStatement cs = null;
try {
cs = con.prepareCall("{call SHOW_SUPPLIERS}");
ResultSet rs = cs.executeQuery();
while (rs.next()) {
String supplier = rs.getString("SUP_NAME");
String coffee = rs.getString("COF_NAME");
System.out.println(supplier + ": " + coffee);
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
cs.close();
}
}Η διεπιφάνεια
CallableStatement
είναι επέκταση τηςPreparedStatement
και χρησιμοποιείται για τη κλήση αποθηκευμένων διαδικασιών.
Σημείωση: ´Οπως και με τα αντικείμενα
Statement
, η κλήση αποθηκευμένης διδικασίας μπορεί να γίνει με κλήση των μεθόδωνexecute
,executeQuery
, ήexecuteUpdate
ανάλογα με πόσα αντικείμεναResultSet
επιστρέφει η διαδικασία. Αν δεν είστε σίγουροι για τον αριθμό των αντικειμένωνResultSet
που θα επιστραφούν, καλέστε τη μέθοδοexecute
.