Connecting to a Database

This chapter describes how an Enterprise Objects application connects to a database and how you can take control of this process. You may want to manually connect to a database for a few reasons:

This chapter is divided into the following sections:

Objects Involved in a Database Connection

This section provides a high-level overview of the objects involved in an Enterprise Objects database connection. For more specific information, see the API reference for the classes mentioned here.

The highest-level object in an Enterprise Objects database transaction is an EODatabase object. This object represents a single database server so your application has as many EODatabase objects as the databases to which it connects. An EODatabase object communicates with an EOAdaptor object that is capable of communicating with a database server.

Each EODatabase contains at least one EODatabaseContext object. Each EODatabaseContext forms a separate transaction scope to a database using an EODatabaseChannel object (which itself uses an EOAdaptorChannel object). Each EODatabaseContext uses one or more EODatabaseChannel objects.

Figure 10-1 illustrates the scenario in which an EODatabase manages a single EODatabaseContext object and the database context uses a single database channel.

Figure 9-1  EODatabaseContext managing a single database channel
EODatabaseContext managing a single database channel

By default, an EODatabaseContext uses a single EODatabaseChannel but in Figure 10-2, the database context is shown using two database channels. Each database channel is always associated with exactly one set of adaptor objects (an EOAdaptor, an EOAdaptorContext, and an EOAdaptorChannel), as Figure 10-2 illustrates.

Figure 9-2  One database context using two database channels
One database context using two database channels

If the database server an EODatabase object represents supports multiple concurrent transaction sessions, that EODatabase may have several EODatabaseContexts. If the adaptor allows multiple channels per context, then an EODatabaseContext may in turn have several EODatabaseChannels, which handle actual access to the database.

The rest of this chapter describes how these objects are used by Enterprise Objects to connect to a database and how you can take control of the connection.

When Database Connections Are Opened

You usually never have to worry about opening database connections programmatically—Enterprise Objects takes care of it for you. The first time any editing context in your application initiates a database operation, a connection to the database is opened by the access layer. Enterprise Objects reuses that same connection for all subsequent database operations to that particular database. If your application accesses multiple databases, a separate database connection is opened for each database.

When Database Connections Are Closed

Enterprise Objects doesn’t close its connections to databases until the application terminates. It reuses open connections so by default it uses a minimum number of connections and you don’t need to limit the number of connections. However, there are situations in which you may want to close connections when they aren’t in use. These situations are rare and usually result when many copies of an application connect to the same database or if you add more database channels as described in Database Channels and the database can’t handle more than a certain number of database connections.

If you’re concerned about the number of open database channels in a particular application, it’s probably best to set a timer in your application and attempt to close open database connections at a specified time. Before closing an open database channel, you should make sure that channel isn’t performing an operation. See Closing Database Channels to learn how to close database channels on demand.

Connection Dictionary

Enterprise Objects uses an EOAdaptor object to connect and log in to a database. WebObjects 5.2 provide two adaptors: one for JDBC data sources and one for JNDI data sources. An adaptor uses a connection dictionary to identify the address of the database and to provide login credentials.

When you’re developing an Enterprise Objects application, you usually supply the connection dictionary information in the EOModel files the application uses. While this is convenient during development, it may not be adequate for deployed applications. Instead, you may need to provide the connection dictionary programmatically. The following sections describe the two ways to provide connection dictionary information to an application.

Storing in a Model File

When you create a new EOModel file, you are prompted to provide login information for the data source to which that model connects. The window that asks for this information is shown in Figure 10-3.

Figure 9-3  Connection dictionary window in EOModeler
Connection dictionary window in EOModeler

The information you provide in this window becomes the model file’s connection dictionary. The connection dictionary is stored in the model file and doesn’t require you to write any code to use it. This approach is useful when all users of the application log in to the database with the same connection information and when the login information is not regarded as sensitive.

However, if you need to provide users with different database connection information or if you still want to allow all users to share the same credentials but you want to secure those credentials, you need to provide some of the connection dictionary information programmatically. You can still provide nonsensitive connection information in the model, such as the database URL, driver, and plug-in, and just provide login credentials programmatically.

Providing the connection dictionary programmatically (or even just part of the connection dictionary) requires more code on your part, so you should do it only if you really need to. Storing in Code tells you how to provide the connection dictionary programmatically.

All EOAdaptor objects that Enterprise Objects creates automatically use the connection dictionary information specified in the model. If you instantiate an adaptor programmatically, however, it doesn’t automatically use the model’s connection dictionary information, so you have to provide it programmatically.

Storing in Code

For any number of reasons, you may need to provide all or part of an EOAdaptor’s connection dictionary programmatically. To do this, you first need to identify the dictionary’s keys so you can set their values.

The following code prints the connection dictionary keys of the Real Estate model:

EOModel model = EOModelGroup.defaultGroup().modelNamed("RealEstate");
NSLog.out.appendln("connection dictionary keys: " +     model.connectionDictionary().allKeys());

The output of this code is:

connection dictionary keys: ("plugin", "jdbc2Info", "username", "driver", "password", "URL")

Now that you know the names of the connection dictionary’s keys, you need to set the values for some of them. You don’t need to set the values for all of the keys, only for the keys you care about. When you force a model to use a new connection dictionary with the method discussed below, only the keys for which you explicitly set values are overridden. So if, for example, the original connection dictionary in the model specifies the database URL and you don’t provide a value for that key in the dictionary of overrides, the value in the model’s dictionary is still used even after you’ve provided an updated connection dictionary.

Where in an application’s execution should you set the values? It depends on your application’s requirements. If you provide each user with an independent connection to the database by giving each user a separate stack as described in Providing Separate Stacks, you should first get a user’s database username and password and then set the values of the connection dictionary before instantiating the stack. After you set the values in the connection dictionary but before you instantiate a stack for each user, you need to invoke the method EODatabaseContext.forceConnectionWithModel. You pass to this method as arguments an EOModel object, a connection dictionary of keys to override with the values you supply programmatically, and an editing context.

The code in Providing a Connection Dictionary in Code shows all these steps.

Connecting to Multiple Data Stores

Enterprise Objects makes it easy to work with multiple data stores in a single application. An application often includes enterprise object instances that represent data from different repositories.

The EOObjectStoreCoordinator is the object that manages multiple repositories in a single application. When a fetch is performed, it determines which repository can service the fetch. Similarly, when changes are committed, it determines which repository to save the changes to. When a fault is fired, it determines which repository can service the fault. And, Enterprise Objects is smart enough to generate database-specific or database-optimized SQL expressions within a single application that connects to multiple repositories.

There are, however, a few limitations when using multiple data sources in an application. They include:

If you understand these limitations, connecting to multiple data stores in an Enterprise Objects application should just work.

Database Channels

The default configuration of the Enterprise Objects core stack provides only a single database channel, regardless of the number of transactions a particular Enterprise Objects stack makes. The default behavior is usually sufficient but certain applications and configurations may benefit from using multiple database channels.

Conflicts within Enterprise Objects due to busy database channels may occur when a database context needs to perform an operation and its database channel is already busy with another operation such as fetching. Most of these types of conflicts are the result of inefficient fetching, such as when faults are inadvertently fired while other database operations are in progress, and can be avoided.

When an EODatabaseContext needs a new channel because all its current channels are busy, it posts an EODatabaseChannelNeededNotification. You can create database channels on demand by registering for this notification and providing an additional channel at that time. In the method you write to create database channels, you should set an upper limit on the number of channels that can be registered with a particular database context. It’s very unusual for an EODatabaseContext to require more than two or three EODatabaseChannels. See Providing Multiple Database Channels to learn how to add additional database channels to an application.

Providing a Connection Dictionary in Code

The code in Listing 10-1 provides a connection dictionary programmatically using the procedure discussed in Storing in Code.

Listing 9-1  Setting connection dictionary programmatically

EOModel model = EOModelGroup.defaultGroup().modelNamed("RealEstate");
NSLog.out.appendln("connection dictionary keys:" +      model.connectionDictionary().allKeys());
 
NSMutableDictionary overrides = new NSMutableDictionary();
overrides.takeValueForKey("brent", "username");
overrides.takeValueForKey("secret", "password");
EODatabaseContext.forceConnectionWithModel(model, overrides, new EOEditingContext());

Providing Multiple Database Channels

To create and register a new database channel with a particular database context, you can use the code in Listing 10-2. It’s probably most appropriate to put this code in your Session class, as this listing does. The code listing also shows how to register for the notification that EODatabaseContext posts when it needs another database channel.

Listing 9-2  Creating and registering a new database channel

import com.webobjects.foundation.*;
import com.webobjects.appserver.*;
import com.webobjects.eocontrol.*;
import com.webobjects.eoaccess.*;
 
public class Session extends WOSession {
 
    private final int DatabaseChannelCountMax = 3;
 
    public Session() {
        super();
        NSNotificationCenter.defaultCenter().addObserver(this, new
            NSSelector("registerNewDatabaseChannel",
            new Class[] { NSNotification.class } ),
            EODatabaseContext.DatabaseChannelNeededNotification, null);
    }
 
    public void registerNewDatabaseChannel(NSNotification notification) {
        EODatabaseContext databaseContext = (EODatabaseContext)notification.object();
        if (databaseContext.registeredChannels().count() < DatabaseChannelCountMax){
            EODatabaseChannel channel = new EODatabaseChannel(databaseContext);
            databaseContext.registerChannel(channel);
        }
    }
 
}

If you create database channels programmatically on a per-session basis, you may end up with a large number of database channels per application instance. When Database Connections Are Closed describes how to check for and close extraneous database connections.

Closing Database Channels

The code in Listing 10-3 demonstrates how to close an application’s database connections at a recurring interval. The closeDatabaseChannels method assumes that the application has only one object store coordinator (per-session object store coordinators are not used) and that all of the cooperating object stores managed by the coordinator are database contexts, which is the usual case.

Listing 9-3  Close database channels at a specified interval

import com.webobjects.foundation.*;
import com.webobjects.appserver.*;
import com.webobjects.eocontrol.*;
import com.webobjects.eoaccess.*;
import java.util.*;
 
public class Application extends WOApplication {
 
    public static void main(String argv[]) {
        WOApplication.main(argv, Application.class);
    }
 
    public Application() {
        super();
        System.out.println("Welcome to " + this.name() + "!");
        setupDatabaseChannelCloserTimer();
    }
 
    public void setupDatabaseChannelCloserTimer() {
        Timer timer = new Timer(true);
        //Close open database connections every four hours.
        timer.scheduleAtFixedRate(new DBChannelCloserTask(), new Date(), 14400);
    }
 
    class DBChannelCloserTask extends TimerTask {
 
     public DBChannelCloserTask() {
      super();
     }
 
      public void run() {
       closeDatabaseChannels();
       NSLog.out.appendln("running timer");
      }
 
      public void closeDatabaseChannels() {
       int i, contextCount, j, channelCount;
       NSArray databaseContexts;
       EOObjectStoreCoordinator coordinator;
 
       coordinator =
       (EOObjectStoreCoordinator)EOObjectStoreCoordinator.defaultCoordinator();
       databaseContexts = coordinator.cooperatingObjectStores();
       contextCount = databaseContexts.count();
 
       //Iterate through all an app’s cooperating object stores (database contexts).
       for (i = 0; i < contextCount; i++) {
        NSArray channels =
         ((EODatabaseContext)databaseContexts.objectAtIndex(i)).registeredChannels();
        channelCount = channels.count();
 
          for (j = 0; j < channelCount; j++) {
          //Make sure the channel you're trying to close isn't performing a transaction.
          if (!((EODatabaseChannel)channels.objectAtIndex(j)).adaptorChannel().
            adaptorContext().hasOpenTransaction()) {
             ((EODatabaseChannel)channels.objectAtIndex(j)).adaptorChannel().
              closeChannel();
           }
          }
        }
      }
    }
}