jRelationalFrameworkadvanced topics |
These are more in-depth topics that are not covered in the other pages:
JDBCHelper jdbcHelper =
new JDBCHelper(
"oracle.jdbc.driver.OracleDriver",
"jdbc:oracle:thin:@pluto.is.com:1521:testdb",
"testuser",
"testpassword");
While it should be rarely necessary to "roll your own" queries like the one below (since the framework does all this for you), here is an example of how the JDBCHelper could be used separate from the framework. Using the JDBCHelper makes for somewhat cleaner, simpler code. When the JDBCHelper is closed, the default behavior is for the connection for committed and closed.
List result = new ArrayList(20);
JDBCHelper helper = null;
try
{
helper = new JDBCHelper("weblogic.jdbc.pool.Driver",
"jdbc:weblogic:pool",
"tmsPool");
helper.executeQuery("SELECT
* FROM Status");
while (helper.next())
{
StatusValue
aStatusValue = new StatusValue();
aStatusValue.setStatusId(helper.getInteger("StatusId"));
aStatusValue.setCode(helper.getString("Code"));
aStatusValue.setActive(helper.getboolean("Active"));
result.addElement(aStatusValue);
}
// while
helper.close();
}
catch (Exception e)
{
try
{
helper.rollback();
helper.close();
}
catch (java.sql.SQLException
e)
{
// The original exception should take precedence
}
System.out.println("***" +
e);
e.printStackTrace();
throw e;
}
// use or return the result
list here.
JDBCHelperPool.createPool(
"XYZ_DB", // Name of the pool
JDBCHelperFactory.create(), // or however you create a JDBCHelper
5); // number of instances in the pool.
If you have more than one database, you can create a separate pool for each database. When a JDBCHelper instance is needed, do something like this:
JDBCHelperPool.getPool("XYZ_DB").getJDBCHelper();
or this:
JDBCHelperPool.getFrom("XYZ_DB");
The above two statements are functionally identical. When you close the JDBCHelper (i.e. aJDBCHelper.close() ) it is automatically returned to the pool and you must retrieve a new one to do any more database calls. However, manually getting and closing JDBCHelper instances is not necessary if you are using the framework to do finds, saves, and deletes.
When you are done with the pool, you can close the connections like this:
JDBCHelperPool.getPool("XYZ_DB").destroy();
It is not a bad idea to use the JDBCHelper pool even if you have a single-threaded application since it will reduce the number of JDBCHelper instances that are created. It will also reduce the overhead of creating so many connections. This pool will keep you from creating more JDBCHelper instances than necessary.
How to use it:
In the setup() method of your AbstractDomain subclass add a line like
this:
this.setJDBCHelperPoolName("XYZ_DB");
Then the
framework will access that pool whenever it needs a database connection.
After postFind() is called, the framework automatically sets the persistent state of the object to CurrentPersistentState.
NO_POST_FIND - Be aware that an infinite loop will occur when using postFind() methods in two domains that end up calling each other. To keep that from happening, one of the postFind() methods should create the other domain with the NO_POST_FIND option. See the CustomerDomain.postFind() example for how this is done.
If you use the save() method with manual transaction control then you
need to understand the *Warning* area of the Transactions
section. This may not seem related, but it is because the default
behavior of save() is to call find() (which calls postFind()) before returning.
If you are not manually controlling transactions then this is not as much
of an issue.
Here is the order of what happens inside of a find():
The last thing the save() method does is to "find" the object it just saved and return it. The intention of this is to make sure the returned object accurately represents the data in the database. For example, setting a Timestamp column to NOW in the SQL requires a find for that row to figure out what the database set it to. In addition, if triggers are used, it is important to pick up the changes that were made to the table by those triggers.
Here is the order of what happens inside of a save. If this save() call is inside of another transaction, the transaction items below won't do anything.
All of these methods have a PersistentObject instance and a JDBCHelper instance as parameters. The JDBCHelper parameter is the instance that is controlling the transaction. If you wish to make a JDBC call that is part of that same transaction, then it is important to pass that JDBCHelper along to any other domain calls.
Note that preSave() is called after the validation occurs so you can
depend on the object being valid at this point. See the save()
section for the order of events during a save().
The two types of validation done are:
If a column is listed as REQUIRED and it returns null (or an
empty string), then MissingAttributeException is thrown.
Using a Timestamp or an Integer column as an optimistic lock is very easy with this framework. A Timestamp column that is defined as an optimistic lock is automatically updated to the current time whenever the object is saved. An Integer optimistic lock column is automatically incremented by one when the object is saved. If the object is updated by another user between the time the current user reads the object and saves it back to the database, an ObjectHasChangedException will be thrown to the current user. The other users object changes can be retrieved with anObjectHasChangedException.
Click here for an example of how to set one up.
Note that, because some database's Timestamps (like Oracle's) are only precise to the nearest second, it is possible that two users could update the same object in the same second. In that case, the data from the first save would be lost without any warning or error message. For these databases it is better to use an Integer column for the optimistic lock.
DEFAULT_TO_NOW should always be used for the Timestamp default
value. When updates occur, the value of the timestamp lock is included
in the WHERE clause so that if someone else has changed that row since
it was read, the update will not occur and ObjectHasChangedException
will be thrown. As mentioned before, If the update does occur, the
table timestamp is updated with the current date and time.
To create an aggregate object, use a null for the ColumnSpec setters (optional) and override the postFind() method to access those columns directly to create the aggregate object. This area could be a little more automated in the future, but at least this can be done right now.
For an example, see the Media object creation code in
VideoDomain#postFind()
You won't need to think much about transactions if you want the transaction to span only one call to save() (which also calls preSave(), postSave()) or delete(). Each of these will begin and end (or rollback if an error occurs) their own transactions.
This framework uses the database's transaction mechanism to group updates into one transaction. For multiple updates to share a transaction, the same java.sql.Connection (and hence the same JDBCHelper) instance must be used. See ExampleTest.test000Setup() in the examples directory of the distribution zip file for an example.
If you want to enlarge the scope of the transaction to more than one domain update call, do something like this...
public void saveThings(List things)
throws
ObjectHasChangedException,
MissingAttributeException,
DuplicateRowException
{
JDBCHelper jdbcHelper = this.getJDBCHelper();
this.beginTransaction(jdbcHelper);
Iterator iterator = things.iterator();
while (iterator.hasNext())
{
Thing aThing = (Thing) iterator.next();
new OtherThingDomain().save(aThing.getOtherThing(),
jdbcHelper);
this.save(aThing, jdbcHelper);
this.executeSQLUpdate("DELETE FROM temptable",
jdbcHelper);
}
this.endTransaction(jdbcHelper);
}
Notice how we used the same JDBCHelper object for beginning the transaction, doing the saves, doing the custom SQL, and ending the transaction. Assuming that only domain methods are used to do the updates, the framework will catch any exception, rollback the transaction, and rethrow the exception so there is NO NEED to put the call to endTransaction(jdbcHelper) into a finally block.
Or similarly if the code is outside of a Domain instance:
....
JDBCHelper jdbcHelper = JDBCHelperPool.getFrom("XYZ_DB");
jdbcHelper.beginTransaction();
i_personDomain.save(aPerson, jdbcHelper);
i_partDomain.save(aPart, jdbcHelper);
jdbcHelper.endTransaction();
jdbcHelper.close(); // return it to the pool
....
Notice that the above code does NOT NEED to explicitly catch exceptions in order to rollback because all updates are done inside of domain methods. Any exception that occurs inside of a domain update automatically rolls back the transaction.
Transaction Gotchas
Some of the below stuff gets pretty complex.
If you want to read it, go ahead, but otherwise ignore it and keep it in mind
for when you run into transaction problems. Your answer may be here.
*Warning* If you are manually beginning and ending a transaction using Oracle or another multi-threaded database it is important that you use the same JDBCHelper instance for any queries inside of that transaction that wish to access any new or changed table rows. (That was a mouthful :-). A different JDBCHelper instance will not see the changes made until the original transaction is committed.
This issue will most likely show up if you are using postFind() to attach an object that was saved inside the same transaction, because (as of version 1.2 and 1.3) postFind() forces you to clone the JDBCHelper if you wish to use it to find other objects. Remember that the default behavior of a save() is to do a find() at the very end before returning (see save() above). If you hope to use the object returned from a save() you need to be aware of this potential.
There is at least one solution to keep people from getting tripped-up on this, but it involves a lot of code changes. Hopefully, in a future version this problem can be addressed. We are working very hard to make this framework as intuitive to use as possible.
XADataSource and Transactions - If you are using an XA DataSource you
will need to make sure that JDBCHelper has shouldCommitOnClose set
to false. Also, If you need to do multiple updates you can safely do it by
beginning the transaction at the top of the method and close the JDBCHelper
at the bottom without ever ending the transaction. XADataSources
don't allow you to manually commit(end) a transaction, but you must close
the JDBCHelper in order to return the connection to the pool.
jRF, like most frameworks, makes a few assumptions about your implementation in order to keep the complexity-level of the code lower:
CustomerDomain customerDomain = new CustomerDomain();
ResultPageIterator iterator =
new ResultPageIterator(customerDomain,
10)
{
List doFind(AbstractDomain
domain)
{
return domain.findAll();
}
};
while (iterator.hasNext())
{
List results = iterator.nextPage();
// do something with this page of 10 objects...
}
top
main page
noticed
a document error?
copyright © 2000 is.com