Transaction mode "Supports" is of very questionable usefulness for Entity beans. Transaction modes "Never" and "NotSupported" are even more problematic wrt Entity beans.
In conclusion, it is not a bug in the container if in such situations your data modifications never show up in the database. It might be argued to be a bug in your deployment, or else a bug in the EJB specification. (The spec does have a note on this in the Entity Contract chapter) The solution is to fix your transaction attributes.
On the other hand it is reasonable to call finders outside of transactions.
For entity beans activation/passivation means that the bean instance is being associated/disassociated with a particular primary key (e.g., EJBObject reference). When in the pooled state, entity instances are not associated with any particular primary key. So, ejbActivate means "the container just associated the instance with a particular primary key, do whatever you need to support this". Likewise, ejbPassivate means "the container is disassociating the instance with the curently associated primary key, do whatever you need to support this". Again, typically, there is nothing to do in either call.
Note that in the Inprise Container, the association between an instance and a primary key can last for the duration of a single transaction or last longer, depending on the selected transaction mode.
We do provide an SPI (service provider interface) to the container managed persistence engine in the container. One can implement the interface for custom persistent stores and be able to have Entity Beans use that backend.
Transactions are used to control access to entity beans. If two different transactions (e.g., clients) access the same entity, they will in fact see different instances, each with a separate copy of the state. If the state is modified in both transactions, then only one of the transactions will be able to commit, and the other will abort. The concurrency control is done by the database (more on this in other FAQs). All locking is provided by the database, via isolation levels specified to the JDBC driver.
Note that the Container supports parallel transactional access to the same entity. Other "popular" EJB containers serialize access to entities, by locking. This will obviously have a severe impact on performance.
Let's take a particular example. We have two clients A, and B. Each client is attempting to access the same entity object E. Client A will be running in one transaction, and client B will be running in the other. Let's say that client A starts first. The container will load in a copy of entity E, and run client A's requests against this first copy of E. Then client B starts. The container will again load in a copy of entity E, and run client B's requests against this second copy of E. Note that there will be two copies of the EJB implementation class (e.g., the Bean class), each containing the same state. However, over time, client A might modify the state of E, as might client B. At some point, both of the clients' transactions are completed (either committed or rolled back). Let's say that client B commits first. This will cause the container to update the database (or whatever) using the state stored in the second copy of E. Later, client A commits, which will cause the container to (attempt to) udate the state stored in the first copy of E. This update may fail, depending on the particulars of the updates. However, the important point is that these transactions will run in parallel in the container. If locking is required, it will be done by the database. If the transactions are incompatible it is up to the database to detect this and (presumably) cause the second update to be rolled back.
Again, the point is that the container is not doing any locking. All locking is done in the database, which as pointed out before, is a good thing.
So, how does this differ from other products? Other products do the locking in the container. Thus, in the above scenario, client B would be blocked until client A had completed its work. If client A is running a relatively long transaction, this quickly becomes unacceptable in terms of overall transaction throughput!
The only optimizations can be in the CMP engine: (Tuned Writes)
* Option A: exclusive database access, retains state across transactions * Option B: avoid passivation/activation across transactions * Option C: passivate/activate across transactions (the behaviour above)Note that Option A, in conjunction with field-level modification-detection in the CMP engine, will allow repeated readonly transaction to run completely in the container, e.g., without going to the database. This should be very fast.
Note that this option makes sense only when the entity “owns” the data in the database. You will have problems if the database is modified externally (eg by a data base administrator, another app) while the EJB server is running.
userTx.begin();
Entity entity = entityHome.create();
entity.doWork();
userTx.commit();
You will notice the entity transition from pooled to ready state via an ejbCreate and then get passivated. On
the doWork() call it will get activated and again passivated.
In Option B, the container will not call passivate after a transaction, and subsequently will not call activate before the next transaction using the same entity. The ejbStore still happens on a commit.
In Option A, the container assumes it has exclusive access to the database, and does not need to load the entity during the next transaction using the same entity. When the database is un-shared, it is possible to avoid reading the state of an entity at the beginning of a transaction, by using the state of the entity from the last (committed) transaction.
OptionB is a much better default than OptionC. OptionC requires that every entity bean go back and forth between the pooled and the ready state, between every transaction. In the (very common) case that you are using a working set of entity beans, this is just busy work, which has a measurable performance overhead.
Basically, we are trying to make the defaults have the best performance for the common use cases, and allow the user to change the defaults when their usage patterns differ. Caching is an option, specifiable in the deployment descriptor (and by default Option A style caching is disabled, since it cannot be assumed that by default the database is unshared).
<property>
<prop-name>ejb.transactionCommitMode</prop-name>
<prop-type>Enumerated</prop-type>
<prop-value>A</prop-value>
</property>
(a) dynamically turning off caching? Is there an IAS API for accessing the container and telling it to unload the cache for some beans at a specified time? This would be useful in situations where it makes sense to enable container caching but where there are (infrequent) external modifications of the database by administrators (eg, cleaning up a screwed-up transaction, running batch process to delete expired accounts, whatever). It makes container caching more usable/useful in situations where it's a selling point.
(b) being able to send a message forcing state to be resynchronized?
(c)The ideal might be some means by which an administrator could turn it off while an external task is running. A typical user scenario is as follows:
We are at present using the ejb.transactionCommitMode=exclusive flag for all of our beans. But for some beans we have an issue that we each night perform a database synhronization with our switches’ internal routing tables. At present this update is done directly to the database outside of the entity beans. The update takes 15 minutes and is done in the early hours so we do not expect any user issues but because the container could be possibly be caching the relevant beans when the user logs on in the morning they would not get the correct view. Now we could get rid of this flag but then we sacrifice our performance gains for the sake of 15 minutes update. What options do I have?
This will effectively clear the cache and update the entities.
If you are unhappy with having the beans be inconsistent for the duration of the update, then you can do
the following:
1) Run the beans as normal in exclusive mode.
2) When you want to do the batch update, substitute versions of the beans running in shared mode for the
beans running in exclusive mode.
3) Do the update.
4) Substitute the exclusive mode beans for the shared mode beans.
We provide a command-line interface which can be used to load and unload beans from a running container, so it should just be a matter of adding these commands to your existing batch run.
But caching does not necessarily need to assume exclusive ownership. There are several applications which can work on an "optimistic concurrency" basis. This sort of design works for many applications where concurrency collisions are rare. It can fail miserably, particularly from the users viewpoint, when concurrency collisions are probable, but such applications are likely to require a more sophisticated design under any architecture.
1) A single container can have exclusive access to the database (or more precisely, exlusive access with respect to the entity's table in the database), and you can use Options A.
2) Multiple replicated containers can have non-exclusive access to the database (e.g., tables), and you can use Options B and C only. In this scenario round robin load balancing works great.
There is no way to support both exclusive access and fail-over simultaneously, given our current design. This really boils down to an OSAgent issue, which doesn't distinguish between round-robin for performance, and fail-over for availability.
In all of these cases, one would very much suspect that the size of the pool is not a limiting factor. In (1) and (2) it is much more likely that the number of database connections would be the bottleneck, since each concurrent find and/or transaction must use its own connection. It is possible to configure the pool for a particular entity by setting some properties in the XML DD. They are:
ejb.maxBeansInPoolThis option specifies the maximum number of beans in the ready pool. If the ready pool exceeds this limit, entities will be removed from the container by calling unsetEntityContext. The default setting is 1000.
ejb.maxBeansInCacheThis option specifies the maximum number of beans in the "Option A" cache. If the cache exceeds this limit, entities will be moved to the ready pool by calling ejbPassivate. The default setting is 1000.
What some Containers could do is while the cached EBs were NOT involved in a transaction, a background thread in the container would walk through the list of cached EJBs (periodically) and check if their data was changed in the database, and if so, refresh them.
Unfortunately, this approach is broken. Say then, at time t1, the container verified that the DB and cache were in sync. Then say at t2, the container starts a new transaction, and uses the up-to-date cached state. Also, let's say at t3, a legacy application changes the data in the DB. The problem occurs if t3 comes between t1 and t2. The container thinks the cache is up-to-date, and in fact it is not. When t3 falls between t1 and t2, the legacy app's changes are potentially wiped out.
The only way to solve this problem is to check the database before starting the transaction. This is exactly what we were trying to avoid in the first place!
Unfortunately, ACID properties are nasty. You just can't cheat! So again, any vendor that claims they solve
this problem is either:
1) lying
2) doing something incorrect
Persistence's appserver claims to use optimistic concurrency control attributes (OCAs) to detect the case where t3 fell in between t1 and t2, in which case the DB would have a higher OCA value than the cached EJB state, and the EJB transaction would either fail with a special concurrency-control exception, or wipe out the legacy app's changes (configurable).
Possibility 2:
The only way to do this correctly is to run triggers in the database within the current transaction
(technically in the before-completion phase of the transaction). This seems extremely painful (and
proprietary). How does the trigger get associated with the current JTS/OTS managed transaction? Just
updating the caches after the database has changed introduces (albeit transient) inconsistencies in the data,
which obviously violates any kind of correctness.
ejbLoad would be called by the Container in the above scenario.
The intent of the EJB spec is that the instance must be "loaded" before ejbRemove is called. Requiring the ejbLoad method is also necessary to make the BMP model consistent with CMP. Allowing the container not to call the ejbLoad would lead to an error-prone programming model for the developer of the ejbRemove method. This is because sometimes the instance would be "loaded", and sometimes it would not. Then an entity with BMP will need to be coded to only depend on the primary key in the entity context, specifically not assuming that its other instance variables (cached state) have been set.
However there is no guarantee that an ejbLoad callback is always performed before ejbRemove. For example, an instance may not receive an ejbLoad if the instance got to the "ready" state via the ejbCreate/ejbPostCreate transition. Here the ejbCreate will essentially load the bean, since it is required to set the bean's instance variables before returning.
The bottomline is that the instance must be "loaded" before ejbRemove is called. In both cases, the container ensures that the instance variables are up-to-date before dispatching the ejbRemove method. This means that unless the container is certain that the instance variables are up-to-date, the container must load the CMP fields from DB and call ejbLoad before it calls ejbRemove.
In A::ejbCreate, I can't create the B since the current instance of A
has no real object (getEJBObject returns null). So, I put the code to
create B into A::ejbPostCreate. Here's a few code fragments:
From A, the ejbCreate and ejbPostCreate methods:
public void ejbCreate( String data )
throws CreateException, RemoteException {
_mid = new MessagePK().allocMID();
_data = new String( data );
System.out.println( "in ejbCreate: mid = " + _mid );
}
public void ejbPostCreate( String data )
throws CreateException, RemoteException {
System.out.println( "in ejbPostCreate( String )" );
// Create our associated meta data object.
// Have to do this here since we need a reference to ourself and in
// ejbCreate getEJBObject returns null.
_metaData =_mmdHome.create( _mid, (Message)_context.getEJBObject());
System.out.println( "Finish: mmd = " + _metaData );
}
Even though the print "Finish:..." shows a valid reference, the database
is not updated to reflect the value. This insert takes place at the end
of ejbCreate:
*cm* execute create "INSERT INTO Message (_mid, _data, _metaData) VALUES (?, ?, ?)" args: [31, Message number 0, null]As expected, the _metaData field is null since it hasn't been created. Yet, at the exit from ejbPostCreate, no database update takes place. So the DB value remains null.
Am I doing something illegal? What is the "right" way to make this mutual cross-reference?
There is no simple way to do what you are trying to do. It could be construed as a bug in the EJB spec or wrong usage.
The only way to do what you want, is as follows:
1) In the ejbCreate for A, create B (not pointing to A, since you don't have A's self-reference). This will
cause A to have a reference to B.
2) In the ejbPostCreate, set B's reference to A. This will cause B to have a reference to A.
If you wrap A's ejbCreate/ejbPostCreate within a single transaction (using either client-managed- transactions, or container-managed-), then you will never see the temporarily incomplete B, except within the scope of that single transaction.
Here's are the relavent code fragments:
public void ejbCreate( String data ) throws CreateException, RemoteException {
_mid = new MessagePK().allocMID();
_data = new String( data );
System.out.println( "in ejbCreate: mid = " + _mid );
// Create our meta data object, MsgMetaData. We'll give it an initial null reference
// to us (since we really don't exist yet). Later, in ejbPostCreate,
// we'll set the reference properly.
_metaData = _mmdHome.create( _mid, null );
}
public void ejbPostCreate( String data ) throws CreateException, RemoteException {
System.out.println( "in ejbPostCreate( String )" );
// Update our associated meta data object's reference to us.
_metaData.setMessage( (Message) _context.getEJBObject() );
}
The MsgMetaData.setMessage method is a single line of code setting the
member reference.