![]() |
James Thornton |
| Internet Business Consultant | Call Toll Free: 1 (800) 409-2501 |
| About James | My MySpace | Internet Marketing | Enron Loophole | Lock Bumping | Contact Me |
|---|
The programmer had to know many details
about the network and sometimes even the hardware. You usually needed to
understand the various “layers” of the networking protocol, and
there were a lot of different functions in each different networking library
concerned with connecting, packing, and unpacking blocks of information;
shipping those blocks back and forth; and handshaking. It was a daunting task.
[ Add Comment ]
However, the basic idea of distributed
computing is not so difficult, and is abstracted very nicely in the Java
libraries. You want to:
Each
topic will be given a light introduction in this chapter. Please note that each
subject is voluminous and by itself the subject of entire books, so this chapter
is only meant to familiarize you with the topics, not make you an expert
(however, you can go a long way with the information presented here on network
programming, servlets and JSPs).
[ Add Comment ]
One of Java’s great strengths is
painless networking. The Java network library designers have made it quite
similar to reading and writing files, except that the “file” exists
on a remote machine and the remote machine can decide exactly what it wants to
do about the information you’re requesting or sending. As much as
possible, the underlying details of networking have been abstracted away and
taken care of within the JVM and local machine installation of Java. The
programming model you use is that of a file; in fact, you actually wrap the
network connection (a “socket”) with stream objects, so you end up
using the same method calls as you do with all other streams. In addition,
Java’s built-in multithreading is exceptionally handy when dealing with
another networking issue: handling multiple connections at once.
[ Add Comment ]
This section introduces Java’s
networking support using easy-to-understand examples.
[ Add Comment ]
Of course, in order to tell one machine
from another and to make sure that you are connected with a particular machine,
there must be some way of uniquely identifying machines
on a network. Early networks were satisfied to provide unique names for machines
within the local network. However, Java works within the Internet, which
requires a way to uniquely identify a machine from all the others in the
world. This is accomplished with the
IP
(Internet Protocol) address which can exist in two
forms“:
[ Add Comment ]
In both
cases, the IP address is represented internally as a 32-bit
number[72] (so each
of the quad numbers cannot exceed 255), and you can get a special Java object to
represent this number from either of the forms above by using the static
InetAddress.getByName( ) method that’s in java.net. The
result is an object of type InetAddress that you can use to build a
“socket,” as you will see later.
[ Add Comment ]
As a simple example of using
InetAddress.getByName( ), consider what happens if you have a
dial-up Internet service provider (ISP). Each time you dial up, you are assigned
a temporary IP address. But while you’re connected, your IP address has
the same validity as any other IP address on the Internet. If someone connects
to your machine using your IP address then they can connect to a Web server or
FTP server that you have running on your machine. Of course, they need to know
your IP address, and since a new one is assigned each time you dial up, how can
you find out what it is?
[ Add Comment ]
The following program uses
InetAddress.getByName( ) to produce your IP address. To use it, you
must know the name of your computer. On Windows 95/98, go to
“Settings,” “Control Panel,” “Network,” and
then select the “Identification” tab. “Computer name” is
the name to put on the command line.
//: c15:WhoAmI.java
// Finds out your network address when
// you're connected to the Internet.
import java.net.*;
public class WhoAmI {
public static void main(String[] args)
throws Exception {
if(args.length != 1) {
System.err.println(
"Usage: WhoAmI MachineName");
System.exit(1);
}
InetAddress a =
InetAddress.getByName(args[0]);
System.out.println(a);
}
} ///:~
In this case, the machine is called
“peppy.” So, once I’ve connected to my ISP I run the
program:
java WhoAmI peppy
I get back a message like this (of
course, the address is different each time):
peppy/199.190.87.75
If I tell my friend this address and I
have a Web server running on my computer, he can connect to it by going to the
URL http://199.190.87.75 (only as long as I continue to stay connected
during that session). This can sometimes be a handy way to distribute
information to someone else, or to test out a Web site configuration before
posting it to a “real” server.
[ Add Comment ]
The whole point of a network is to allow
two machines to connect and talk to each other. Once the two machines have found
each other they can have a nice, two-way conversation. But how do they find each
other? It’s like getting lost in an amusement park: one machine has to
stay in one place and listen while the other machine says, “Hey, where are
you?”
[ Add Comment ]
The machine that “stays in one
place” is called the
server, and the one that
seeks is called the
client. This distinction
is important only while the client is trying to connect to the server. Once
they’ve connected, it becomes a two-way communication process and it
doesn’t matter anymore that one happened to take the role of server and
the other happened to take the role of the client.
[ Add Comment ]
So the job of the server is to listen for
a connection, and that’s performed by the special server object that you
create. The job of the client is to try to make a connection to a server, and
this is performed by the special client object you create. Once the connection
is made, you’ll see that at both server and client ends, the connection is
magically turned into an I/O stream object, and from then on you can treat the
connection as if you were reading from and writing to a file. Thus, after the
connection is made you will just use the familiar I/O commands from Chapter 11.
This is one of the nice features of Java networking.
[ Add Comment ]
For many reasons, you might not have a
client machine, a server machine, and a network available to test your programs.
You might be performing exercises in a classroom situation, or you could be
writing programs that aren’t yet stable enough to put onto the network.
The creators of the Internet Protocol were aware of this issue, and they created
a special address called
localhost to be the
“local loopback” IP
address for testing without a network. The generic way to produce this address
in Java is:
InetAddress addr = InetAddress.getByName(null);
If you hand getByName( ) a
null, it defaults to using the localhost. The InetAddress
is what you use to refer to the particular machine, and you must produce this
before you can go any further. You can’t manipulate the contents of an
InetAddress (but you can print them out, as you’ll see in the next
example). The only way you can create an InetAddress is through one of
that class’s overloaded static member methods
getByName( ) (which is what you’ll usually use),
getAllByName( ), or getLocalHost( ).
[ Add Comment ]
You can also produce the local loopback
address by handing it the string localhost:
InetAddress.getByName("localhost");
(assuming “localhost” is
configured in your machine’s “hosts” table), or by using its
dotted quad form to name the reserved IP number for the
loopback:
InetAddress.getByName("127.0.0.1");
All three forms produce the same result.
[ Add Comment ]
An IP address isn’t enough to
identify a unique server, since many servers can exist on one machine. Each IP
machine also contains ports, and when you’re setting up a client or
a server you must choose a port
where both client and server agree to connect; if you’re meeting someone,
the IP address is the neighborhood and the port is the bar.
[ Add Comment ]
The port is not a physical location in a
machine, but a software abstraction (mainly for bookkeeping purposes). The
client program knows how to connect to the machine via its IP address, but how
does it connect to a desired service (potentially one of many on that machine)?
That’s where the port numbers come in as a second level of addressing. The
idea is that if you ask for a particular port, you’re requesting the
service that’s associated with the port number. The time of day is a
simple example of a service. Typically, each service is associated with a unique
port number on a given server machine. It’s up to the client to know ahead
of time which port number the desired service is running on.
[ Add Comment ]
The system services reserve the use of
ports 1 through 1024, so you shouldn’t use those or any other port that
you know to be in use. The first choice for examples in this book will be port
8080 (in memory of the venerable old 8-bit Intel 8080 chip in my first computer,
a CP/M machine).
[ Add Comment ]
The socket is the software
abstraction used to represent the “terminals” of a connection
between two machines. For a given connection, there’s a socket on each
machine, and you can imagine a hypothetical “cable” running between
the two machines with each end of the “cable” plugged into a socket.
Of course, the physical hardware and cabling between machines is completely
unknown. The whole point of the abstraction is that we don’t have to know
more than is necessary.
[ Add Comment ]
In Java, you create a socket to make the
connection to the other machine, then you get an InputStream and
OutputStream (or, with the appropriate converters, Reader and
Writer) from the socket in order to be able to treat the
connection as an I/O stream object. There are two stream-based socket classes: a
ServerSocket that a server uses to “listen” for incoming
connections and a Socket that a client uses in order to initiate a
connection. Once a client makes a socket connection, the ServerSocket
returns (via the accept( )
method) a corresponding
Socket through which communications will take place on the server side.
From then on, you have a true Socket to Socket connection and you
treat both ends the same way because they are the same. At this point,
you use the methods
getInputStream( )
and
getOutputStream( )
to produce the corresponding InputStream and OutputStream objects
from each Socket. These must be wrapped inside buffers and formatting
classes just like any other stream object described in Chapter 11.
[ Add Comment ]
The use of the term ServerSocket
would seem to be another example of a confusing naming scheme in the Java
libraries. You might think ServerSocket would be better named
“ServerConnector” or something without the word “Socket”
in it. You might also think that ServerSocket and Socket should
both be inherited from some common base class. Indeed, the two classes do have
several methods in common, but not enough to give them a common base class.
Instead, ServerSocket’s job is to wait until some other machine
connects to it, then to return an actual Socket. This is why
ServerSocket seems to be a bit misnamed, since its job isn’t really
to be a socket but instead to make a Socket object when someone else
connects to it.
[ Add Comment ]
However, the ServerSocket does
create a physical “server” or listening socket on the host machine.
This socket listens for incoming connections and then returns an
“established” socket (with the local and remote endpoints defined)
via the accept( ) method. The confusing part is that both of these
sockets (listening and established) are associated with the same server socket.
The listening socket can accept only new connection requests and not data
packets. So while ServerSocket doesn’t make much sense
programmatically, it does “physically.”
[ Add Comment ]
When you create a ServerSocket,
you give it only a port number. You don’t have to give it an IP address
because it’s already on the machine it represents. When you create a
Socket, however, you must give both the IP address and the port number
where you’re trying to connect. (However, the Socket that comes
back from ServerSocket.accept( ) already contains all this
information.)
[ Add Comment ]
This example makes the simplest use of
servers and clients using sockets. All the server does is wait for a connection,
then uses the Socket produced by that connection to create an
InputStream and OutputStream. These are converted to a
Reader and a Writer, then wrapped in a BufferedReader and a
PrintWriter. After that, everything it reads from the
BufferedReader it echoes to the PrintWriter until it receives the
line “END,” at which time it closes the connection.
[ Add Comment ]
The client makes the connection to the
server, then creates an OutputStream and performs the same wrapping as in
the server. Lines of text are sent through the resulting PrintWriter. The
client also creates an InputStream (again, with appropriate conversions
and wrapping) to hear what the server is saying (which, in this case, is just
the words echoed back).
[ Add Comment ]
Both the server and client use the same
port number and the client uses the local loopback address to connect to the
server on the same machine so you don’t have to test it over a network.
(For some configurations, you might need to be connected to a network for
the programs to work, even if you aren’t communicating over that
network.)
[ Add Comment ]
Here is the server:
//: c15:JabberServer.java
// Very simple server that just
// echoes whatever the client sends.
import java.io.*;
import java.net.*;
public class JabberServer {
// Choose a port outside of the range 1-1024:
public static final int PORT = 8080;
public static void main(String[] args)
throws IOException {
ServerSocket s = new ServerSocket(PORT);
System.out.println("Started: " + s);
try {
// Blocks until a connection occurs:
Socket socket = s.accept();
try {
System.out.println(
"Connection accepted: "+ socket);
BufferedReader in =
new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
// Output is automatically flushed
// by PrintWriter:
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())),true);
while (true) {
String str = in.readLine();
if (str.equals("END")) break;
System.out.println("Echoing: " + str);
out.println(str);
}
// Always close the two sockets...
} finally {
System.out.println("closing...");
socket.close();
}
} finally {
s.close();
}
}
} ///:~
You can see that the ServerSocket
just needs a port number, not an IP address (since it’s running on
this machine!). When you call accept( ), the method
blocks until some client tries to connect to it. That is, it’s
there waiting for a connection, but other processes can run (see Chapter 14).
When a connection is made, accept( ) returns with a Socket
object representing that connection.
[ Add Comment ]
The responsibility for cleaning up the
sockets is crafted carefully here. If the ServerSocket constructor fails,
the program just quits (notice we must assume that the constructor for
ServerSocket doesn’t leave any open network sockets lying around if
it fails). For this case, main( ) throws IOException
so a try block is not necessary. If the ServerSocket constructor
is successful then all other method calls must be guarded in a
try-finally block to ensure that, no matter how the block is left, the
ServerSocket is properly closed.
[ Add Comment ]
The same logic is used for the
Socket returned by accept( ). If accept( ) fails,
then we must assume that the Socket doesn’t exist or hold any
resources, so it doesn’t need to be cleaned up. If it’s successful,
however, the following statements must be in a try-finally block so that
if they fail the Socket will still be cleaned up. Care is required here
because sockets use important nonmemory resources, so you must be diligent in
order to clean them up (since there is no destructor in Java to do it for you).
[ Add Comment ]
Both the ServerSocket and the
Socket produced by accept( ) are printed to
System.out. This means that their toString( ) methods are
automatically called. These produce:
ServerSocket[addr=0.0.0.0,PORT=0,localport=8080] Socket[addr=127.0.0.1,PORT=1077,localport=8080]
Shortly, you’ll see how these fit
together with what the client is doing.
[ Add Comment ]
The next part of the program looks just
like opening files for reading and writing except that the InputStream
and OutputStream are created from the Socket object. Both the
InputStream and OutputStream objects are converted to
Reader and
Writer objects using the
“converter” classes
InputStreamReader and
OutputStreamWriter,
respectively. You could also have used the Java 1.0
InputStream and
OutputStream classes
directly, but with output there’s a distinct advantage to using the
Writer approach. This appears with
PrintWriter, which has an
overloaded constructor that takes a second argument, a boolean flag that
indicates whether to automatically flush the output at the end of each
println( ) (but not print( )) statement. Every
time you write to out, its buffer must be flushed so the information goes
out over the network. Flushing is important for this particular example because
the client and server each wait for a line from the other party before
proceeding. If flushing doesn’t occur, the information will not be put
onto the network until the buffer is full, which causes lots of problems in this
example.
[ Add Comment ]
When writing network programs you need to
be careful about using automatic flushing. Every time you flush the buffer a
packet must be created and sent. In this case, that’s exactly what we
want, since if the packet containing the line isn’t sent then the
handshaking back and forth between server and client will stop. Put another way,
the end of a line is the end of a message. But in many cases, messages
aren’t delimited by lines so it’s much more efficient to not use
auto flushing and instead let the built-in buffering decide when to build and
send a packet. This way, larger packets can be sent and the process will be
faster.
[ Add Comment ]
Note that, like virtually all streams you
open, these are buffered. There’s an exercise at the end of this chapter
to show you what happens if you don’t buffer the streams (things get
slow).
[ Add Comment ]
The infinite while loop reads
lines from the BufferedReader in and writes information to
System.out and to the PrintWriter out. Note that in
and out could be any streams, they just happen to be connected to the
network.
[ Add Comment ]
When the client sends the line consisting
of “END,” the program breaks out of the loop and closes the
Socket.
Here’s the client:
//: c15:JabberClient.java
// Very simple client that just sends
// lines to the server and reads lines
// that the server sends.
import java.net.*;
import java.io.*;
public class JabberClient {
public static void main(String[] args)
throws IOException {
// Passing null to getByName() produces the
// special "Local Loopback" IP address, for
// testing on one machine w/o a network:
InetAddress addr =
InetAddress.getByName(null);
// Alternatively, you can use
// the address or name:
// InetAddress addr =
// InetAddress.getByName("127.0.0.1");
// InetAddress addr =
// InetAddress.getByName("localhost");
System.out.println("addr = " + addr);
Socket socket =
new Socket(addr, JabberServer.PORT);
// Guard everything in a try-finally to make
// sure that the socket is closed:
try {
System.out.println("socket = " + socket);
BufferedReader in =
new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
// Output is automatically flushed
// by PrintWriter:
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())),true);
for(int i = 0; i < 10; i ++) {
out.println("howdy " + i);
String str = in.readLine();
System.out.println(str);
}
out.println("END");
} finally {
System.out.println("closing...");
socket.close();
}
}
} ///:~
In main( ) you can see all
three ways to produce the InetAddress of the local loopback IP address:
using null, localhost, or the explicit reserved address
127.0.0.1. Of course, if you want to connect to a machine across a
network you substitute that machine’s IP address. When the InetAddress
addr is printed (via the automatic call to its toString( )
method) the result is:
[ Add Comment ]
localhost/127.0.0.1
By handing getByName( ) a
null, it defaulted to finding the localhost, and that produced the
special address 127.0.0.1.
[ Add Comment ]
Note that the
Socket called
socket is created with both the InetAddress and the port number.
To understand what it means when you print one of these Socket objects,
remember that an Internet connection is determined uniquely by these four pieces
of data: clientHost, clientPortNumber, serverHost, and
serverPortNumber. When the server comes up, it takes up its assigned port
(8080) on the localhost (127.0.0.1). When the client comes up, it is allocated
to the next available port on its machine, 1077 in this case, which also happens
to be on the same machine (127.0.0.1) as the server. Now, in order for data to
move between the client and server, each side has to know where to send it.
Therefore, during the process of connecting to the “known” server,
the client sends a “return address” so the server knows where to
send its data. This is what you see in the example output for the server
side:
Socket[addr=127.0.0.1,port=1077,localport=8080]
This means that the server just accepted
a connection from 127.0.0.1 on port 1077 while listening on its local port
(8080). On the client side:
[ Add Comment ]
Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]
which means that the client made a
connection to 127.0.0.1 on port 8080 using the local port 1077.
[ Add Comment ]
You’ll notice that every time you
start up the client anew, the local port number is incremented. It starts at
1025 (one past the reserved block of ports) and keeps going up until you reboot
the machine, at which point it starts at 1025 again. (On UNIX machines, once the
upper limit of the socket range is reached, the numbers will wrap around to the
lowest available number again.)
[ Add Comment ]
Once the Socket object has been
created, the process of turning it into a BufferedReader and
PrintWriter is the same as in the server (again, in both cases you start
with a Socket). Here, the client initiates the conversation by sending
the string “howdy” followed by a number. Note that the buffer must
again be flushed (which happens automatically via the second argument to the
PrintWriter constructor). If the buffer isn’t flushed, the whole
conversation will hang because the initial “howdy” will never get
sent (the buffer isn’t full enough to cause the send to happen
automatically). Each line that is sent back from the server is written to
System.out to verify that everything is working correctly. To terminate
the conversation, the agreed-upon “END” is sent. If the client
simply hangs up, then the server throws an exception.
[ Add Comment ]
You can see that the same care is taken
here to ensure that the network resources represented by the Socket are
properly cleaned up, using a try-finally block.
[ Add Comment ]
Sockets produce a
“dedicated” connection that persists until
it is explicitly disconnected. (The dedicated connection can still be
disconnected unexplicitly if one side, or an intermediary link, of the
connection crashes.) This means the two parties are locked in communication and
the connection is constantly open. This seems like a logical approach to
networking, but it puts an extra load on the network. Later in this chapter
you’ll see a different approach to networking, in which the connections
are only temporary.
[ Add Comment ]
The JabberServer works, but it can
handle only one client at a time. In a typical server, you’ll want to be
able to deal with many clients at once. The answer is
multithreading, and in languages
that don’t directly support multithreading this means all sorts of
complications. In Chapter 14 you saw that multithreading in Java is about as
simple as possible, considering that multithreading is a rather complex topic.
Because threading in Java is reasonably straightforward, making a server that
handles multiple clients is relatively easy.
[ Add Comment ]
The basic scheme is to make a single
ServerSocket in the server and call accept( ) to wait for a
new connection. When accept( ) returns, you take the resulting
Socket and use it to create a new thread whose job is to serve that
particular client. Then you call accept( ) again to wait for a new
client.
[ Add Comment ]
In the following server code, you can see
that it looks similar to the JabberServer.java example except that all of
the operations to serve a particular client have been moved inside a separate
thread class:
//: c15:MultiJabberServer.java
// A server that uses multithreading
// to handle any number of clients.
import java.io.*;
import java.net.*;
class ServeOneJabber extends Thread {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
public ServeOneJabber(Socket s)
throws IOException {
socket = s;
in =
new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
// Enable auto-flush:
out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())), true);
// If any of the above calls throw an
// exception, the caller is responsible for
// closing the socket. Otherwise the thread
// will close it.
start(); // Calls run()
}
public void run() {
try {
while (true) {
String str = in.readLine();
if (str.equals("END")) break;
System.out.println("Echoing: " + str);
out.println(str);
}
System.out.println("closing...");
} catch(IOException e) {
System.err.println("IO Exception");
} finally {
try {
socket.close();
} catch(IOException e) {
System.err.println("Socket not closed");
}
}
}
}
public class MultiJabberServer {
static final int PORT = 8080;
public static void main(String[] args)
throws IOException {
ServerSocket s = new ServerSocket(PORT);
System.out.println("Server Started");
try {
while(true) {
// Blocks until a connection occurs:
Socket socket = s.accept();
try {
new ServeOneJabber(socket);
} catch(IOException e) {
// If it fails, close the socket,
// otherwise the thread will close it:
socket.close();
}
}
} finally {
s.close();
}
}
} ///:~
The ServeOneJabber thread takes
the Socket object that’s produced by accept( ) in
main( ) every time a new client makes a connection. Then, as before,
it creates a BufferedReader and auto-flushed PrintWriter object
using the Socket. Finally, it calls the special Thread method
start( ), which performs thread initialization and then calls
run( ). This performs the same kind of action as in the previous
example: reading something from the socket and then echoing it back until it
reads the special “END” signal.
[ Add Comment ]
The responsibility for cleaning up the
socket must again be carefully designed. In this case, the socket is created
outside of the ServeOneJabber so the responsibility can be shared. If the
ServeOneJabber constructor fails, it will just throw the exception to the
caller, who will then clean up the thread. But if the constructor succeeds, then
the ServeOneJabber object takes over responsibility for cleaning up the
thread, in its run( ).
[ Add Comment ]
Notice the simplicity of the
MultiJabberServer. As before, a ServerSocket is created and
accept( ) is called to allow a new connection. But this time, the
return value of accept( ) (a Socket) is passed to the
constructor for ServeOneJabber, which creates a new thread to handle that
connection. When the connection is terminated, the thread simply goes away.
[ Add Comment ]
If the creation of the
ServerSocket fails, the exception is again thrown through
main( ). But if the creation succeeds, the outer try-finally
guarantees its cleanup. The inner try-catch guards only against the
failure of the ServeOneJabber constructor; if the constructor succeeds,
then the ServeOneJabber thread will close the associated socket.
[ Add Comment ]
To test that the server really does
handle multiple clients, the following program creates many clients (using
threads) that connect to the same server. The maximum number of threads allowed
is determined by the final int MAX_THREADS.
//: c15:MultiJabberClient.java
// Client that tests the MultiJabberServer
// by starting up multiple clients.
import java.net.*;
import java.io.*;
class JabberClientThread extends Thread {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
private static int counter = 0;
private int id = counter++;
private static int threadcount = 0;
public static int threadCount() {
return threadcount;
}
public JabberClientThread(InetAddress addr) {
System.out.println("Making client " + id);
threadcount++;
try {
socket =
new Socket(addr, MultiJabberServer.PORT);
} catch(IOException e) {
System.err.println("Socket failed");
// If the creation of the socket fails,
// nothing needs to be cleaned up.
}
try {
in =
new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
// Enable auto-flush:
out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())), true);
start();
} catch(IOException e) {
// The socket should be closed on any
// failures other than the socket
// constructor:
try {
socket.close();
} catch(IOException e2) {
System.err.println("Socket not closed");
}
}
// Otherwise the socket will be closed by
// the run() method of the thread.
}
public void run() {
try {
for(int i = 0; i < 25; i++) {
out.println("Client " + id + ": " + i);
String str = in.readLine();
System.out.println(str);
}
out.println("END");
} catch(IOException e) {
System.err.println("IO Exception");
} finally {
// Always close it:
try {
socket.close();
} catch(IOException e) {
System.err.println("Socket not closed");
}
threadcount--; // Ending this thread
}
}
}
public class MultiJabberClient {
static final int MAX_THREADS = 40;
public static void main(String[] args)
throws IOException, InterruptedException {
InetAddress addr =
InetAddress.getByName(null);
while(true) {
if(JabberClientThread.threadCount()
< MAX_THREADS)
new JabberClientThread(addr);
Thread.currentThread().sleep(100);
}
}
} ///:~
The JabberClientThread constructor
takes an InetAddress and uses it to open a Socket. You’re
probably starting to see the pattern: the Socket is always used to create
some kind of Reader and/or Writer (or InputStream and/or
OutputStream) object, which is the only way that the Socket can be
used. (You can, of course, write a class or two to automate this process instead
of doing all the typing if it becomes painful.) Again, start( )
performs thread initialization and calls run( ). Here, messages are
sent to the server and information from the server is echoed to the screen.
However, the thread has a limited lifetime and eventually completes. Note that
the socket is cleaned up if the constructor fails after the socket is created
but before the constructor completes. Otherwise the responsibility for calling
close( ) for the socket is relegated to the run( )
method.
[ Add Comment ]
The threadcount keeps track of how
many JabberClientThread objects currently exist. It is incremented as
part of the constructor and decremented as run( ) exits (which means
the thread is terminating). In MultiJabberClient.main( ), you can
see that the number of threads is tested, and if there are too many, no more are
created. Then the method sleeps. This way, some threads will eventually
terminate and more can be created. You can experiment with MAX_THREADS to
see where your particular system begins to have trouble with too many
connections.
[ Add Comment ]
The examples you’ve seen so far use
the
Transmission
Control Protocol (TCP, also known as
stream-based
sockets), which is designed for ultimate reliability and guarantees that the
data will get there. It allows retransmission of lost data, it provides multiple
paths through different routers in case one goes down, and bytes are delivered
in the order they are sent. All this control and reliability comes at a cost:
TCP has a high overhead.
[ Add Comment ]
There’s a second protocol, called
User
Datagram Protocol (UDP), which doesn’t guarantee that the packets will
be delivered and doesn’t guarantee that they will arrive in the order they
were sent. It’s called an
“unreliable
protocol” (TCP is a
“reliable
protocol”), which sounds bad, but because it’s much faster it can be
useful. There are some applications, such as an audio signal, in which it
isn’t so critical if a few packets are dropped here or there but speed is
vital. Or consider a time-of-day server, where it really doesn’t matter if
one of the messages is lost. Also, some applications might be able to fire off a
UDP message to a server and can then assume, if there is no response in a
reasonable period of time, that the message was lost.
[ Add Comment ]
Typically, you’ll do most of your
direct network programming with TCP, and only occasionally will you use UDP.
There’s a more complete treatment of UDP, including an example, in the
first edition of this book (available on the CD ROM bound into this book, or as
a free download from www.BruceEckel.com).
[ Add Comment ]
It’s possible for an applet to
cause the display of any URL through the Web browser the applet is running
within. You can do this with the following line:
getAppletContext().showDocument(u);
in
which u is the URL object. Here’s a simple example that
redirects you to another Web page. Although you’re just redirected to an
HTML page, you could also redirect to the output of a CGI program.
[ Add Comment ]
//: c15:ShowHTML.java
// <applet code=ShowHTML width=100 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import com.bruceeckel.swing.*;
public class ShowHTML extends JApplet {
JButton send = new JButton("Go");
JLabel l = new JLabel();
public void init() {
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
send.addActionListener(new Al());
cp.add(send);
cp.add(l);
}
class Al implements ActionListener {
public void actionPerformed(ActionEvent ae) {
try {
// This could be a CGI program instead of
// an HTML page.
URL u = new URL(getDocumentBase(),
"FetcherFrame.html");
// Display the output of the URL using
// the Web browser, as an ordinary page:
getAppletContext().showDocument(u);
} catch(Exception e) {
l.setText(e.toString());
}
}
}
public static void main(String[] args) {
Console.run(new ShowHTML(), 100, 50);
}
} ///:~
The beauty of the
URL class is how much it
shields you from. You can connect to Web servers without knowing much at all
about what’s going on under the covers.
[ Add Comment ]
A variation on the above program reads a
file located on the server. In this case, the file is specified by the
client:
//: c15:Fetcher.java
// <applet code=Fetcher width=500 height=300>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import com.bruceeckel.swing.*;
public class Fetcher extends JApplet {
JButton fetchIt= new JButton("Fetch the Data");
JTextField f =
new JTextField("Fetcher.java", 20);
JTextArea t = new JTextArea(10,40);
public void init() {
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
fetchIt.addActionListener(new FetchL());
cp.add(new JScrollPane(t));
cp.add(f); cp.add(fetchIt);
}
public class FetchL implements ActionListener {
public void actionPerformed(ActionEvent e) {
try {
URL url = new URL(getDocumentBase(),
f.getText());
t.setText(url + "\n");
InputStream is = url.openStream();
BufferedReader in = new BufferedReader(
new InputStreamReader(is));
String line;
while ((line = in.readLine()) != null)
t.append(line + "\n");
} catch(Exception ex) {
t.append(ex.toString());
}
}
}
public static void main(String[] args) {
Console.run(new Fetcher(), 500, 300);
}
} ///:~
The creation of the URL object is
similar to the previous example—getDocumentBase( ) is the
starting point as before, but this time the name of the file is read from the
JTextField. Once the URL object is created, its String
version is placed in the JTextArea so we can see what it looks like. Then
an InputStream is procured from the URL, which in this case will
simply produce a stream of the characters in the file. After converting to a
Reader and buffering, each line is read and appended to the
JTextArea. Note that the JTextArea has been placed inside a
JScrollPane so that scrolling is handled automatically.
[ Add Comment ]
There’s actually a lot more to
networking than can be covered in this introductory treatment. Java networking
also provides fairly extensive support for URLs, including protocol handlers for
different types of content that can be discovered at an Internet site. You can
find other Java networking features fully and carefully described in Java
Network Programming by Elliotte Rusty Harold (O’Reilly, 1997).
[ Add Comment ]
It has been estimated that half of all
software development involves client/server operations. A great promise of Java
has been the ability to build platform-independent client/server database
applications. This has come to fruition with
Java
DataBase Connectivity (JDBC).
[ Add Comment ]
One of the major problems with databases
has been the feature wars between the database companies. There is a
“standard” database language,
Structured Query Language
(SQL-92), but you must usually know which database vendor you’re working
with despite the standard. JDBC is designed to be platform-independent, so you
don’t need to worry about the database you’re using while
you’re programming. However, it’s still possible to make
vendor-specific calls from JDBC so you aren’t restricted from doing what
you must.
[ Add Comment ]
One place where programmers may need to
use SQL type names is in the SQL TABLE
CREATE statement when they are creating a new
database table and defining the SQL type for each column. Unfortunately there
are significant variations between SQL types supported by different database
products. Different databases that support SQL types with the same semantics and
structure may give those types different names. Most major databases support an
SQL data type for large binary values: in Oracle this type is called a
LONG RAW,
Sybase calls it
IMAGE,
Informix calls it
BYTE, and
DB2 calls it LONG VARCHAR FOR BIT
DATA. Therefore, if database portability is a
goal you should try to use only generic SQL type identifiers.
[ Add Comment ]
Portability is an issue when writing for
a book where readers may be testing the examples with all kinds of unknown data
stores. I have tried to write these examples to be as portable as possible. You
should also notice that the database-specific code has been isolated in order to
centralize any changes that you may need to perform to get the examples
operational in your environment.
[ Add Comment ]
JDBC, like many of the APIs in Java, is
designed for simplicity. The method calls you make correspond to the logical
operations you’d think of doing when gathering data from a database:
connect to the database, create a statement and execute the query, and look at
the result set.
[ Add Comment ]
To allow this platform independence, JDBC
provides a driver manager that dynamically maintains all the driver
objects that your database queries will need. So if you have three different
kinds of vendor databases to connect to, you’ll need three different
driver objects. The driver objects register themselves with the driver manager
at the time of loading, and you can force the loading using
Class.forName( ).
[ Add Comment ]
All
this information is combined into one string, the “database URL.”
For example, to connect through the ODBC subprotocol to a database identified as
“people,” the database URL could be:
[ Add Comment ]
String dbUrl = "jdbc:odbc:people";
If you’re connecting across a
network, the database URL will contain the connection information identifying
the remote machine and can become a bit intimidating. Here is an example of a
CloudScape database being called from a remote client utilizing RMI:
[ Add Comment ]
jdbc:rmi://192.168.170.27:1099/jdbc:cloudscape:db
This database URL is really two jdbc
calls in one. The first part
“jdbc:rmi://192.168.170.27:1099/”
uses RMI to make the connection to the remote database engine listening on port
1099 at IP Address 192.168.170.27. The second part of the URL,
“jdbc:cloudscape:db”
conveys the more typical settings using the subprotocol and database name but
this will only happen after the first section has made the connection via RMI to
the remote machine.
[ Add Comment ]
When you’re ready to connect to the
database, call the static method
DriverManager.getConnection( ) and pass it the database URL, the
user name, and a password to get into the database. You get back a
Connection object that you can then use to query and manipulate the
database.
[ Add Comment ]
The following example opens a database of
contact information and looks for a person’s last name as given on the
command line. It selects only the names of people that have email addresses,
then prints out all the ones that match the given last name:
//: c15:jdbc:Lookup.java
// Looks up email addresses in a
// local database using JDBC.
import java.sql.*;
public class Lookup {
public static void main(String[] args)
throws SQLException, ClassNotFoundException {
String dbUrl = "jdbc:odbc:people";
String user = "";
String password = "";
// Load the driver (registers itself)
Class.forName(
"sun.jdbc.odbc.JdbcOdbcDriver");
Connection c = DriverManager.getConnection(
dbUrl, user, password);
Statement s = c.createStatement();
// SQL code:
ResultSet r =
s.executeQuery(
"SELECT FIRST, LAST, EMAIL " +
"FROM people.csv people " +
"WHERE " +
"(LAST='" + args[0] + "') " +
" AND (EMAIL Is Not Null) " +
"ORDER BY FIRST");
while(r.next()) {
// Capitalization doesn't matter:
System.out.println(
r.getString("Last") + ", "
+ r.getString("fIRST")
+ ": " + r.getString("EMAIL") );
}
s.close(); // Also closes ResultSet
}
} ///:~
You can see the creation of the database
URL as previously described. In this example, there is no password protection on
the database so the user name and password are empty strings.
[ Add Comment ]
Once the connection is made with
DriverManager.getConnection( ), you can use the resulting
Connection object to create a Statement object using the
createStatement( )
method. With the resulting
Statement, you can call
executeQuery( ),
passing in a string containing an SQL-92 standard SQL statement. (You’ll
see shortly how you can generate this statement automatically, so you
don’t have to know much about SQL.)
[ Add Comment ]
The executeQuery( ) method
returns a ResultSet
object, which is an iterator: the next( ) method moves the iterator
to the next record in the statement, or returns false if the end of the
result set has been reached. You’ll always get a ResultSet object
back from executeQuery( ) even if a query results in an empty set
(that is, an exception is not thrown). Note that you must call
next( ) once before trying to read any record data. If the result
set is empty, this first call to next( ) will return false.
For each record in the result set, you can select the fields using (among other
approaches) the field name as a string. Also note that the capitalization of the
field name is ignored—it doesn’t matter with an SQL database. You
determine the type you’ll get back by calling
getInt( ),
getString( ),
getFloat( ), etc. At
this point, you’ve got your database data in Java native format and can do
whatever you want with it using ordinary Java code.
[ Add Comment ]
With JDBC, understanding the code is
relatively simple. The confusing part is making it work on your particular
system. The reason this is confusing is that it requires you to figure out how
to get your JDBC driver to load properly, and how to set up a database using
your database administration software.
[ Add Comment ]
Of course, this process can vary
radically from machine to machine, but the process I used to make it work under
32-bit Windows might give you clues to help you attack your own situation.
[ Add Comment ]
The program above contains the
statement:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
This implies a directory structure, which
is deceiving. With this particular installation of JDK 1.1, there was no file
called JdbcOdbcDriver.class, so if you looked at this example and went
searching for it you’d be frustrated. Other published examples use a
pseudo name, such as “myDriver.ClassName,” which is less than
helpful. In fact, the load statement above for the jdbc-odbc driver (the only
one that actually comes with the JDK) appears in only a few places in the online
documentation (in particular, a page labeled “JDBC-ODBC Bridge
Driver”). If the load statement above doesn’t work, then the name
might have been changed as part of a Java version change, so you should hunt
through the documentation again.
[ Add Comment ]
If the load statement is wrong,
you’ll get an exception at this point. To test whether your driver load
statement is working correctly, comment out the code after the statement and up
to the catch clause; if the program throws no exceptions it means that
the driver is loading properly.
[ Add Comment ]
Again, this is specific to 32-bit
Windows; you might need to do some research to figure it out for your own
platform.
[ Add Comment ]
First, open the control panel. You might
find two icons that say “ODBC.” You must use the one that says
“32bit ODBC,” since the other one is for backward compatibility with
16-bit ODBC software and will produce no results for JDBC. When you open the
“32bit ODBC” icon, you’ll see a tabbed dialog with a number of
tabs, including “User DSN,” “System DSN,” “File
DSN,” etc., in which “DSN” means “Data Source
Name.” It turns out that for the JDBC-ODBC bridge, the only place where
it’s important to set up your database is “System DSN,” but
you’ll also want to test your configuration and create queries, and for
that you’ll also need to set up your database in “File DSN.”
This will allow the Microsoft Query tool (that comes with Microsoft Office) to
find the database. Note that other query tools are also available from other
vendors.
[ Add Comment ]
The most interesting database is one that
you’re already using. Standard ODBC supports a number of different file
formats including such venerable workhorses as DBase. However, it also includes
the simple “comma-separated ASCII” format, which virtually every
data tool has the ability to write. In my case, I just took my
“people” database that I’ve been maintaining for years using
various contact-management tools and exported it as a comma-separated ASCII file
(these typically have an extension of .csv). In the “System
DSN” section I chose “Add,” chose the text driver to handle my
comma-separated ASCII file, and then un-checked “use current
directory” to allow me to specify the directory where I exported the data
file.
[ Add Comment ]
You’ll notice when you do this that
you don’t actually specify a file, only a directory. That’s because
a database is typically represented as a collection of files under a single
directory (although it could be represented in other forms as well). Each file
usually contains a single table, and the SQL statements can produce results that
are culled from multiple tables in the database (this is called a
join). A database that
contains only a single table (like my “people” database) is usually
called a
flat-file
database. Most problems that go beyond the simple storage and retrieval of
data generally require multiple tables that must be related by joins to produce
the desired results, and these are called
relational
databases.
[ Add Comment ]
To test the configuration you’ll
need a way to discover whether the database is visible from a program that
queries it. Of course, you can simply run the JDBC program example above, up to
and including the statement:
Connection c = DriverManager.getConnection( dbUrl, user, password);
If an exception is thrown, your
configuration was incorrect.
[ Add Comment ]
However, it’s useful to get a
query-generation tool involved at this point. I used Microsoft Query that came
with Microsoft Office, but you might prefer something else. The query tool must
know where the database is, and Microsoft Query required that I go to the ODBC
Administrator’s “File DSN” tab and add a new entry there,
again specifying the text driver and the directory where my database lives. You
can name the entry anything you want, but it’s helpful to use the same
name you used in “System DSN.”
[ Add Comment ]
Once you’ve done this, you will see
that your database is available when you create a new query using your query
tool.
[ Add Comment ]
The query that I created using Microsoft
Query not only showed me that my database was there and in good order, but it
also automatically created the SQL code that I needed to insert into my Java
program. I wanted a query that would search for records that had the last name
that was typed on the command line when starting the Java program. So as a
starting point, I searched for a specific last name, “Eckel.” I also
wanted to display only those names that had email addresses associated with
them. The steps I took to create this query were:
The result of this
query will show you whether you’re getting what you want.
[ Add Comment ]
Now you can press the SQL button and
without any research on your part, up will pop the correct SQL code, ready for
you to cut and paste. For this query, it looked like this:
SELECT people.FIRST, people.LAST, people.EMAIL FROM people.csv people WHERE (people.LAST='Eckel') AND (people.EMAIL Is Not Null) ORDER BY people.FIRST
Especially with more complicated queries
it’s easy to get things wrong, but by using a query tool you can
interactively test your queries and automatically generate the correct code.
It’s hard to argue the case for doing this by hand.
[ Add Comment ]
You’ll notice that the code above
looks different from what’s used in the program. That’s because the
query tool uses full qualification for all of the names, even when there’s
only one table involved. (When more than one table is involved, the
qualification prevents collisions between columns from different tables that
have the same names.) Since this query involves only one table, you can
optionally remove the “people” qualifier from most of the names,
like this:
[ Add Comment ]
SELECT FIRST, LAST, EMAIL FROM people.csv people WHERE (LAST='Eckel') AND (EMAIL Is Not Null) ORDER BY FIRST
In addition, you don’t want this
program to be hard coded to look for only one name. Instead, it should hunt for
the name given as the command-line argument. Making these changes and turning
the SQL statement into a dynamically-created String produces:
[ Add Comment ]
"SELECT FIRST, LAST, EMAIL " + "FROM people.csv people " + "WHERE " + "(LAST='" + args[0] + "') " + " AND (EMAIL Is Not Null) " + "ORDER BY FIRST");
SQL has another way to insert names into
a query called
stored
procedures, which is used for speed. But for much of your database
experimentation and for your first cut, building your own query strings in Java
is fine.
[ Add Comment ]
You can see from this example that by
using the tools currently available—in particular the query-building
tool—database programming with SQL and JDBC can be quite straightforward.
[ Add Comment ]
It’s more useful to leave the
lookup program running all the time and simply switch to it and type in a name
whenever you want to look someone up. The following program creates the lookup
program as an application/applet, and it also adds name completion so the data
will show up without forcing you to type the entire last name:
//: c15:jdbc:VLookup.java
// GUI version of Lookup.java.
// <applet code=VLookup
// width=500 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.sql.*;
import com.bruceeckel.swing.*;
public class VLookup extends JApplet {
String dbUrl = "jdbc:odbc:people";
String user = "";
String password = "";
Statement s;
JTextField searchFor = new JTextField(20);
JLabel completion =
new JLabel(" ");
JTextArea results = new JTextArea(40, 20);
public void init() {
searchFor.getDocument().addDocumentListener(
new SearchL());
JPanel p = new JPanel();
p.add(new Label("Last name to search for:"));
p.add(searchFor);
p.add(completion);
Container cp = getContentPane();
cp.add(p, BorderLayout.NORTH);
cp.add(results, BorderLayout.CENTER);
try {
// Load the driver (registers itself)
Class.forName(
"sun.jdbc.odbc.JdbcOdbcDriver");
Connection c = DriverManager.getConnection(
dbUrl, user, password);
s = c.createStatement();
} catch(Exception e) {
results.setText(e.toString());
}
}
class SearchL implements DocumentListener {
public void changedUpdate(DocumentEvent e){}
public void insertUpdate(DocumentEvent e){
textValueChanged();
}
public void removeUpdate(DocumentEvent e){
textValueChanged();
}
}
public void textValueChanged() {
ResultSet r;
if(searchFor.getText().length() == 0) {
completion.setText("");
results.setText("");
return;
}
try {
// Name completion:
r = s.executeQuery(
"SELECT LAST FROM people.csv people " +
"WHERE (LAST Like '" +
searchFor.getText() +
"%') ORDER BY LAST");
if(r.next())
completion.setText(
r.getString("last"));
r = s.executeQuery(
"SELECT FIRST, LAST, EMAIL " +
"FROM people.csv people " +
"WHERE (LAST='" +
completion.getText() +
"') AND (EMAIL Is Not Null) " +
"ORDER BY FIRST");
} catch(Exception e) {
results.setText(
searchFor.getText() + "\n");
results.append(e.toString());
return;
}
results.setText("");
try {
while(r.next()) {
results.append(
r.getString("Last") + ", "
+ r.getString("fIRST") +
": " + r.getString("EMAIL") + "\n");
}
} catch(Exception e) {
results.setText(e.toString());
}
}
public static void main(String[] args) {
Console.run(new VLookup(), 500, 200);
}
} ///:~
Much of the database logic is the same,
but you can see that a DocumentListener is added to listen to the
JTextField (see the javax.swing.JTextField entry in the Java HTML
documentation from java.sun.com for details), so that whenever you type a
new character it first tries to do a name completion by looking up the last name
in the database and using the first one that shows up. (It places it in the
completion JLabel, and uses that as the lookup text.) This way, as
soon as you’ve typed enough characters for the program to uniquely find
the name you’re looking for, you can stop.
[ Add Comment ]
When you browse the online documentation
for JDBC it can seem daunting. In particular, in the
DatabaseMetaData
interface—which is just huge, contrary to most of the interfaces you see
in Java—there are methods such as
dataDefinitionCausesTransactionCommit( ),
getMaxColumnNameLength( ), getMaxStatementLength( ),
storesMixedCaseQuotedIdentifiers( ),
supportsANSI92IntermediateSQL( ),
supportsLimitedOuterJoins( ), and so on. What’s this all
about?
[ Add Comment ]
As mentioned earlier, databases have
seemed from their inception to be in a constant state of turmoil, primarily
because the demand for database applications, and thus database tools, is so
great. Only recently has there been any convergence on the common language of
SQL (and there are plenty of other database languages in common use). But even
with an SQL “standard” there are so many variations on that theme
that JDBC must provide the large DatabaseMetaData interface so that your
code can discover the capabilities of the particular “standard” SQL
database that it’s currently connected to. In short, you can write simple,
transportable SQL, but if you want to optimize speed your coding will multiply
tremendously as you investigate the capabilities of a particular vendor’s
database.
[ Add Comment ]
This, of course, is not Java’s
fault. The discrepancies between database products are just something that JDBC
tries to help compensate for. But bear in mind that your life will be easier if
you can either write generic queries and not worry quite as much about
performance, or, if you must tune for performance, know the platform
you’re writing for so you don’t need to write all that investigation
code.
[ Add Comment ]
A more interesting
example[73]
involves a multitable database that resides on a server. Here, the database is
meant to provide a repository for community activities and to allow people to
sign up for these events, so it is called the Community Interests Database
(CID). This example will only provide an overview of the database and its
implementation, and is not intended to be an in-depth tutorial on database
development. There are numerous books, seminars, and software packages that will
help you in the design and development of a database.
[ Add Comment ]
In addition, this example presumes the
prior installation of an SQL database on a server (although it could also be run
on a local machine), and the interrogation and discovery of an appropriate JDBC
driver for that database. Several free SQL databases are available, and some are
even automatically installed with various flavors of Linux. You are responsible
for making the choice of database and locating the JDBC driver; the example here
is based on an SQL database system called “Cloudscape.”
[ Add Comment ]
To keep changes in the connection
information simple, the database driver, database URL, user name, and password
are placed in a separate class:
//: c15:jdbc:CIDConnect.java
// Database connection information for
// the community interests database (CID).
public class CIDConnect {
// All the information specific to CloudScape:
public static String dbDriver =
"COM.cloudscape.core.JDBCDriver";
public static String dbURL =
"jdbc:cloudscape:d:/docs/_work/JSapienDB";
public static String user = "";
public static String password = "";
} ///:~
In this example, there is no password
protection on the database so the user name and password are empty strings.
[ Add Comment ]
The database consists of a set of tables
that have a structure as shown here:

“Members” contains community
member information, “Events” and “Locations” contain
information about the activities and where they take place, and
“Evtmems” connects events and members that would like to attend that
event. You can see that a data member in one table produces a key in another
table.
[ Add Comment ]
The following class contains the SQL
strings that will create these database tables (refer to an SQL guide for an
explanation of the SQL code):
[ Add Comment ]
//: c15:jdbc:CIDSQL.java
// SQL strings to create the tables for the CID.
public class CIDSQL {
public static String[] sql = {
// Create the MEMBERS table:
"drop table MEMBERS",
"create table MEMBERS " +
"(MEM_ID INTEGER primary key, " +
"MEM_UNAME VARCHAR(12) not null unique, "+
"MEM_LNAME VARCHAR(40), " +
"MEM_FNAME VARCHAR(20), " +
"ADDRESS VARCHAR(40), " +
"CITY VARCHAR(20), " +
"STATE CHAR(4), " +
"ZIP CHAR(5), " +
"PHONE CHAR(12), " +
"EMAIL VARCHAR(30))",
"create unique index " +
"LNAME_IDX on MEMBERS(MEM_LNAME)",
// Create the EVENTS table
"drop table EVENTS",
"create table EVENTS " +
"(EVT_ID INTEGER primary key, " +
"EVT_TITLE VARCHAR(30) not null, " +
"EVT_TYPE VARCHAR(20), " +
"LOC_ID INTEGER, " +
"PRICE DECIMAL, " +
"DATETIME TIMESTAMP)",
"create unique index " +
"TITLE_IDX on EVENTS(EVT_TITLE)",
// Create the EVTMEMS table
"drop table EVTMEMS",
"create table EVTMEMS " +
"(MEM_ID INTEGER not null, " +
"EVT_ID INTEGER not null, " +
"MEM_ORD INTEGER)",
"create unique index " +
"EVTMEM_IDX on EVTMEMS(MEM_ID, EVT_ID)",
// Create the LOCATIONS table
"drop table LOCATIONS",
"create table LOCATIONS " +
"(LOC_ID INTEGER primary key, " +
"LOC_NAME VARCHAR(30) not null, " +
"CONTACT VARCHAR(50), " +
"ADDRESS VARCHAR(40), " +
"CITY VARCHAR(20), " +
"STATE VARCHAR(4), " +
"ZIP VARCHAR(5), " +
"PHONE CHAR(12), " +
"DIRECTIONS VARCHAR(4096))",
"create unique index " +
"NAME_IDX on LOCATIONS(LOC_NAME)",
};
} ///:~
The following program uses the
CIDConnect and CIDSQL information to load the JDBC driver, make a
connection to the database, and then create the table structure diagrammed
above. To connect with the database, you call the static method
DriverManager.getConnection( ), passing it the database URL, the
user name, and a password to get into the database. You get back a
Connection object that you can use to query and manipulate the database.
Once the connection is made you can simply push the SQL to the database, in this
case by marching through the CIDSQL array. However, the first time this
program is run, the “drop table” command will fail, causing an
exception, which is caught, reported, and then ignored. The reason for the
“drop table” command is to allow easy experimentation: you can
modify the SQL that defines the tables and then rerun the program, causing the
old tables to be replaced by the new.
[ Add Comment ]
In this example, it makes sense to let
the exceptions be thrown out to the console:
//: c15:jdbc:CIDCreateTables.java