Tuesday, August 5, 2008

Having to scale a web application?

In a recent sprint planning meeting, solution owners unveiled how a new customer would be using our web application. Of course, it's a web application and, as such, needs to support many, concurrent users. Up until now, we were looking for our application to support approximately roughly 500 users; probably not concurrent, but potentially.

With learning how the new client will use the application, we may blow those numbers out of the water, both overall users as well as concurrent. So, how do you program and application to support large, very large volumes of users? Will the application scale if we cluster the web application servers? Will performance decrease with more users? Can we just throw a bigger machine with more CPU & memory to handle the load?

I'm a big believer in the fact that scalability needs to be designed from the get-go and then monitored. While some items may not be implemented immediately due to project constraints, scalability needs to be considered from day 1 and the code watched and reviewed to ensure new designs, code, etc will not adversely affect scalability and potentially performance.

Two articles were published in TSS on scaling JEE applications that "hit the nail on the head". If you're faced with having to support volumes of users, these articles are a must read as the writer had (has?) the opportunity to pound on application to test their ability to scale under heavy load AND to analyze why they failed or succeeded.

If you're not having to program for scalability now, the articles are still an excellent read & resource!

Scaling Your Java EE Applications - Part 1
Scaling Your Java EE Applications Part 2

Tuesday, July 1, 2008

SVN Merge (Trunk to Branch)

Ever have code changes that need to be pushed into a branch? Or merged back into HEAD or the trunk?

Recently (today), I had the need to merge code changes from HEAD into a newly created branch. Given the fact that my changes spanned a couple of weeks (no lectures, please, as I was on vacation :D), I could not remember all of the lines that were changed in 9 files. I didn't want to blindly copy the files into the branch as I might (shouldn't really) overwrite another developers changes.

After a quick google search and a read of a short blog posting, I had found a quick path forward. For the same reasons that caused the Jake to write a blog, I'm also writing this so that I can easily find it.

I checked in my files into HEAD and got the revision number (7200). Then I changed directories to the directory of the branch and ran the following command:

svn merge -r 7199:7200 https://phlcvs01/svn/netcds/trunk .

If you want to preview the changes, specify '--dry-run' which causes SVN to list the changes that will occur. Using '-r 7199:7200' causes subversion to only grab the differences between those revisions. Upon executing the command, 'svn stat' shows the modified files that you need to check into the branch.

Simple and easy.

Monday, March 17, 2008

Supporting Multiple DBs using iBatis

I previously wrote about configuring multiple data sources within JBoss. When writing SQL, you may undoubtedly encounter cases where SQL is not ANSI 92 compliant. In other words, you have DB specific SQL statements. Maybe it's because of performance reasons. Maybe it's because the difference in handling sequential columns. How can you write, configure, & deploy the application without having specific SQL statements being handled in your Java code?

We encountered this issue on our current project and created an elegant, but simple solution.

Using iBatis for our Java to DB mapping framework, all of our SQL statements were contained within XML files. With the potential requirement to support multiple databases (SQL Server & at least Oracle), we wanted a way to reuse the statements that were compliant across both databases.

Step 1:
Within the MappedStatements XML files, we appended the DB name. For example, for Oracle specific SQL, the name would be 'getSomethingData-Oracle'. If the SQL was DB neutral, we omitted the DB designation. How do we modify the app to determine the appropriate SQL for the database at runtime?

Step 2:
Like good little programmers, we created a BaseDao class from which all concrete DAOs extended. Upon initialization of a DAO, our BaseDao class retrieved the connection meta data and determined the connected DB. Now, we know the runtime DB. But how do we choose the appropriate SQL?

Step 3:
We modified BaseDao to extend org.springframework.orm.ibatis.support.SqlMapClientDaoSupport providing our code the ability to retrieve a MappedStatement from the XML files. This class extension gives our code the ability to check for the existence of a MappedStatement - be it DB specific or non-DB specific.

Step 4:
Finally, within the concrete DAO classes, we called 'checkMappedStatement' when attempting to retrieve any MappedStatement. The BaseDao class handles retrieving the appropriate SQL for the runtime DB - be it specific or generic.

The concrete DAOs would retrieve the SQL from iBatis using the following construct. If DB specific SQL existed within the MappingStatement XML files for the runtime DB, that SQL would be returned.

getSqlMapClientTemplate().queryForList(checkMappedStatement("getSomethingData"));

Below is our simple BaseDao class. Quite simple.

public class BaseDaoiBatis extends SqlMapClientDaoSupport {
private static final Logger log = Logger.getLogger(BaseDaoiBatis.class);
private String dbProduct = null;

protected String checkMappedStatement(String id) {
MappedStatement ms;
String statementId = id;

try {
ms = ((SqlMapClientImpl) getSqlMapClient()).getMappedStatement(id + "-" + getDbProduct());

// Look for DB specific SQL. If found, return DB specific mapping id
if (ms != null) {
statementId = id + "-" + dbProduct;
}
}
catch (SqlMapException sme) {
// If not found, use default SQL mapping
log.debug("DB-specific SQL not found, using default SQL mapping");
}

return statementId;
}

private void initDbProduct() {
DatabaseMetaData dbMetaData;
Connection conn = null;

try {
conn = getSqlMapClientTemplate().getDataSource().getConnection();
dbMetaData = conn.getMetaData();

log.info("Database product name is '" + dbMetaData.getDatabaseProductName() + "'");

if (dbMetaData.getDatabaseProductName().indexOf("SQL Server") > 0) {
dbProduct = "MSSQLServer"
} else if (dbMetaData.getDatabaseProductName().indexOf("Oracle") > 0) {
dbProduct = "Oracle"
} else {
dbProduct = dbMetaData.getDatabaseProductName();
}

log.info("Using " + dbProduct + " XML statements...");
}
catch (Exception e) {
log.error("Exception occurred obtaining database information", e);
}
finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
log.error("SQLException thrown when closing connection");
}
}
}
}
}