The MSQL Java Package


In this section will will write our own SQL client to connect to our MSQL server and manipulate some data. To make our live easier, we will use the "MSQL Java" package, written by Darryl Collins from the University of Queensland in Australia. The very first thing you must do is to download that package to your own computer.
Click here to download the MSQL Java package
The package consists of several *.java and corresponding *.class files in zipped format. Make sure you save those files into a separate, new directory on your computer. Once you saved the above file, you need to unzip it (for questions, post !). All *.java and *.class files must reside in a new directory. You can, if you like, delete the zip file after you unzipped the files.

All applets and programs that you write using the MSQL Java package must be saved in the same directory as the MSQL Java files downloaded above. For a tutorial written by Darryl Collins, check a separate link for this lecture. Here, we will create a few applets that manipulate tables in the database "csas4083".

Note: I will assume the following for the remaining examples:
you have downloaded an unzipped the MSQL Java package, described above
you have created a table in the "csas4083" database, using the "msql client", as described in the previous lecture.
your database contains four fields, "fname", "lname", "phone", and "email"
your database is named "username_address", where "username" is your Sciris user name
In all examples below, I will use my own table, named "wachsmut_address". You should substitute your own table name whenever appropriate. If you did not create your own table, however, feel free to manipulate my table instead, if you must. For details on the classes and methods available in the MSQL Java package, check the appropriate link for this lecture (you should download that MSQL API to your local computer as well).


Example 1: Let's retrieve all last names from our table, and display them in a list. We will create an applet with one "Load" button. When clicking on that button, our applet will connect to the MSQL server, retrieve all last names, and place them into a list. Note that only the last names will be retrieved. Here is the code:
import java.applet.*;
import java.awt.*;

public class AddressNames extends Applet
{
   private Button load = new Button("Load");
   private List names = new List();
   private Label status = new Label("Click on Load to connect");

   private Msql server = new Msql();

   public void init()
   {
      Panel buttons = new Panel();
      buttons.setLayout(new FlowLayout());
      buttons.add(load);
      
      setLayout(new BorderLayout());
      add("North", status);
      add("Center", names);
      add("South", buttons);
   }

   public boolean action(Event e, Object o)
   {
      if (e.target == load)
         handleLoad();
      return true;
   }

   private void handleLoad()
   {
      try
      {
         status.setText("Connecting, please wait ...");
         server.Connect("sciris.shu.edu","wachsmut");
         status.setText("Selecting database, please wait ...");
         server.SelectDB("csas4083");
         status.setText("Retrieving data, please wait ...");
         MsqlResult result = server.Query("select lname from wachsmut_address");
        
         String row[]; 
         int rows = result.NumRows(); 

         while((row = result.FetchRow()) != null) 
            names.addItem(row[0]); 
         status.setText("List of last names in the database:");
         server.Close();  
      }
      catch(MsqlException e)
      {
         status.setText("Unknown error. Check Java console for details."); 
         System.err.println(e);
      }
   }
}
Here is the applet in action. Note that the applet must be stored on Sciris (because of security restrictions), while this homepage is on "www.shu.edu" - thus, I have to use the "CODEBASE" option in the APPLET tag ...

There are the usual setup methods in the init method, and the standard action method. One nice feature is the use of a "Label" to display status information. I could have used the browser's status line as well (with showStatus("blah blah")), but this is somewhat nicer.

Don't forget to change the name of the database, and the name under which to connect, to your own name if you created your own table.

The main code is in the "handleLoad" method. Here's what's happening (removing the status.setText() lines and the "catch" block):

server.Connect("sciris.shu.edu","wachsmut"); Connects to MSQL database server running on "sciris.shu.edu" as user "wachsmut". No password is required to connect to the Msql server in this case. Note that for this to work, your applet must be loaded from the same machine "sciris.shu.edu"; otherwise, applet security restrictions will prevent you from making a connection. You can, however, use the "appletviewer": it allows you to relax the security restrictions by clickin on "Applet | Properties" and changing the default entries. If a connection can not be established, the "catch" block will display an error message in the status line.
server.SelectDB("csas4083"); Once connected, this command requests that the database "csas4083" is selected for further processing. You must always select a database first, before processing the table info.
MsqlResult result = server.Query("select lname from wachsmut_address");  Sending the SQL query. This is the same query that I used to type in when using the "msql client" on Sciris. However, the user of my applet does not need to know any SQL. They just click on the button, and the appropriate command is sent automatically. When successful, this command will return the data in an object of type MsqlResult (which is, bascially, a table representing all or some of the data). In our case, our table will have only one column, since we only selected the "lname" column. It will have as many rows as there are data entries.
String row[];
int rows = result.NumRows();
while((row = result.FetchRow()) != null)
   names.addItem(row[0]);
This is the code to retrieve the data from the field "result" of type "MsqlResult". We need an array of Strings for each row (actually, right now a row has only one column, so an array may not be necessary).  Then, in the while loop, we use the "FetchRow" method of the "MsqlResult" to fetch a row of data. We store it in "row", and keep doing that until the "row" is null, i.e. we have looked at all the data. The "FetchRow" method automatically starts with the first row, and each subsequent call to that method will retrieve the next row automatically, or return null if there is no more next row. Inside the while loop, the 0th entry of the array of Strings row is added to our list of names. It's the 0th entry, because each row only has one column anyway, in this case.
server.Close();  Closing the connection to the Msql server. That should always be used after the data has been successfully retrieved or manipulated.
 By the way, what will happen if you click on "Load", wait until the names are there, then click on "Load" again ? That's not, of course, what we want, so how could you change the applet to remedy the situation ? 


Example 2: Now let's retrieve all information, but this time we'll store it in a TextArea instead of a List. We do that because a TextArea can be scrolled left and right, while a List can only go up and down. Here's the code, with the major modifications in the "handleLoad" method.
import java.applet.*;
import java.awt.*;

public class Addresses extends Applet
{
   private Button load = new Button("Load");
   private TextArea names = new TextArea();
   private Label status = new Label("Click on Load to connect");

   private Msql server = new Msql();

   public void init()
   {
      Panel buttons = new Panel();
      buttons.setLayout(new FlowLayout());
      buttons.add(load);
      
      setLayout(new BorderLayout());
      add("North", status);
      add("Center", names);
      add("South", buttons);
   }

   public boolean action(Event e, Object o)
   {
      if (e.target == load)
         handleLoad();
      return true;
   }

   private void handleLoad()
   {
      try
      {
         status.setText("Connecting, please wait ...");
         server.Connect("sciris.shu.edu","wachsmut");
         status.setText("Selecting database, please wait ...");
         server.SelectDB("csas4083");
         status.setText("Retrieving data, please wait ...");
         MsqlResult result = server.Query("select * from wachsmut_address");
        
         String row[]; 
         int rows = result.NumRows(); 
         int cols = result.NumFields();

         names.setText("");
         while((row = result.FetchRow()) != null) 
         {
            names.appendText(row[0] + " " + row[1] + "\n");
            if (!row[2].equals(""))
               names.appendText("\t Phone: " + row[2] +"\n");
            if (!row[3].equals("")) 
               names.appendText("\t Email: " + row[3] + "\n"); 
         }
         status.setText("List of last names in the database:");
         server.Close();  
      }
      catch(MsqlException e)
      {
         status.setText("Unknown error. Check Java console for details."); 
         System.err.println(e);
      }
   }
}
Here's the applet in action.


The changes were pretty simple: I changed the type of the "names" Field from "List" to "TextArea", and the "addItem" methods in the "handleLoad" method to "appendText". Then I also append row[i] objects, with the approriate label in front, if they are present, to get a reasonably nice display format. Remember that "\n" stands for "new line" and "\t" for tab character. Don't forget to change the name of the database, and the name under which to connect, to your own name if you created your own table.


Example 3: Now let's change our AddressBook applet so that we can enter new information to our address list, as well as delete the existing information. We will this time display only one row at a time, but we will add buttons to "Add", "Change", or "Delete" the information, as well as a "Next" and a "Previous" button to move around in our database. The data is loaded as soon as the applet initializes, so the "Load" button is no longer needed.

Note that the variable "result" is now a field in our class, so that every method can access that information. Also, another variable 'currentRow' has been added as a field. For the rest, read the code carefully, figure out what it does, and why it might work, and if you have any questions, POST THEM !
 
import java.applet.*;
import java.awt.*;

public class AddressBook extends Applet
{
   private static final String HOST  = "sciris.shu.edu";
   private static final String USER  = "wachsmut";
   private static final String DBASE = "csas4083";
   private static final String TABLE = "wachsmut_address";
   private static final int    nTexts = 4;

   private Button back = new Button("<=");
   private Button next = new Button("=>");
   private Button add  = new Button("Add");
   private Button del  = new Button("Delete");
   private Button mod  = new Button("Change");

   private TextField texts[] = {new TextField() , new TextField(), 
                                 new TextField(),  new TextField() };
   private Label labels[] = {new Label("First Name:"), new Label("Last Name"),
                             new Label("Phone Number"), new Label("Email Address") };
   private MsqlFieldDesc fields[] = null;

   private Label status = new Label("Click on Load to connect");
   private MsqlResult result = null;
   private int currentRow = 0;
   private Msql server = new Msql();

   public void init()
   {
      Panel buttons = new Panel();
      buttons.setLayout(new FlowLayout());
      buttons.add(back);
      buttons.add(next);
      buttons.add(add);
      buttons.add(del);
      buttons.add(mod);
    
      Panel dataGrid = new Panel();
      dataGrid.setLayout(new GridLayout(4, 2));
      for (int i = 0; i < nTexts; i++)
      {
         dataGrid.add(labels[i]);
         dataGrid.add(texts[i]);
      }

      setLayout(new BorderLayout());
      add("North", status);
      add("Center", dataGrid);
      add("South", buttons);

      sendQuery("select * from " + TABLE);
      fields = result.ListFields();
      showRow();
   }
   public boolean action(Event e, Object o)
   {
      if (e.target == next)
         handleNext();
      else if (e.target == back)
         handleBack();
      else if (e.target == add)
         handleAdd();
      else if (e.target == del)
         handleDel();
      else if (e.target == mod)
         handleMod();
      return true;
   }
   private void handleNext()
   {
      if (currentRow < result.NumRows() - 1)
      {
         currentRow++;
         showRow();
      }
      else
         status.setText("No more data available.");
   }
   private void handleBack()
   {
      if (currentRow > 0)
      {
         currentRow--;
         showRow();
      }
      else
         status.setText("Already at first data set.");
   }
   private void handleAdd()
   {
      if (!texts[1].getText().trim().equals(""))
      {  
         String sqlQuery = new String("insert into " + TABLE + " values (");
         for (int i = 0; i < nTexts-1; i++)
            sqlQuery += "'" + texts[i].getText().trim() +"',";
         sqlQuery += "'" + texts[nTexts-1].getText().trim() +"')";

         sendQuery(sqlQuery);
         sendQuery("select * from " + TABLE);

         currentRow = result.NumRows()-1;
         showRow();
      }
      else
         status.setText("You must provide at least a last name.");
   }

   private void handleDel()
   {
      String sqlQuery = new String("delete from " + TABLE + " where ");
      for (int i = 0; i < nTexts-1; i++)
         sqlQuery += fields[i].FieldName()+ " = '" + texts[i].getText().trim() + "' and ";
      sqlQuery += fields[nTexts-1].FieldName() + " = '" + texts[nTexts-1].getText().trim() +"'";

      sendQuery(sqlQuery);
      sendQuery("select * from " + TABLE);
      if (currentRow == result.NumRows())
         currentRow--;
      showRow();
   }

   private void handleMod()
   {
      result.DataSeek(currentRow);
      String row[] = result.FetchRow();

      String sqlQuery = new String("update " + TABLE + " set ");
      for (int i = 0; i < nTexts - 1; i++)
         sqlQuery += fields[i].FieldName() + " = '" + texts[i].getText().trim() + "', ";
      sqlQuery += fields[nTexts-1].FieldName() + " = '" + texts[nTexts-1].getText().trim() +"'";

      sqlQuery += " where ";
      for (int i = 0; i < nTexts - 1; i++)      
         sqlQuery += fields[i].FieldName() + " = '" + row[i] + "' and ";
      sqlQuery += fields[nTexts-1].FieldName() + " = '" + row[nTexts-1] +"'";

      sendQuery(sqlQuery);
      sendQuery("select * from " + TABLE);
      showRow();      
   }
   private void showRow()
   {
      result.DataSeek(currentRow);
      String row[] = result.FetchRow();
      for (int i = 0; i < nTexts; i++)
         texts[i].setText(row[i]);
      status.setText("Showing row " + (currentRow+1) + " of " + result.NumRows());
   }

   private void sendQuery(String query)
   {
      try
      {
         status.setText("Connecting, please wait ...");
         server.Connect(HOST,USER);
         server.SelectDB(DBASE);
         result = server.Query(query);
         server.Close();  
      }
      catch(MsqlException e)
      {
         status.setText("Unknown error. Check Java console for details.");
         System.err.println("Sending: " + query);
         System.err.println(e);
         result = null;
      }
   }
}

Here's the table in action. Note that are dealing with "life" data: any change, addition, or deletion you make will immediately be saved. Everybody else will then see those changes. So, be careful and don't delete all of the data. Only delete data that you yourself has entered.


Note: The above code may look rather complicated. However, it is written to work for other tables with minimal changes. On the other hand, the code is also *very* slow: after each change, addition, or deletion, the entire table is loaded again. That is not very efficient, but it works. Feel free, of course, to improve on the code. Just one explanation might help: let's look at the code for the handleAdd method:

Recall that the database has four fields: fname, lname, phone, and email. In the first line, we check if there's something in the second textfield:

      if (!texts[1].getText().trim().equals(""))
If so, we start building an SQL query string by reading what is entered in the text fields and appending it to the ongoing query string:
         String sqlQuery = new String("insert into " + TABLE + " values (");
         for (int i = 0; i < nTexts-1; i++)
            sqlQuery += "'" + texts[i].getText().trim() +"',";
         sqlQuery += "'" + texts[nTexts-1].getText().trim() +"')";
We need to handle the last text field separately, because it needs to end with a closing bracket, different from the prior ones. Next, we submit that query string to the SQL server, using the private method sendQuery:
         sendQuery(sqlQuery);
After the query is sent, the table has changed. We reload the entire table to make sure that what the user sees is the same data that is contained in the table. 
        sendQuery("select * from " + TABLE);
We then show the last row, which is the row that has just been added.
         currentRow = result.NumRows()-1;
         showRow();
We should really also add a "Search" feature, but this shall suffice - after all, all good things must come to an end sometimes - which, they herewith do.


The End - bgw