JavaData & JavaThe Java Database Control in BEA Weblogic

The Java Database Control in BEA Weblogic

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.


This is Chapter 6: The Database Control from the book BEA WebLogic Workshop Kick Start (ISBN:0-672-32417-2) written by Joseph Weber and Mark Wutka, published by Sams Publishing.


Chapter 6: The Database Control

In This Chapter

  • Creating a Database Control

  • Defining a Database Connection

  • Creating an SQL String

  • Including Variables

  • Getting a Result Set

  • Executing Multiple Statements

  • A Sample Application

Many Web services act as a front end to a database. That is, some Web services simply act as a means to store data in the database and to retrieve it. Other Web services use a database as part of the overall application functionality. In these cases, the database can contain vital application data, but the Web service provides data validation and additional business logic. Because database operations are very common and often tedious, Workshop provides a database control that handles the tedious parts.

Creating a Database Control

To add a database control to your application, select Service, Add Control, and Add Database Control. You will see the Add Database Control dialog box, as shown in Figure 6.1.

Figure 6.1

You can add a database control to handle database access.

As with other controls, you must give the control a variable name and then either specify an existing control, or create a new control. When you create a new control, you must also specify a data source. A data source is a factory for creating JDBC database connections, similar to the JDBC DriverManager class. One of the advantages of a data source is that you can locate it using JNDI, giving you a central place to keep your database URLs. Data sources were introduced as part of Java 2 Enterprise Edition and represent a cleaner way to access database connections.

A data source gets its connections from a JDBC connection pool. A connection pool can keep several database connections open at one time. Without a connection pool, you might encounter delays while the JDBC driver sets up a new connection each time you need one. Because the pool maintains a group of reusable connections, your program is more efficient when allocating a connection.

Defining a Database Connection

To use the database control, you must define a data source. To define a data source in WebLogic, you must first define a connection pool. The easiest way to define a connection pool is through the WebLogic server console. Figure 6.2 shows the page for creating a connection pool. Notice that you must supply the JDBC connection URL and the JDBC driver class.

Figure 6.2

You can create connection pools using the WebLogic console.

After you create a connection pool, you can create a data source that uses the connection pool. The WebLogic console makes it easy to create connection pools, as shown in Figure 6.3. Simply give the data source a name and enter the name of the connection pool that the data source should use to obtain database connections.

Figure 6.3

You can create data sources using the WebLogic console.

The WebLogic samples server includes a sample data source that you can use for test programs. The data source name is cgSampleDataSource. You can use this data source to create new database tables and then execute SQL statements to manipulate these tables.

Creating an SQL String

To execute database statements from a database control, you first create a method that accepts any parameters you want to pass to the database statement. For example, you might want to pass an order number to search for, or a customer's new address. Next, you use the @jws:sql JavaDoc tag to create the database statement.

You can find more information on SQL, including a tutorial and links to the SQL specification, on the support page for this book at http://www.samspublishing.com.

The only attribute in the @jws:sql tag is statement, which contains the database statement. For example:

/**
 * @jws:sql statement="select * from accounts"
 */

If a statement is long, you can split it across multiple lines. Instead of enclosing the statement in quotes, use the statement:: form of the attribute using :: to end the statement, like this:

/**
 * @jws:sql statement:: update personnel set
 *   title='Manager', department='Toys'
 *   where id='123456'
 * ::
 */

Selecting Values

The general format for the SQL SELECT statement is

SELECT fields FROM table

The fields can either be * to indicate all fields in the table, or a comma-separated list like first_name, last_name, city, state, zip.

One of the most common clauses in a SELECT statement is the WHERE clause, which narrows the selection. The WHERE clause contains a Boolean expression, which can contain comparison operators like =, <, and >. You can also combine multiple expressions with AND, OR, and NOT. To test whether a column has no value, use IS NULL. Here are some sample queries:

SELECT * FROM personnel WHERE department='123456'
SELECT * FROM personnel WHERE spouse IS NULL
SELECT * FROM orders WHERE status='SHIPPED' or status='BACKORDERED'

You can add an order by clause to sort the results. The order by clause takes a list of fields to sort by. For example, to sort first by last name and then by first name, use the following query:

SELECT * FROM personnel ORDER BY last_name, first_name

By default, ORDER BY sorts items in ascending order. You can use the DESC keyword after a field to sort that field in descending order. You can use the ASC keyword to explicitly specify ascending order for a field, which for complex ORDER BY clauses might improve readability. To sort by last name, then first name, then by age in descending order, use the following query:

SELECT * FROM personnel ORDER BY last_name, first_name, age DESC

Updating Values

The UPDATE statement updates values in a table. The general format is

UPDATE table SET column=value, column=value, ... WHERE where-clause

For example:

UPDATE personnel SET department='Marketing', manager_id='987654' WHERE id='123456'

Inserting Values

The INSERT statement inserts values into a table. The general format is

INSERT INTO table (columns) values (column-values)

For example:

INSERT INTO personnel (id, first_name, last_name)
  VALUES (1, 'Kaitlynn', 'Tippin')

Deleting Values

The DELETE statement deletes values from a table. The general format is

DELETE FROM table WHERE where-clause

For example:

DELETE FROM personnel WHERE id='321654'

Joining Tables

One of the most powerful aspects of SQL is the ability to coordinate data from multiple tables. This technique is called joining. You join tables by comparing values from two different tables in a WHERE clause. When the tables have duplicate column names, you can prefix the field name with the table name, using the form table.field, for example: personnel.first_name.

Because your SELECT statement might get cluttered with long table names, you can create aliases for tables in the FROM clause. Simply list the alias after the table name. For example, if the FROM clause is FROM personnel p, you can refer to fields in the personnel table with p.field in the FROM clause. You can also use the table.field form when you list the fields you want to select.

The following SELECT clause locates all items ordered by customers in Atlanta— locating customers in a particular city, orders whose customer ID matches the customer's ID, and the products whose order ID matches the order's ID:

SELECT p.* FROM customer c, products p, orders o
  WHERE p.order_id = o.id AND o.customer_id = c.id
    AND c.city = 'ATLANTA'

Including Variables

The database control uses the same variable substitution mechanism that you use to map incoming XML values to method parameters and vice versa. That is, you can include an incoming methods parameter in a database statement by surrounding it with {}s. You don't need to include the quotes for string fields, WebLogic Workshop handles that for you. For example, suppose you create a method that updates personnel information, like this:

public void updatePersonnel(String id, String firstName, String lastName);

You can substitute the parameters into an update statement like this:

/**
 * @jws:sql statement::
 *  UPDATE personnel UPDATE first_name={firstName}, last_name={lastName}
 *    WHERE id={id}
 */
public void updatePersonnel(String id, String firstName, String lastName);

Getting a Result Set

Although the database control has an easy way to map incoming parameters into an SQL statement, mapping SQL return values into method return values is more difficult. The problem is that a Java method can only return a single value—a primitive type or an object.

Returning a Variable

If a SELECT statement returns a single value, the database control can automatically return the result, like this:

/**
 * @jws:sql statement="SELECT last_name FROM personnel WHERE id={id}
 */
public String getLastName(String id);

The INSERT, UPDATE, and DELETE statements each return an integer value indicating the number of rows that have been inserted, updated, or deleted. You can return this value from a database control method:

/**
 * @jws:sql statement="DELETE FROM personnel WHERE id={id}"
 */
int delete(String id);

In this case, the return value of the delete method is the number of rows actually deleted.

Returning a Row of Results

If a statement returns multiple values for a single column (as opposed to multiple columns), you can return an array of all the values:

/**
 * @jws:sql statement="SELECT last_name FROM personnel"
 */
String[] getAllLastNames();

You can limit the number of items returned in an array with the array-max-length attribute:

/**
 * @jws:sql statement="SELECT last_name FROM personnel"
 *   array-max-length="25"
 */
String[] getFirst25LastNames();

By default, the database control limits the number of rows returned to 1,024. Use "all" with array-max-length to force the control to return all values no matter how many there are.

Returning a Class

Returning single values or an array of values from a single column isn't very useful in a large application. You usually need to retrieve many values. The database control can map column values to fields in a Java class, as long as the field names match the column names.

For example, to retrieve id, first_name, and last_name from the personnel table, you can use the following class:

public class Personnel
{
  public String id;
  public String first_name;
  public String last_name;

  public Personnel()
  {
  }
}

Now, to retrieve all the values from the personnel table, use the following declaration:

/**
 * @jws:sql statement="select * from personnel"
 */
public Personnel[] getAllPersonnel();

Returning a HashMap

If you don't want to write new classes for every variation of columns that you might retrieve, you can simply return a HashMap (a Java data structure that associates keys with values) or an array of HashMaps (if you want to return multiple database rows). The database control stores each column value in the HashMap using the column name as the key, for example:

/**
 * @jws:sql statement="select * from personnel"
 */
public HashMap[] getAllPersonnel();

To fetch the value of the "firstName" column for the first row returned by the getAllPersonnel method, you could use a statement like this:

HashMap[] personnel = getAllPersonnel();
String firstName = (String) personnel[0].get("firstName");

Returning a Multiple Row Result Set in a Container

For managing large data sets, you might want to use a Java iterator instead of returning an array of values. To use an iterator, you must use the iterator-element-type attribute to specify what kind of object the iterator should return.

To iterate through the personnel table, use the following declaration:

/**
 * @jws:sql statement="select * from personnel"
 *   iterator-element-type="Personnel"
 */
public java.util.Iterator getAllPersonnel();

The database control can also return an iterator that returns HashMaps. Simply specify java.util.HashMap as the iterator element type. To access each of these iterators, you could do something like this:

Iterator iter = getAllPersonnel();
while (iter.hasNext())
{
  HashMap row = (HashMap) iter.next();
  String lastName = (String) row.get("lastName");
  // do something with lastName
}

Executing Multiple Statements

Sometimes you need to execute multiple database statements as part of a single transaction. Even though you can only execute a single SQL statement from a database control method, you can still execute several statements within a single transaction. Because of the way WebLogic Workshop creates Web service methods, each Web service method executes as a single transaction. Any database operations you perform during the Web service method are part of the same transaction. If any one of the operations fail, they all do. This way, you don't need to worry about reversing previous operations if another operation fails—it happens automatically.

You can also invoke a stored procedure from a database control, using the stored procedure syntax for your specific database (the syntax varies from database to database). Although stored procedures are occasionally useful, the fact that there is no standard for stored procedures makes it difficult to switch databases. Stored procedures do have their own unique advantages. They tend to run a series of statements faster because all the statements are already on the database. They can provide additional security because they are self-contained and residing on the database, eliminating the possibility that someone could tamper with intermediate results. Although some application architects make heavy use of stored procedures, putting much of the business logic into them, other architects use them only as a last resort to solve some specific performance or security problem.

JDBC includes a special syntax for calling stored procedures, but unfortunately you can't use it in a WebLogic Workshop database control. Instead, you must use a database-specific syntax. For example, suppose your data uses the following syntax:

CALL updatePersonnel('Samantha', 'Tippin', 123456)

You might define the following function in a WebLogic Workshop database control to invoke the procedure:

/**
 * @jws:sql statement="CALL updatePersonnel({firstName}, {lastName}, {id});
 */
public int updatePersonnel(String firstName, String lastName, String id);

A Sample Application

Suppose you want to allow customers to check on whether their orders have shipped. Assuming you have a database table that indicates order status, you really only need a table of customers and their order status to provide this service.

For this example, you can use the sample database and data source in the WebLogic samples server. WebLogic comes with a pure-Java database server called PointBase. Although you can use the PointBase console for maintaining your database, this example uses the database control to create the sample tables and insert test data values.

Listing 6.1 shows the database control that creates tables, inserts test data, and allows you to query for status information. The control contains methods for creating and dropping tables because the WebLogic Workshop doesn't provide any tools to manage its built-in PointBase database. In a typical production environment, you wouldn't include these types of methods because you often have a database administrator who is responsible for creating and dropping the tables. The other methods in the control simply perform the kinds of SQL statements that the application needs—inserting customers and order status, and querying for customers.

Listing 6.1 Source Code for OrderStatusCtrl.ctrl

import weblogic.jws.*; 
import weblogic.jws.control.*; 
import java.sql.SQLException; 

/** 
 * Defines a new database control. 
 * 
 * The @jws:connection tag indicates which WebLogic data source will be used by 
 * this database control. Please change this to suit your needs. You can see a 
 * list of available data sources by going to the WebLogic console in a browser 
 * (typically http://localhost:7001/console) and clicking Services, JDBC, 
 * Data Sources. 
 * 
 * @jws:connection data-source-jndi-name="cgSampleDataSource" 
 */ 
public interface OrderStatusCtrl extends DatabaseControl 
{
  /**
   * @jws:sql statement::
   *  create table orderStatus(
   *    orderId INTEGER,
   *    customerId INTEGER,
   *    orderStatus VARCHAR(255),
   *    orderStatusCode INTEGER)
   * ::  
   */
  void createOrderStatusTable(); 
  
  /**
   * @jws:sql statement="drop table orderStatus"
   */
  void dropOrderStatusTable();
  
  /**
   * @jws:sql statement::
   *  insert into orderStatus (orderId, customerId, orderStatus,
   *    orderStatusCode) values ({order.orderId}, {order.customerId},
   *      {order.orderStatus}, {order.orderStatusCode})
   * ::
   */
  void insertOrderStatus(OrderStatus order);

  /**
   * @jws:sql statement::
   *   create table customer(
   *     customerId Integer,
   *     address varchar(255),
   *     city varchar(64),
   *     state varchar(32),
   *     zip varchar(10),
   *     userName varchar(32),
   *     password varchar(32))
   * ::
   */
  void createCustomerTable();
  
  /**
   * @jws:sql statement="drop table customer"
   */
  void dropCustomerTable();
   
   /**
   * @jws:sql statement::
   *   insert into customer (customerId, address, city, state, zip,
   *     userName, password) values ({cust.customerId}, {cust.address},
   *     {cust.city}, {cust.state}, {cust.zip}, {cust.userName},
   *     {cust.password})
   * ::
   **/
   void insertCustomer(Customer cust);
   
   /**
   * @jws:sql statement="select * from customer where userName={userName}"
   */
   Customer getCustomerByUserName(String userName);
   
   /**
   * @jws:sql statement::
   *   select * from orderStatus where
   *     customerId={customerId}
   * ::
   */
   OrderStatus[] getCustomerOrders(int customerId);
   
   /**
   * @jws:sql statement::
   *   select * from orderStatus where
   *     customerId={customerId} and
   *     orderId={orderId}
   * ::
   */
   OrderStatus getOrderStatus(int customerId, int orderId);
}

Before you can run the example, you must first create the tables and insert data. Listing 6.2 shows the Admin service that allows you to create the tables, insert data, and delete the tables. Again, the only reason for the Admin service is that there is no tool in WebLogic Workshop to manage the built-in database. In a production environment, you would do these kinds of operations using a database tool (or leave them up to the database administrator). In this case, the tool creates the necessary tables and populates them with data using the OrderStatusCtrl database control from Listing 6.1.

Listing 6.2 Source Code for Admin.jws

import weblogic.jws.control.JwsContext;

public class Admin
{ 

  /**
   * @jws:control
   */
  private OrderStatusCtrl orderStatus;
  /** @jws:context */ 
  JwsContext context; 

  /**
   * @jws:operation
   */
  public void initializeTables()
  {
    orderStatus.createCustomerTable();
    orderStatus.insertCustomer(
      new Customer(1, "Samco",
        "123 Main St.", "Lithonia", "GA", "30038",
        "sammy", "barbie"));
    orderStatus.insertCustomer(
      new Customer(2, "Katy World",
        "6 Reader Lane", "Lithonia", "GA", "30038",
        "katy", "katy"));
    
    orderStatus.createOrderStatusTable();
    orderStatus.insertOrderStatus(
      new OrderStatus(1, 1, "Shipped: 1 Box of 1024 Crayons",
        OrderStatus.ORDER_SHIPPED));
    orderStatus.insertOrderStatus(
      new OrderStatus(2, 1, "Backorder: 3 Reams Multi-color card stock",
        OrderStatus.ORDER_BACKORDERED));
    orderStatus.insertOrderStatus(
      new OrderStatus(3, 2, "Processing: 1 Copy Ozzie's World",
        OrderStatus.ORDER_IN_PROCESS));
    orderStatus.insertOrderStatus(
      new OrderStatus(4, 2,
        "Partial: Shipped-Where The Wild Things Are; "+
        "Backorder-Hop On Pop", OrderStatus.ORDER_PARTIAL_SHIPPED));
  }

  /**
   * @jws:operation
   */
  public void removeTables()
  {
    orderStatus.dropOrderStatusTable();
    orderStatus.dropCustomerTable();
  }
  
  /**
   * @jws:operation
   */
  public void removeOrderStatusTable()
  {
    orderStatus.dropOrderStatusTable();
  }

  /**
   * @jws:operation
   */
  public void removeCustomerTable()
  {
    orderStatus.dropCustomerTable();
  }
} 

To retrieve or insert data, you usually need to define classes to contain table data (if you don't use a HashMap). Listing 6.3 shows the class that represents a customer. You can compare this Customer class to the customer table defined by the createCustomerTable in the OrderStatusCtrl database control in Listing 6.1. Notice that there is a field in the Customer class for each column defined in the customer database.

Listing 6.3 Source Code for Customer.java

public class Customer 
{ 
  public int customerId;
  public String name;
  public String address;
  public String state;
  public String city;
  public String zip;
  public String userName;
  public String password;
  
  public Customer()
  {
  }
  
  public Customer(int aCustomerId, String aName, String anAddress,
    String aState, String aCity, String aZip, String aUserName,
    String aPassword)
  {
    customerId = aCustomerId;
    name = aName;
    address = anAddress;
    state = aState;
    city = aCity;
    zip = aZip;
    userName = aUserName;
    password = aPassword;
  }
} 

Listing 6.4 shows the class that represents an order status. You can compare this class to the orderStatus table defined in the createOrderStatusTable method in Listing 6.1. As with the Customer class, the OrderStatus class contains a field for each column in the orderStatus table. In addition, the class defines numeric constants (the public static final int fields) to represent the various order status codes that can be stored in the database.

Listing 6.4 Source Code for OrderStatus.java

public class OrderStatus 
{ 
  public static final int ORDER_IN_PROCESS = 1;
  public static final int ORDER_SHIPPED = 2;
  public static final int ORDER_BACKORDERED = 3;
  public static final int ORDER_PARTIAL_SHIPPED = 4;
  public static final int ORDER_SUSPENDED = 5;
  
  public int orderId;
  public int customerId;
  public String orderStatus;
  public int orderStatusCode;
  
  public OrderStatus()
  {
  }
  
  public OrderStatus(int anOrderId, int aCustomerId,
    String anOrderStatus, int anOrderStatusCode)
  {
    orderId = anOrderId;
    customerId = aCustomerId;
    orderStatus = anOrderStatus;
    orderStatusCode = anOrderStatusCode;
  }
} 

If you want to require customers to log in before they check their order status, you need your Web service to maintain a conversation. The login method initiates a conversation, and the logout method terminates the conversation. During the life of the conversation, you keep track of the customer's ID so you can use it for any status queries.

Listing 6.5 shows the main order status Web service. In addition to the login and logout methods, the Orders Web service includes methods to retrieve all available orders (using the customer ID determined during the login method), and also to get the status for a particular order. These data retrieval methods simply make use of the OrderStatusCtrl database control from Listing 6.1, and also the OrderStatus data object from Listing 6.4.

Listing 6.5 Source Code for Orders.jws

import weblogic.jws.control.JwsContext;

public class Orders
{ 
  public int customerId;
  
  /**
   * @jws:control
   */
  private OrderStatusCtrl orderStatus;
  /** @jws:context */ 
  JwsContext context; 

  /**
   * @jws:operation
   * @jws:conversation phase="start"
   */
  public String login(String userName, String password)
  {
    Customer cust = orderStatus.getCustomerByUserName(
      userName);
    
    if (cust != null)
    {
      if (!cust.password.equals(password))
      {
        customerId = -1;
        context.finishConversation();
        return "Invalid password";
      }
      else
      {
        customerId = cust.customerId;
        return "Login successful";
      }
    }
    else
    {
      customerId = -1;
      context.finishConversation();
      return "Invalid user-id";
    }
  }
  
  /**
   * @jws:operation
   * @jws:conversation phase="continue"
   * @jws:return-xml xml-map::
   *   <getAllOrdersResponse xmlns="http://openuri.org/">
   *   <order-statuses>
   *   <order xm_multiple="o in return" xmlns_xm="http://bea.com/jws/xmlmap"
   *     id="{o.orderId}">
   *     <status>{o.orderStatus}</status>
   *     <statusCode>{o.orderStatusCode}</statusCode>
   *   </order>
   *   </order-statuses>
   *   </getAllOrdersResponse>
   *   
   * ::
   */
  public OrderStatus[] getAllOrders()
  {
    if (customerId >= 0)
    {
      return orderStatus.getCustomerOrders(customerId);
    }
    else
    {
      return null;
    }
  }
  
  /**
   * @jws:operation
   * @jws:conversation phase="continue"
   * @jws:parameter-xml xml-map::
   *   <getOrderStatus xmlns="http://openuri.org/">
   *   <order-id>{orderId}</order-id>
   *   </getOrderStatus>
   *   
   * ::
   * @jws:return-xml xml-map::
   *   <getOrderStatusResponse xmlns="http://openuri.org/">
   *   <order id="{return.orderId}">
   *     <status>{return.orderStatus}</status>
   *     <statusCode>{return.orderStatusCode}</statusCode>
   *   </order>
   *   </getOrderStatusResponse>
   *   
   * ::
   */
  public OrderStatus getOrderStatus(int orderId)
  {
    if (customerId >= 0)
    {
      return orderStatus.getOrderStatus(customerId, orderId);
    }
    else
    {
      return null;
    }
  }

  /**
   * @jws:operation
   * @jws:conversation phase="finish"
   */
  public void logout()
  {
  }
} 

Although the database control might not solve all your database needs, it should certainly be sufficient for smaller applications. In larger, more complex applications, it becomes more difficult to manage your code. The reason for this is that there is often logic in your Java code that implements business rules on top of the data. For example, you might have a rule that says that a customer ID can never start with '9'. When you have several Web services that manipulate the customer data, you might find that you are enforcing these rules in several places.

To help manage the complexity of business rules, developers often make "business objects" that manage data in the database and also maintain business rules. In this kind of scenario, any Web service that needed to update the customer table would use methods in a special Customer business object that would perform any special processing or validation of the customer data.

Enterprise JavaBeans (EJBs) are a special case of business object. The EJB architecture provides a standard way to represent these business objects and also provides powerful mechanisms for storing these objects in a database and retrieving them. You will learn how to access EJBs in Chapter 10, "Including an EJB Control."

About the Authors

Joseph Weber is a Software Architect, Manager and consultant from Wisconsin. Mr. Weber has been an outspoken champion of Java and related technologies since their public birth in 1995. During his career he has provided senior leadership in software definition, research, development and implementation to numerous Fortune 200 and large government organizations. Currently Mr. Weber is a Senior Software Engineer and Project Manager for UltraVisual Medical Systems where he is helping to develop next generation medical imaging software (PACS). Mr. Weber is also the founder, and sole official member of the Green Sky Society dedicated to irradiating the social misunderstanding that the sky is blue and not green. BEA WebLogic Workshop Kick Start marks Joe’s 11th book. He recently outlined and contributed to Sams’ Java Web Services Unleashed (0-672-32363-X) and co-wrote Que’s Special Edition Using Java 2 (2000 edition: 0-7897-2468-5).

Mark Wutka has been programming since the Carter administration and considers programming a relaxing pastime. He managed to get a computer science degree while designing and developing networking software at Delta Air Lines. Although he has been known to delve into areas of system and application architecture, he isn’t happy unless he’s writing code-usually in Java. As a consultant for Wutka Consulting, Mr. Wutka enjoys solving interesting technical problems and helping his coworkers explore new technologies. He has taught classes, written articles and books, and given lectures. His first book, Hacking Java, outsold Stephen King at the local technical bookstore. He’s also known for having a warped sense of humor.

Most recently, Mr. Wutka contributed to Java Web Services Unleashed, and wrote Special Edition Using Java Server Pages and Servlets (ISBN: 0-7897-2441-3) and Special Edition Using Java 2 Enteprise Edition (ISBN: 0-7897-2503-7) He plays a mean game of Scrabble, a lousy game of chess, and is the bane of every greenskeeper east of Atlanta.

Source of this material

This is Chapter 6: The Database Control from the book BEA WebLogic Workshop Kick Start (ISBN:0-672-32417-2) written by Joseph Weber and Mark Wutka, published by Sams Publishing.

To access the full Table of Contents for the book


Other Chapters from Sams Publishing:

Web Services and Flows (WSFL)

Overview of JXTA

Introduction to EJBs

Processing Speech with Java


Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories