Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 71 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
71
Dung lượng
350,77 KB
Nội dung
rset.execute(); Once this method is called, it will remove any current contents and create a new set of contents for this instance. If you've forgotten to set some values, exceptions are also thrown. However, all the setup information is retained between calls. This makes the execute() method call great if you want multiple queries for the same thing with little overhead. For example, if you want to display a single Web page that contains a listing of all printed material, books and magazines, you use the following code: RowSet rset = new rset.setCommand("SELECT * FROM Product WHERE category = ?"); rset.setString(1, "books"); rset.setDataSourceName("jdbc/Oracle"); rset.execute(); // do stuff with the rowset rset.setString(1, "magazines"); rset.execute(); // now process the new information Synchronizing back with the database Because RowSets are just an extended form of ResultSet, you can make all the same changes to the underlying data source. How to get them back to the underlying database is an interesting problem, as it depends on what your RowSet represented in the first place — was it just some offline version of the ResultSet, or was it used as a live JavaBean representation of the data, or was it used in some other fashion? What you did in the first place determines how information gets back to the database. When acting as a JavaBean, the RowSet typically represents a live view of the underlying database — just as the ResultSet does. Therefore, all the methods act in the same way. A call to updateRow() or deleteRow() will make those changes immediately. Note The definition of immediately is also influenced by the transaction−handling of the connection. We look at this in more detail later in Chapter 23, but the actual results may not make it to the database until you call commit() on the Connection that this RowSet used to fill its information. For RowSet instances that work as an offline representation of the database, there is no defined way of making those changes appear in the database when connections come online again (for example, re−synching your Palm Pilot's address book with the desktop PC). The JDBC specification is very unclear about how to make these changes appear, and so we can't help you much here. You will have to read the documentation for your particular implementation and find out the best method in your case. Managing custom datatypes With the more modern, more complex databases, you can create custom datatypes as part of the SQL99 standard. For databases that support this feature, you would like to be able to map those custom types to Java classes. JDBC enables you to do this by means of a simple lookup map. Once defined, all the connections on that database use this type map. Chapter 7: Using JDBC to Interact with SQL Databases 134 Creating a custom type class Custom datatypes are represented by the SQLData interface. Any class that wants to present complex data must implement this interface and its methods, because the interface provides the information needed to create new instances of the actual data. First you have to start with a data definition from the SQL schema (this is probably defined by your DBA). For illustration purposes, we'll change the Product table that we've been using so that now it will only take an ID integer and a custom datatype that represents all the information about an individual product: CREATE TYPE ProductInfo AS ( name VARCHAR(64) NOT NULL, price DECIMAL(6,2) DEFAULT 0.00 in_stock INTEGER DEFAULT 0, category VARCHAR(16) ) NOT FINAL; You represent this by the class of the same name — ProductInfo public class ProductInfo implements SQLData { public String getSQLTypeName() { } public void readSQL(SQLInput input, String type) { } public void writeSQL(SQLOutput output) { } } This class represents a single instance of a piece of data from the database, but there is no restraint on how you present the data to the end user. Most of the time using public variables is an acceptable solution (ignoring the screams of the OO purists here!), and so for your class you declare the following: public String name; public float price; public int stockCount; public String category; You also need another variable that represents the SQL type name returned by the getSQLTypeName(). It doesn't really matter how you store that variable for this example, because the class only ever represents one type. You can either return a constant string or keep a real variable around internally. For maximum flexibility, choose the latter option (someone may choose to create a derived type of our type later). With the basic class setup out of the road, you now look to dealing with getting the information into and out of the database. The readSQL() and writeSQL() methods enable you to do this. Writing is just the opposite of reading, so we'll treat reading first. You are given information about the real data in the database by the SQLInput class. You have no choice about the order in which that data is presented to you. When reading data from the stream, you must do it in the order in which the fields are declared in the SQL statement. If the SQL type makes references to other types, you must read those types fully before reading the next attribute for your current type. The ordering is a depth−first read of the values from the database. As your datatype is really simple, you don't need to worry about this. typeName = type; Chapter 7: Using JDBC to Interact with SQL Databases 135 name = input.readString(); BigDecimal price_dec = input.readBigDecimal(); price = price_dec.floatValue(); stockCount = input.readInt(); category = input.readString(); Writing values back out is just the opposite process. You must write values to the stream in the same order in which they are declared, in the same depth−first fashion as when reading: output.writeString(name); BigDecimal dec_price = new BigDecimal(price); output.writeBigDecimal(dec_price); output.writeInt(stockCount); output.writeString(category); Your type−map implementation is now complete. This class can be compiled and is ready to be registered with JDBC. Populating the type map and informing JDBC Once you have completed the classes that represent custom datatypes, you need to register them with the system. Type mappings are registered on a per−connection basis. While it may seem annoying that you have to do this for every connection you create, this gives you more flexibility in placing different mappings for the same datatype on different connections. Registering a new mapping involves asking for the current type map and then adding your new information to that. You start by asking for the current map from the Connection interface: Connection conn = Map type_map = conn.getTypeMap(); The map returned is an instance of the standard java.util.Map. To this you can now register your new type classes. In the map, you use the string name of the datatype as the key and the Class representation of your new type as the value. The string name must include the schema name that holds your type definition. If you don't have a defined schema as an SQL construct, this string is the name of the virtual database in which the type was declared. For example, if the ProductInfo type was declared in the test_db database, then the type name would be test_db.ProductInfo. With the map instance in hand, all you need to do is put() the values into it. As it is just a general lookup map, you do not need to set() the map back to the connection. The map you are given is the internal one, so just call put() with your additional values and then continue working on other more important code. Connection conn = Map type_map = conn.getTypeMap(); type_map.put("test_db.ProductInfo", ProductInfo.class); An alternative to this is to use Class.forName() to create your Class instance: type_map.put("test_db.ProductInfo", Class.forName("ProductInfo")); Chapter 7: Using JDBC to Interact with SQL Databases 136 Of course, if you really want to trash all of the currently set maps (you don't want to play nice!), you can supply your own map. Just create a new Map instance and then use setTypeMap() as follows: Connection conn = Map type_map = new HashMap(); type_map.put("test_db.ProductInfo", ProductInfo.class); conn.setTypeMape(type_map); Working with custom type classes in code Now, every time your code accesses a custom type in the database, your class will be returned to represent it. You can also use these same classes to set values in the database. Let's say you have your ResultSet from a query. You know that Column 2 contains your product−information custom type. You would like to access the custom type and use the values. To access custom types in the table columns, use the getObject() method. This method will take a look at the type map that you registered before and return the class that represents the type that you have here. The return type is actually an Object that you must cast to the right class to use. To use your ProductInfo class from Column 2, you can make the following call: ResultSet rs = ProductInfo info = (ProductInfo)rs.getObject(2); System.out.println("The product name is " + info.name); To set or change the value in the database, you can use the updateObject() method and pass it your object instance. ProductInfo info = new ProductInfo(); info.name = "Java 2 Enterprise Bible"; info.category = "books"; info.price = 49.95f; info.stockCount = 5; rs.updateObject(2, info); In this example you create a completely new set of information and update the database with it. If you just wanted to modify one item of the existing data, you can simply use the existing class instance returned and pass it back in the updateObject() call, as follows: ResultSet rs = ProductInfo info = (ProductInfo)rs.getObject(2); if(info.category.equals("boks")) { info.category = "book"; rs.updateObject(2, info); } Tip Classes returned from the getObject() represent the information at the time of reading. They are not live, so once you have an instance you can do whatever you like with it. Changing the values in the instance will not change the underlying database. That covers the introduction to the data structures that JDBC provides you. The next step is to ask the database to return these values to you. Chapter 7: Using JDBC to Interact with SQL Databases 137 Interacting with the Database Having a bunch of data doesn't do you much good if you cannot access it. Between the Connection and the data structures you've just read about, you need a process to make queries of the database. Two more steps exist in the process of going from a connection to having the data in your hand. The first is representing the SQL code you want to execute, and the second is making that statement happen. Representing an SQL statement within Java Your first step in accessing the contents of the database is to tell the connection about the SQL statement that you want to execute. As SQL is one language and Java is quite obviously another, you need to use some form of interpretative mechanism to move from Java's world to SQL's world. As a minimum, you need something to parse the SQL string and send it off to the database in whatever form the JDBC driver uses. Note For a long time there have been some efforts to provide Java embedded in SQL for use in stored procedures. These are slowly merging, and an SQL/J standard is now going through the Java Community Process. The representation of a single SQL statement SQL works as a single command−type language. All the information needed to make one action will be entirely self−contained within that one statement. This is quite different from normal programming languages like Java or C wherein you combine groups of statements to create meaning. Note A stored procedure is not an SQL statement. Stored procedures combine a programming language that embeds SQL statements with extra constructs to allow using information from multiple separate statements to be combined together. This will always involve a proprietary language, such as Oracle's PL/SQL. The exception to this rule is that a number of database vendors are moving to replace their scripting languages for stored procedures with Java code. Calling a stored procedure is a statement, however, because you only invoke the stored procedure through a single SQL statement. All SQL statements that JDBC can execute are represented by the Statement interface. The core interface itself is relatively simple. You may set a number of properties about the returned data that you would like to see, and that is it. The Statement interface just represents the actual SQL information. It does not represent the query as it is processed. To actually make something happen, you need to call one of the myriad execute() methods available to you. Which one you should call depends on the action you are about to perform. Are you asking for data or sending updates? In order to sort out the confusion about which method to call, we will introduce each of the tasks after we introduce the different statement types you can have. For each of the types of statements you can create, there are also options to control what you get back in the ResultSet for queries. Each of the creation methods has a version that provides two integers — typically called resultSetType and resultSetConcurrency. The values that you pass to these parameters are the same ones that we introduced earlier in the chapter as the return values from getType() and getConcurrency(), respectively. Chapter 7: Using JDBC to Interact with SQL Databases 138 Standard statements for quick queries If you know exactly what you are going to ask for, then the simplest way to grab a statement is to use the basic Statement interface from the connection. These forms of statements tend to represent quick one−off requests to the database in situations where you always know everything about the query. To create an ordinary statement, use the createStatement() method from the Connection interface. This will pass you a Statement instance to use. This instance can now be used to make queries or updates of the database through the various execute() methods wherein you must pass the SQL string when you want it to be executed. For example, to create a new statement from a DataSource ds, you use the following code: Connection conn = ds.getConnection(); Statement stmt = conn.createStatement(); Creating template statements The downside of these fast statements is the large performance cost. Each time you ask this statement to execute, it must make the full trip of parsing the SQL string and making the connection to the database and waiting for the results. For high−load server applications, the penalty can be very high. To get around this problem, you can create a form of precompiled statements that caches all the startup and return−value information — the PreparedStatement. Creating a prepared statement requires the use of the prepareStatement() call of Connection. For this method, you must always pass a String that represents the SQL command that you want executed. If the string is properly formed, it will return an instance of the PreparedStatement interface. Most of the time the driver implementation will also send the SQL off to the database to compile it for later use. The idea is that you now have a preoptimized command ready to go. All you have to do is fill in any blanks and tell the database to run it. PreparedStatement interfaces are really geared toward making the same query over and over — that is, the typical interaction you will see in an enterprise application server. In particular, they are best when you have a known query of which one part is dynamically set for each time it is run. Back in the RowSet introduction, we demonstrated the use of the SQL setCommand() method and the accompanying setX methods to fill in parameter values. Well, prepared statements can work in the same way, using almost identical method calls. In your Web server, you want to always have a query waiting around to ask for the list of products in any given category. Having one complete PreparedStatement instance for each category is a waste of resources. Your code won't be flexible, either for adding or removing categories on the fly. To cope with this, you use the prepared statement with wildcards and then fill in the wildcards just before making the requests: String cmd = "SELECT * FROM Product WHERE category = ?"; Connection conn = ds.getConnection(); PreparedStatement stmt = conn.prepareStatement(cmd); stmt.setString(1, "book"); // now run the statement to get values back Chapter 7: Using JDBC to Interact with SQL Databases 139 The PreparedStatement interface extends the Statement interface, so all the functionality that you have there will also be available here. To this, you just add the setX methods to set all the parameter datatypes that you have seen so far. Calling stored procedures Stored procedures are collections of code stored inside the database that act on the tables just like regular function calls. These procedures look to some extent like ordinary Java method calls. They have parameter values and return values. Sometimes a parameter may have its value modified or be used to pass information outwards to the caller (which makes it a little different from the Java model). To call a stored procedure, you need to have one defined. This is where your database administrator (DBA) comes in handy. Your DBA should give you the details about what is available. In keeping with previous examples, say you have a stored procedure that you can ask to list all the products from a certain category. This takes a single parameter: the category name. PROCEDURE LIST_CATEGORY(IN: category) Creating a stored procedure is similar to creating a prepared statement. You pass in a string with a procedure to be called using the appropriate SQL syntax (in this case the SQL CALL command). Stored procedures are represented by the CallableStatement interface, which is derived from PreparedStatement. To create an instance of CallableStatement you use the prepareCall() method from the Connection and pass it the string representing your SQL call: String cmd = "CALL LIST_CATEGORY('books')"; Connection conn = ds.getConnection(); CallableStatement stmt = conn.prepareCall(cmd); You can now execute the CallableStatement just as you would the other statement types. However, just as with prepared statements, the real idea is to use the stored procedure as a template and pass in information for each query execution. To do this, you start with the same wildcarding that you've used before in this chapter. String cmd = "CALL LIST_CATEGORY(?)"; Stored procedures have parameters, but they can be slightly different from Java's. Java only supports parameters that are read−only. You can pass information in, but you can't use the parameters to pass information out. Stored procedures in SQL are different. Three different forms of parameters exist: IN: This parameter is used to pass information into the procedure. This parameter is treated as read−only and cannot be changed. • OUT: This parameter takes no values when called, but can be read after the call returns. It is used a bit like return types in Java, but you can have many of them to returns lots of different information. • INOUT: This parameter combines the functionalities of IN and OUT. You can set the values during the call, but they may change and hold new information on the way out. • Because each of these parameter types works differently, you need to match each parameter in the string you've passed to JDBC with the appropriate parameter type. When you pass the information to JDBC in the prepareCall() method call, JDBC has no knowledge of the actual script. You must tell JDBC what to expect. Nominating parameters in callable statements are treated with a similar fashion to prepared statements. IN parameters use the same setX methods that you use in prepared statements to set wildcard values in SQL. OUT parameters need to be registered with a registerOutX method. INOUT parameters combine the IN and Chapter 7: Using JDBC to Interact with SQL Databases 140 OUT functionalities, so you can use these methods to register each part. To register information about an outgoing parameter, you must tell the statement what that parameter type is. The underlying JDBC code does not know what to expect, so you need to give it some extra information. Thus, when you call the registerOutX method, you need to supply the parameter that you are changing with an integer that tells it the type of data to expect. This integer is one of the values defined in the Types class that is defined in the core package. As an example, let's say your stored procedure returned the number of items in the category as an integer OUT parameter: PROCEDURE LIST_CATEGORY(IN: category, OUT: num_items) You can register the information on the num_items parameter and set up the call with the following code: String cmd = "CALL LIST_CATEGORY(?, ?)"; Connection conn = ds.getConnection(); CallableStatement stmt = conn.prepareCall(cmd); stmt.registerOutParameter(2, Types.INTEGER); stmt.setString(1, "books"); In a departure from the other statement types, you can call the set and register methods using either a positional index or a name string. The position index works as you would expect from the previous uses. If you pass a name string, this is used to try to map the parameter to the name declared in the stored procedure in the database. Tip Do not try to combine parameter names and position index values within one statement. This could lead to problems or exceptions being generated by the database. Pick one and use it consistently. Querying the database for information You've got the driver, you've got a connection, and you've even registered interest of executing a statement. Finally you have enough information to make a query of the database! We mentioned earlier that you need to call one of the execute methods in order to make a real query to the database. Of course, nothing you do is ever simple, and the execute method you call depends on the type of statement you created in the first place. So we'll first introduce the generic differences among execute methods before getting into more specifics. Types of statement execution Statements can represent either changing of information in the database or queries for information. These requests will return different types of information will be returned to the caller. In the case of updates, you want to know how many rows have been affected. In the case of queries, you want to know what the results were. Because you know you have to deal with two different return types, two different forms of the execute methods exist — executeQuery() and executeUpdate(). You can consider these a form of strong type checking. If you call executeQuery() when the SQL is really an update, an exception will be generated. Sometimes when you execute the statement you may not know whether you are making an update or a query. The more general execute() method helps in this case. This version returns a boolean value. If the value is true, then the statement was a query; false indicates that the statement was an update. Of course, you want to know the results in either case, so you can use one of the convenience methods to ask for it, as follows: boolean is_query = stmt.execute(); Chapter 7: Using JDBC to Interact with SQL Databases 141 if(is_query) { ResultSet rs = stmt.getResultSet(); } else { int rows_updated = stmt.getUpdateCount(); } Calling simple statements With the simple Statement object, you don't have any SQL commands issued before you get to call execute. So, for these statements, you need to use one of the execute statements that takes a string. The string contains the SQL that you want to run. A simple query runs like this: Statement stmt = conn.getStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM Product"); With the ResultSet in hand, you can now process the values as we discussed earlier in the chapter. Calling prepared statements In prepared statements, you already have the majority of the SQL data set. To execute a statement, you only need to fill in missing parameter values and call the executeQuery() method. This time, as you have already set the SQL data, you do not need to supply any values to executeQuery(). String cmd = "SELECT * FROM Product WHERE category = ?"; PreparedStatement stmt = conn.prepareStatement(cmd); stmt.setString(1, "book"); ResultSet rs = stmt.executeQuery(); Calling stored procedures Stored procedure calls add one more interesting twist: You can have values returned as a result set, but you also have OUT parameters to deal with. To start with, you set up the query and execute the action just as you do with the prepared statement: String cmd = "CALL LIST_CATEGORY(?, ?)"; CallableStatement stmt = conn.prepareCall(cmd); stmt.registerOutParameter(2, Types.INTEGER); stmt.setString(1, "books"); ResultSet rs = stmt.executeQuery(); After executing the statement, you will need to read the value of the OUT parameter in position index 2. In the preceding code, you have marked it as being an integer value, so you use the getInt() method from the CallableStatement interface to read the value back out. int num_items = stmt.getInt(2); The position index here must be the same as the one you declared when registering the OUT parameter earlier. Tip If you are using the generic execute() method rather than executeQuery(), the specification Chapter 7: Using JDBC to Interact with SQL Databases 142 recommends that you always fetch the ResultSet before accessing the OUT parameter values. Making updates to the database Making changes to the existing database is similar to querying the database. For simple queries, you pass in the SQL statement to be executed, where the pre−built versions will not need arguments. The one crucial difference is the return value of the methods. When making a query, you get back a collection of the rows that match. When making an update, you get a number representing the number of rows that have been affected by that update. As far as JDBC is concerned, any change to the table structure is an update. Modifying, inserting, or deleting rows all count as updates. Also considered updates are the basic database commands, such as creating, altering, or dropping tables. Because these are just SQL commands, you can create the database and all its contents from JDBC. There is no need to build external scripts for your database management should you choose not to. Note The following instructions show you how to create new updates to the database. Earlier in this chapter you saw how to make changes once you have the results of a query. Those techniques are just as useful as these and the one you choose to make changes depends on what your code needs to do and on the information it already has. For example, there is no real point in making a query for all of the values and then looping through to change one column when it is far faster just to issue an SQL statement to do the same thing. Executing simple updates Simple updates follow the same pattern as simple queries. You must call the executeUpdate() method that takes a string argument. The string is the SQL statement to be executed. Statement stmt = conn.getStatement(); int rows_updated = stmt.executeUpdate( "INSERT INTO ProductInfo VALUES ('Java 2 Enterprise Bible'" + ", 49.95, 5, 'books')" ); Because this is an insert of new data, the return value of rows_updated will always be the value 1. If you want to update a collection of rows — say to fix a typo — you get a value that reflected the items changed. int rows_updated = stmt.executeUpdate( "UPDATE ProductInfo SET category='books' WHERE " + "category = 'boks'" ); Executing prepared updates OK, by now you should be starting to get the hang of all this. The process of making updates with prepared statements follows the same pattern: Create the statement, fill in any parameters, and then execute the update. You can make the previous example completely reusable by making the following changes: PreparedStatement stmt = conn.prepareStatement( "INSERT INTO ProductInfo VALUES (?, ?, ?, ?)" ); stmt.setString(1, "Java 2 Enterprise Bible"); Chapter 7: Using JDBC to Interact with SQL Databases 143 [...]... stmt.setString(1, "Java 2 Enterprise Bible" ); stmt.setBigDecimal (2, new BigDecimal(49.95)); stmt.setInt (3, 5); stmt.setString(4, "books"); To indicate that you wish to batch updates, you now call the addBatch() method that takes no arguments This tells the underlying implementation to store those values and get ready for another: stmt.addBatch(); stmt.setString(1, "Java 2 Bible" ); stmt.setBigDecimal (2, new BigDecimal (39 .95));... database for evaluation stmt.addBatch("INSERT INTO Customer VALUES (" + "'555 Mystreet Ave', 'AU', 'Justin Couch'," + "'+61 2 1 23 4 5678')" ); stmt.addBatch("INSERT INTO Order VALUES (" + "49.95, " + "(SELECT customer_id FROM Customer WHERE " + "name='Justin Couch' AND " + "phone='+61 2 1 23 4 5678'), " + "" ); You can submit as many queries in the batch as you want Each request is stored internally for use... LDAP usage? Distinguished names Let's start your first example of a distinguished name using a product — this book Its ISBN allocated is 0−7645−08 82 2 (at least for the American edition! ) Under the structure that you've just created, the DN is: isbn=0−7645−08 82 2, cat=books, ou=products, o=ExampleApp What does all this mean? Well, let's start at the beginning — a DN is a comma−delimited list of entries... basic enterprise transaction handling There is much more to it than this — particularly when you start looking at handling commits across multiple data−source types such as LDAP, file systems, and databases We'll address the topic in much greater detail in Chapter 23 149 Chapter 7: Using JDBC to Interact with SQL Databases Summary JDBC is a big system of APIs, and with the introduction of JDBC 3. 0 it... great benefit not only in enterprise programming, but also in programming smaller−scale systems such as desktops and PDAs The latest version of the specification is or will be part of the next iteration of the enterprise and standard specifications In this chapter, we: • Introduced the Java representation of a database JDBC • Examined how JDBC represents SQL information within the Java language environments... core of J2EE itself Directory services are accessed through the JNDI APIs If you have worked through Chapter 7 you will have noticed that you access all the drivers through a directory−service interface As you will see in later chapters, all the Enterprise JavaBeans (EJBs) and high−end services are accessed through JNDI as well Put frankly — you can't avoid using directory services in a J2EE application... are illustrated in Figures 8 2 and 8 3 The first shows a company−style structure that holds information relative to the functional requirements — geographic office locations and then functional items such as printers, staff, and so on Figure 8 2: A directory−information tree organized by functional requirements 158 Chapter 8: Working with Directory Services and LDAP Figure 8 3: A directory−information... System.err.println("ERROR!!!! " + me.getMessage()); conn.rollback(); } Savepoint spt1 = conn.setSavepoint(); try { module _2. performAnotherAction(stmt2); } catch(ModuleException me) { System.err.println("ERROR!!!! " + me.getMessage()); conn.rollback(); } conn.setSavepoint(); try { module _3. performThirdAction(stmt2); } catch(ModuleException me) { System.err.println("ERROR!!!! " + me.getMessage()); if(me.getErrorCode()... directory services in a J2EE application environment Introducing LDAP After the vanilla directory services that J2EE provides you, LDAP will be the directory−services capability you use most in your enterprise application In this section, we'll introduce the major ideas about LDAP Note The J2EE environment uses the CORBA naming service COS Naming as the default service provider in JNDI This provides... interact with them was very slow too (given the available bandwidth of the day) Note The LDAP standard is defined as part of a number of Internet RFCs The most recent standard is RFC 225 1, "Lightweight Directory Access Protocol (v3)." Like most of the other technologies that we mentioned, LDAP started its life as a way to provide a simplified, very lightweight access mechanism to the X.500 system that would . ?)" ); stmt.setString(1, " ;Java 2 Enterprise Bible& quot;); Chapter 7: Using JDBC to Interact with SQL Databases 1 43 stmt.setBigDecimal (2, new BigDecimal(49.95)); stmt.setInt (3, 5); stmt.setString(4,. using the normal setX methods: stmt.setString(1, " ;Java 2 Enterprise Bible& quot;); stmt.setBigDecimal (2, new BigDecimal(49.95)); stmt.setInt (3, 5); stmt.setString(4, "books"); To indicate. ready for another: stmt.addBatch(); stmt.setString(1, " ;Java 2 Bible& quot;); stmt.setBigDecimal (2, new BigDecimal (39 .95)); stmt.setInt (3, 5); stmt.setString(4,"books"); stmt.addBatch(); Once