Introducing the Spring Framework--2 (an article posted on TheServerSide.com)

本文介绍了Spring在数据访问和事务管理方面的优势。在数据访问上,Spring解决了JDBC使用中的问题,提供抽象API和有意义的异常层次结构,还支持多种数据访问方式的混合使用。在事务管理方面,Spring提供了编程式和声明式管理,其事务抽象不依赖特定技术,便于应用扩展。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JDBC abstraction and data access exception hierarchy

Data access is another area in which Spring shines.

JDBC offers fairly good abstraction from the underlying database, but is a painful API to use. Some of the problems include:

  • The need for verbose error handling to ensure that ResultSets, Statements and (most importantly) Connections are closed after use. This means that correct use of JDBC can quickly result in a lot of code. It's also a common source of errors. Connection leaks can quickly bring applications down under load.
  • The relatively uninformative SQLException. JDBC does not offer an exception hierarchy, but throws SQLException in response to all errors. Finding out what actually went wrong - for example, was the problem a deadlock or invalid SQL? - involves examining the SQLState value and error code. The meaning of these values varies between databases.

Spring addresses these problems in two ways:

  • By providing APIs that move tedious and error-prone exception handling out of application code into the framework. The framework takes care of all exception handling; application code can concentrate on issuing the appropriate SQL and extracting results.
  • By providing a meaningful exception hierarchy for your application code to work with in place of SQLException. When Spring first obtains a connection from a DataSource it examines the metadata to determine the database product. It uses this knowledge to map SQLExceptions to the correct exception in its own hierarchy descended from org.springframework.dao.DataAccessException. Thus your code can work with meaningful exceptions, and need not worry about proprietary SQLState or error codes. Spring's data access exceptions are not JDBC-specific, so your DAOs are not necessarily tied to JDBC because of the exceptions they may throw.

The following UML class diagram illustrates a part of this data access exception hierarchy, indicating its sophistication. Note that none of the exceptions shown here is JDBC-specific. There are JDBC-specific subclasses of some of these exceptions, but calling code is generally abstracted wholly away from dependence on JDBC: an essential if you wish to use truly API-agnostic DAO interfaces to hide your persistence strategy.

Spring provides two levels of JDBC abstraction API. The first, in the org.springframework.jdbc.core package, uses callbacks to move control - and hence error handling and connection acquisition and release - from application code inside the framework. This is a different type of Inversion of Control, but equally valuable to that used for configuration management.

Spring uses a similar callback approach to address several other APIs that involve special steps to acquire and cleanup resources, such as JDO (acquiring and relinquishing a PersistenceManager), transaction management (using JTA) and JNDI. Spring classes that perform such callbacks are called templates.

For example, the Spring JdbcTemplate object can be used to perform a SQL query and save the results in a list as follows:

JdbcTemplate template = new JdbcTemplate(dataSource);
List names = template.query("SELECT USER.NAME FROM USER",
	new RowMapper() {
		public Object mapRow(ResultSet rs, int rowNum) throws SQLException;
			return rs.getString(1);
		}
	});

The mapRow callback method will be invoked for each row of the ResultSet.

Note that application code within the callback is free to throw SQLException: Spring will catch any exceptions and rethrow them in its own hierarchy. The application developer can choose which exceptions, if any, to catch and handle.

The JdbcTemplate provides many methods to support different scenarios including prepared statements and batch updates. Simple tasks like running SQL functions can be accomplished without a callback, as follows. The example also illustrates the use of bind variables:

int youngUserCount = template.queryForInt("SELECT COUNT(0) FROM USER WHERE USER.AGE < ?",
	new Object[] { new Integer(25) });

The Spring JDBC abstraction has a very low performance overhead beyond standard JDBC, even when working with huge result sets. (In one project in 2004, we profiled the performance of a financial application performing up to 1.2 million inserts per transaction. The overhead of Spring JDBC was minimal, and the use of Spring facilitated the tuning of batch sizes and other parameters.)

The higher level JDBC abstraction is in the org.springframework.jdbc.object package. This is built on the core JDBC callback functionality, but provides an API in which an RDBMS operation - whether query, update or stored procedure - is modelled as a Java object. This API was partly inspired by the JDO query API, which I found intuitive and highly usable.

A query object to return User objects might look like this:

class UserQuery extends MappingSqlQuery {

	public UserQuery(DataSource datasource) {
		super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?");
		declareParameter(new SqlParameter(Types.NUMERIC));
		compile();
	}

	// Map a result set row to a Java object
	protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
		User user = new User();
		user.setId(rs.getLong("USER_ID"));
		user.setForename(rs.getString("FORENAME"));
		return user;
	}

	public User findUser(long id) {
		// Use superclass convenience method to provide strong typing
		return (User) findObject(id);
	}
}

This class can be used as follows:

User user = userQuery.findUser(25);

Such objects are often inner classes inside DAOs. They are threadsafe, unless the subclass does something unusual.

Another important class in the org.springframework.jdbc.object package is the StoredProcedure class. Spring enables a stored procedure to be proxied by a Java class with a single business method. If you like, you can define an interface that the stored procedure implements, meaning that you can free your application code from depending on the use of a stored procedure at all.

The Spring data access exception hierarchy is based on unchecked (runtime) exceptions. Having worked with Spring on several projects I'm more and more convinced that this was the right decision.

Data access exceptions not usually recoverable. For example, if we can't connect to the database, a particular business object is unlikely to be able to work around the problem. One potential exception is optimistic locking violations, but not all applications use optimistic locking. It's usually bad to be forced to write code to catch fatal exceptions that can't be sensibly handled. Letting them propagate to top-level handlers like the servlet or EJB container is usually more appropriate. All Spring data access exceptions are subclasses of DataAccessException, so if we do choose to catch all Spring data access exceptions, we can easily do so.

Note that if we do want to recover from an unchecked data access exception, we can still do so. We can write code to handle only the recoverable condition. For example, if we consider that only an optimistic locking violation is recoverable, we can write code in a Spring DAO as follows:

try {
	// do work
}
catch (OptimisticLockingFailureException ex) {
	// I'm interested in this
}

If Spring data access exceptions were checked, we'd need to write the following code. Note that we could choose to write this anyway:

try {
	// do work
}
catch (OptimisticLockingFailureException ex) {
	// I'm interested in this
}
catch (DataAccessException ex) {
	// Fatal; just rethrow it
}

One potential objection to the first example - that the compiler can't enforce handling the potentially recoverable exception - applies also to the second. Because we're forced to catch the base exception (DataAccessException), the compiler won't enforce a check for a subclass (OptimisticLockingFailureException). So the compiler would force us to write code to handle an unrecoverable problem, but provide no help in forcing us to deal with the recoverable problem.

Spring's use of unchecked data access exceptions is consistent with that of many - probably most - successful persistence frameworks. (Indeed, it was partly inspired by JDO.) JDBC is one of the few data access APIs to use checked exceptions. TopLink and JDO, for example, use unchecked exceptions exclusively. Hibernate switched from checked to unchecked exceptions in version 3.

Spring JDBC can help you in several ways:

  • You'll never need to write a finally block again to use JDBC
  • Connection leaks will be a thing of the past
  • You'll need to write less code overall, and that code will be clearly focused on the necessary SQL
  • You'll never need to dig through your RDBMS documentation to work out what obscure error code it returns for a bad column name. Your application won't be dependent on RDBMS-specific error handling code.
  • Whatever persistence technology use, you'll find it easy to implement the DAO pattern without business logic depending on any particular data access API.
  • You'll benefit from improved portability (compared to raw JDBC) in advanced areas such as BLOB handling and invoking stored procedures that return result sets.

In practice we find that all this amounts to substantial productivity gains and fewer bugs. I used to loathe writing JDBC code; now I find that I can focus on the SQL I want to execute, rather than the incidentals of JDBC resource management.

Spring's JDBC abstraction can be used standalone if desired - you are not forced to use the other parts of Spring.

O/R mapping integration

Of course often you want to use O/R mapping, rather than use relational data access. Your overall application framework must support this also. Thus Spring integrates out of the box with Hibernate (versions 2 and 3), JDO (versions 1 and 2), TopLink and other ORM products. Its data access architecture allows it to integrate with any underlying data access technology. Spring and Hibernate are a particularly popular combination.

Why would you use an ORM product plus Spring, instead of the ORM product directly? Spring adds significant value in the following areas:

  • Session management. Spring offers efficient, easy, and safe handling of units of work such as Hibernate or TopLink Sessions. Related code using the ORM tool alone generally needs to use the same "Session" object for efficiency and proper transaction handling. Spring can transparently create and bind a session to the current thread, using either a declarative, AOP method interceptor approach, or by using an explicit, "template" wrapper class at the Java code level. Thus Spring solves many of the usage issues that affect many users of ORM technology.
  • Resource management. Spring application contexts can handle the location and configuration of Hibernate SessionFactories, JDBC datasources, and other related resources. This makes these values easy to manage and change.
  • Integrated transaction management. Spring allows you to wrap your ORM code with either a declarative, AOP method interceptor, or an explicit 'template' wrapper class at the Java code level. In either case, transaction semantics are handled for you, and proper transaction handling (rollback, etc.) in case of exceptions is taken care of. As we discuss later, you also get the benefit of being able to use and swap various transaction managers, without your ORM-related code being affected. As an added benefit, JDBC-related code can fully integrate transactionally with ORM code, in the case of most supported ORM tools. This is useful for handling functionality not amenable to ORM.
  • Exception wrapping, as described above. Spring can wrap exceptions from the ORM layer, converting them from proprietary (possibly checked) exceptions, to a set of abstracted runtime exceptions. This allows you to handle most persistence exceptions, which are non-recoverable, only in the appropriate layers, without annoying boilerplate catches/throws, and exception declarations. You can still trap and handle exceptions anywhere you need to. Remember that JDBC exceptions (including DB specific dialects) are also converted to the same hierarchy, meaning that you can perform some operations with JDBC within a consistent programming model.
  • To avoid vendor lock-in. ORM solutions have different performance other characterics, and there is no perfect one size fits all solution. Alternatively, you may find that certain functionality is just not suited to an implemention using your ORM tool. Thus it makes sense to decouple your architecture from the tool-specific implementations of your data access object interfaces. If you may ever need to switch to another implementation for reasons of functionality, performance, or any other concerns, using Spring now can make the eventual switch much easier. Spring's abstraction of your ORM tool's Transactions and Exceptions, along with its IoC approach which allow you to easily swap in mapper/DAO objects implementing data-access functionality, make it easy to isolate all ORM-specific code in one area of your application, without sacrificing any of the power of your ORM tool. The PetClinic sample application shipped with Spring demonstrates the portability benefits that Spring offers, through providing variants that use JDBC, Hibernate, TopLink and Apache OJB to implement the persistence layer.
  • Ease of testing. Spring's inversion of control approach makes it easy to swap the implementations and locations of resources such as Hibernate session factories, datasources, transaction managers, and mapper object implementations (if needed). This makes it much easier to isolate and test each piece of persistence-related code in isolation.

Above all, Spring facilitates a mix-and-match approach to data access. Despite the claims of some ORM vendors, ORM is not the solution to all problems, although it is a valuable productivity win in many cases. Spring enables a consistent architecture, and transaction strategy, even if you mix and match persistence approaches, even without using JTA.

In cases where ORM is not ideally suited, Spring's simplified JDBC is not the only option: the "mapped statement" approach provided by iBATIS SQL Maps is worth a look. It provides a high level of control over SQL, while still automating the creation of mapped objects from query results. Spring integrates with SQL Maps out of the box. Spring's PetStore sample application illustrates iBATIS integration. Transaction management

Abstracting a data access API is not enough; we also need to consider transaction management. JTA is the obvious solution, but it's a cumbersome API to use directly, and as a result many J2EE developers used to feel that EJB CMT is the only rational option for transaction management. Spring has changed that.

Spring provides its own abstraction for transaction management. Spring uses this to deliver:

  • Programmatic transaction management via a callback template analogous to the JdbcTemplate, which is much easier to use than straight JTA
  • Declarative transaction management analogous to EJB CMT, but without the need for an EJB container. Actually, as we'll see, Spring's declarative transaction management capability is a semantically compatible superset of EJB CMT, with some unique and important benefits.

Spring's transaction abstraction is unique in that it's not tied to JTA or any other transaction management technology. Spring uses the concept of a transaction strategy that decouples application code from the underlying transaction infrastructure (such as JDBC).

Why should you care about this? Isn't JTA the best answer for all transaction management? If you're writing an application that uses only a single database, you don't need the complexity of JTA. You're not interested in XA transactions or two phase commit. You may not even need a high-end application server that provides these things. But, on the other hand, you don't want to have to rewrite your code should you ever have to work with multiple data sources.

Imagine you decide to avoid the overhead of JTA by using JDBC or Hibernate transactions directly. If you ever need to work with multiple data sources, you'll have to rip out all that transaction management code and replace it with JTA transactions. This isn't very attractive and led most writers on J2EE, including myself, to recommend using global JTA transactions exclusively, effectively ruling out using a simple web container such as Tomcat for transactional applications. Using the Spring transaction abstraction, however, you only have to reconfigure Spring to use a JTA, rather than JDBC or Hibernate, transaction strategy and you're done. This is a configuration change, not a code change. Thus, Spring enables you to write applications that can scale down as well as up.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值