- 浏览: 222830 次
- 性别:
- 来自: 成都
文章分类
- 全部博客 (213)
- SQLServer (8)
- flex (8)
- 文章 (5)
- java (91)
- 数据结构 (0)
- 设计模式 (0)
- C# (2)
- Oracle (4)
- 技术 (4)
- 云计算 (0)
- 算法 (0)
- 记录 (3)
- javascript (5)
- div/css (1)
- http (0)
- IE (1)
- web (1)
- hadoop (0)
- extjs (4)
- hibernate (6)
- 错误记录 (5)
- mysql (4)
- json (1)
- jvm (1)
- spring (4)
- 工具 (2)
- tomcat (3)
- cxf (3)
- spring data (1)
- memcached (5)
- android-exception (2)
- 数据压缩 (1)
- 博客 (2)
- bat (0)
- nginx (3)
- svn (2)
- jpa (1)
- windows (2)
- h2 (2)
- webservice (2)
- android (5)
- oa (0)
- eclipse (2)
- jquery (2)
- jni (4)
- weblogic (1)
- work (0)
- smartclient (1)
- sql (0)
- excel (0)
- test (0)
- t (0)
- js (4)
- utils (0)
- bootstrap (0)
- sniper (0)
- ztree (0)
- google (0)
- mdb (0)
- redis (1)
- 思想 (1)
- css (0)
- appCan (0)
- activiti (0)
- 工作 (0)
- 浏览器 (1)
http://dapple.iteye.com/blog/762079
http://hi.baidu.com/dapplehou/blog/item/6ba8034f5114e038aec3abdb.html
Table of Contents
1 言前
2 项目1 (久远,尚无SSH流行)
2.1 使用方式
2.2 原理
2.3 UML
2.4 优点
2.5 缺点
3 项目2
3.1 使用方式(步骤)
3.2 原理
3.3 UML
3.4 优点:
3.5 缺点
4 项目3(大约04年,hibernate)
4.1 使用方式
4.2 原理( session 的获取方式 )
4.3 UML
4.4 优缺点
4.5 两个对hibernate使用的例子
5 项目4 (struts+spring+ibatis)
5.1 架构说明
5.2 使用方式及其原理
5.2.1 UserDAO的实现以及如何使用第三方SqlMapClient.
5.2.2 为什么SqlMapClientFactoryBean与SqlMapClient类型不同也可被注入?
5.2.3 如何使用IBatis
6 Spring DAO
7 多库、多数据源
8 结束
1 言前
两、三年前说过要坚持写文剖析各种算法、面向对象、框架、架构及各种应用技术,然进展缓慢,一者力有不逮,二者工作变动,无暇它顾。如今咬牙还债,算给自己个交代。这次就从最常用的DAO开始吧,
J2EE应用开发中,数据库的使用非常频繁,大多公司里所使用的Framework都有对数据库操作进行封装的被称为DAO层的部分。下面,我结合自己在不同时期所参与的项目,就各种DAO层的封装方式进行一下比照。
2 项目1 (久远,尚无SSH流行)
2.1 使用方式
用户DAO继承BaseDAO
在业务模块初始化用户DAO,并传递给如下3个参数
- String dataSourceName 数据源名称(JNDI).
- boolean autoReleaseConnection 是否自动释放链接.
- boolean useStoreProcedure 是否使用存储过程.
在业务模块根据需要调用用户DAO对象的相应方法.
例子: 业务逻辑中调用:
UserDAO userDAO = new UserDAO(null,false,false);
List allUser = userDAO.getAllUser();
2.2 原理
主要实现了对SQL执行过程的封装。以一个查询SQL为例,它的书写只需要这么一句:
DataSet ds = execQuery(querySQL);
在execQuery(String sql)中,做了3件事:
获取连接
使用获取的连接执行SQL
把从数据库中取得的数据封装进DateSet对象中,返回给用户。程序员在用户DAO对象里面进行组装SQL的工作,然后调用父类提供的执行 execQuery方法,返回封装的DataSet对象,该对象以List方式存放着所有查询结果。
2.3 UML
2.4 优点
省却了婆婆妈妈的RecordSet, Statement的声明和繁琐重复的异常捕获。把查询结果放进缓存,不用一直维持着数据库的连接,可以减少数据库的数据连接压力。
2.5 缺点
把DAO的模式控制放进业务模块里不合适。
只支持单一数据库。
数据量太大的查询,会导致内存不足。
3 项目2
3.1 使用方式(步骤)
业务逻辑中声明用户自定义的DAO,它必须继承自framework中的 BaseDAO 比如
IndexDAO dao = new IndexDAO();
在业务逻辑中把请求数据封装为DataSet 比如:
DataSet dataset=this.getDataSet(request);
业务逻辑中使用DAO处理DataSet 比如
dao.addData(dataset,user);
在用户自定义DAO的addData方法中继续对DataSet对象进行补充,比如:
ds.set("userstatus", "0");
ds.tablename = "T_Pop_User";
设置完DataSet后,最关键的一步来了,就是DBOperator直接对DataSet 进行操作,比如:
dboperator.insertData(ds);
3.2 原理
对客户端(浏览器)数据进行自动封装
在服务端对封装的数据进行自动解析,解析成SQL,并执行.
实际上,这相当于建立了从客户端(一般而言是Form表单)到数据库表间的映射.
3.3 UML
3.4 优点:
使用过程中不但不需要写繁复的连接释放等代码,而且连SQL语句都不用写了,这使得整个代码显得清晰简洁。根据用户的DataSet对象动态、智能的生成SQL的工作都被隐藏到了框架内部.
把用户请求被自动封装为DataSet,免去了用户使用request对象方法一个一个的去接收数据。DAO层自动根据DataSet生成SQL去执行。在这个过程中,程序员做的工作被极大的简化。
支持多库。
3.5 缺点
被封装的DataSet里需要包含数据库字段信息,才能被程序自动解析为 SQL,这要求前端Form中的Field名称必须和数据库字段名称一致(严格的说,要求传递给Server端的数据的Field字段必须与数据库字段名称一致)。这或多或少限制了前端开发的灵活性。但考虑到整个过程的便利性,这点限制并非不值。
前端Form直接暴露数据库字段信息不安全。对于信息安全要求高的系统这样的做法会直接Pass掉。
4 项目3(大约04年,hibernate)
4.1 使用方式
基本上式是直接使用hibernate进行DAO层操作,未对hibernate进行二次封装;
在Action中使用如下方式得到User对象,
User user = new User();
user.setID(23);
user.setName("Dapple");
UserManager.updateUser(user);
在UserManager中,通过SessionManager直接使用session进行数据对象存取;
Session s = SessionManager.getSession();
s.update(buyer);
4.2 原理( session 的获取方式 )
在SessionManager中,session的获取方式如下,
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session s = sessionFactory.openSession();
这段代码会读取hibernate配置文件(hibernate.cfg.xml ), hibernate配置文件中有数据库连接信息,对象与数据库之间的映射文件的地址;
Hibernate的原理请参照hibernate相关文档;
4.3 UML
4.4 优缺点
其实就是hibernate的优缺点。这个大家可以参考已经非常丰富的hibernate 资料;从审美角度上看,hibernate使数据存取代码简洁、清晰。而简洁清晰的代码总是更容易使用。
4.5 两个对hibernate使用的例子
在下一个紧接着的项目中,有的模块对hibernate又进行了一层简单的封装,这样做的目的是为了使之扩展性更强。但实际上这也同时使得代码复杂度增加。这中间的平衡取舍往往不单单是技术问题。
下面我们这里举两种封装例子。
先看UML
从这个UML中,我们可以看出它的思路,就是尘归尘、土归土,就是user的业务处理归UserManager,user的数据处理归UserManagerDAO.两者互不干扰。假如现在需求有变,数据被要求存在文件里而非数据库中,要做哪些工作呢?
新写一个实现了UserManagerDAO接口的UserManagerDAOFileImpl实体类。
DAOFactory中需要增加一个生成负责文件存储的实现类的方法,我们暂叫它为方法B;(原先负责生成数据库存储实现类的方法叫做方法A;)
UserManager中需要把原来DAOFactory中调用方法A的代码修改为调用方法B;
可以看出,已有代码中修改了2处。这正是简单工厂模式的特点。
先看UML
它减少了Manager这一层,而让用户直接使用DAO.它使用的模式是工厂模式。假如需求也有变,要求把数据存进文件里而非数据库中,要做哪些工作呢?
新写两个类,即继承了DAOFactory抽象类的DAOFileFactory和实现了 UserDAO接口的UserDAOFileImpl实体类。
在实例化DAOHBFactory的地方(这里实际上是DAOFactory抽象类中有一个实现具体的实现方法)做出修改。该为实例化DAOFileFactory。
我们可以看出,这里对已有代码修改了一处。大家都知道OCP原则,即对扩展开放,对修改关闭。其本质就是对于已经存在的代码尽量不去动就能实现新的功能(扩展),其完全实现必然伴随形成热插拔的能力。
这里,仍然对已有代码做了一处修改,必然还需要重新编译。如果把实例化的控制开关放进配置文件中又配置人员来设定,就可以不需要再修改代码再编译,则基本可以看做可热插拔了。
5 项目4 (struts+spring+ibatis)
补充一点,所有项目的业务含义就不说了。因为那样做不好。
5.1 架构说明
架构采用 struts+spring+ibatis 组合。
请求被Action接收后, Action–>Logic–>Dao–>Ibatis. 符号"–>"代表调用. 关键的地方在于,所有的调用都是通过Spring的依赖注入实现,而不用程序员去写被调用对象的创建代码(比如写一个工厂类用来创建对象)。省了好多事哦。
Struts是最著名的MVC Framework,它的实现实质就是用一个Servlet统一接收请求,然后根据配置文件分发给不同的action,程序员在action中再调用自己的逻辑模块,处理完后,struts又根据配置文件把结果转发到相应的页面。这就完成了MVC的一个全过程。它是这么著名,以至于到处都是它的资料。所以欲知其详,请google.
Ibatis是半自动化数据封装方案。它的实现实质就是通过读取写在配置文件中的SQL语句自动生成数据对象.
Spring则发展的最火,因为它的确最棒。但要说它的实现实质,则也是通过读取含有class信息的配置文件,动态的生成类对象。多个class的依赖关系也写在配置文件中,Spring读取依赖关系、进行对象的构造,这就是依赖注入。
捎带提一句,要感谢夏昕为Spring的普及所做的贡献。正是2004年夏昕的 Spring Guide加速了Spring在众多的程序员中的普及进程。
配置文件的例子如下:
<bean id="user" class="test.User">
<property name="name"><value>张三</value></property>
<property name="age"><value>20</value></property>
</bean>
<bean id="department" class="test.Department">
<property name="user"><ref bean="user"/></property>
</bean>
这个配置文件中,Department类引用了User类(Spring会自动根据引用构建依赖关系,也可以手工指定依赖关系)。Spring读这些文件,然后通过反射就可以构造出user对象与Department对象。然后再在此基础上发展演化,变成了今天的Spring. 它的基本原理简单,但为什么这样的framework总是被老外开发出来呢?不是中国程序员笨,可能要从历史和大环境找原因吧。
不说闲话,回归正题。这个项目采用了struts+spring+ibatis做架构,我们就来看一下它的使用方式。
5.2 使用方式及其原理
这里用一个简单过程来说明。比如用户输入用户名和密码进行Logon,系统校验用户名密码是不是正确。(这是个演示例子,所以后面的代码一切从简)。
用户点击登陆,请求被struts送到了Logonaction,如下:
protected ActionForward doExecute(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception{
String name = request.getParameter("name");
String password = request.getParameter("password");
ServletContext sc = request.getSession().getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);
UserHandle userHandle = (UserHandle)ctx.getBean("userhandle"); //获取userhanle实例
boolean isSuccess = userHandle.check(name,password);
if(isSuccess){
return mapping.findForward("success");
}else{
return mapping.findForward("failed");
}
}
userHandle实例是通过容器(ApplicationContext)获取的,不需要自己写创建对象的代码了。容器实际上是通过读取配置文件加载UserHandle类的,而这个过程对于程序员是透明的。
然后我们再分析下面这一句:
boolean isSuccess = userHandle.check(name,password);
userHanle类的通常实现方法可能是这样,
class UserHandle{
public boolean check(String name,String password){
UserDAO userDao = DBOperator.getUserDAO(); //你自己的DB操作的封装类
return userDao.check(name,password);
}
}
然后就可以想象UserDAO里就是数据库连接,发送SQL的那一套。这样做,程序员就不得不写一个 DBOperator类,用来创建DAO. 而Spring就是用来解放这样的创建工作的,这样 DBOperator这样的类就没必要程序员劳神去写了,只需要这样做:
class UserHandle{
private UserDAO userDao;
public boolean check(String name,String password){
return userDao.check(name,password);
}
public void setUserDao(UserDAO userDao){
this.userDao = userDao;
}
public UserDAO getUserDao(){
return this.userDao;
}
}
Spring 会根据配置文件中的类的依赖关系,自动的把UserDAO对象注入到 UserHandle对象里,然后程序员去使用userDao就可以了。
既然是根据配置文件来注入的,那么配置文件是什么样子呢?如下:
<beans>
<!--4-->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>efmweb</value></property>
</bean>
<!--3-->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean" >
<property name="configLocation"><value>WEB-INF/sql-map-config.xml</value></property>
<property name="dataSource"><ref local="dataSource"/></property>
</bean>
<!--2-->
<bean id="userdao" class="UserDAO">
<property name="sqlMapClient"><ref bean="sqlMapClient"/></property>
</bean>
<!--1-->
<bean id="userhandle" class="UserHandle">
<property name="userDao"><ref bean="userdao" /></property>
</bean>
</beans>
从这个配置文件里我们可以看到它们的引用关系是:1–>2–>3–>4,即 UserHandle引用UserDAO, UserDAO引用 SqlMapClientFactoryBean, SqlMapClientFactoryBean引用 JndiObjectFactoryBean,JndiObjectFactoryBean负责根据jndiName获得 datasource.
通过这样层层引用的配置,spring就能自动生成对象,不但免了程序员手动写对象生成的代码,还很容易的把ibatis串连起来。
前面我们已经根据代码和配置文件知道了UserHandle是怎么引用和注入 UserDAO的方式,现在我们再看UserDAO是怎么引用 SqlMapClientFactoryBean 这个第三方类的.
5.2.1 UserDAO的实现以及如何使用第三方SqlMapClient.
根据UserHandle引用UserDAO的方式,我们也应该猜到UserDAO引用 SqlMapClientFactoryBean时应该大致是这样:
class UserDAO{
private SqlMapClientFactoryBean sqlMapClient;
public void setSqlMapClient(SqlMapClientFactoryBean sqlMapClient){
this.sqlMapClient = sqlMapClient;
}
public SqlMapClientFactoryBean getSqlMapClient(){
return this.sqlMapClient;
}
...;//你自己定义的其它业务相关userDao逻辑;
}
但实际上,第三方类已经把set get这样的方法封装好了,你只需要继承第三方类就可以了,如这样:
class UserDAO extends SqlMapClientDaoSupport{
...;//你自己定义的其它业务相关userDao逻辑;
}
这样就不用手工写那些set get方法了。原理也不难理解,那些set 和 get方法无非被挪进了SqlMapClientDaoSupport这个Spring提供的第三方类里面而已,我们可以看它的源码片段:
public abstract class SqlMapClientDaoSupport extends DaoSupport {
private SqlMapClientTemplate sqlMapClientTemplate = new SqlMapClientTemplate();
private boolean externalTemplate = false;
/**
* Set the JDBC DataSource to be used by this DAO.
* Not required: The SqlMapClient might carry a shared DataSource.
* @see #setSqlMapClient
*/
public final void setDataSource(DataSource dataSource) {
if (!this.externalTemplate) {
this.sqlMapClientTemplate.setDataSource(dataSource);
}
}
/**
* Return the JDBC DataSource used by this DAO.
*/
public final DataSource getDataSource() {
return this.sqlMapClientTemplate.getDataSource();
}
/**
* Set the iBATIS Database Layer SqlMapClient to work with.
* Either this or a "sqlMapClientTemplate" is required.
* @see #setSqlMapClientTemplate
*/
public final void setSqlMapClient(SqlMapClient sqlMapClient) {
if (!this.externalTemplate) {
this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient);
}
}
/**
* Return the iBATIS Database Layer SqlMapClient that this template works with.
*/
public final SqlMapClient getSqlMapClient() {
return this.sqlMapClientTemplate.getSqlMapClient();
}
/**
* Return the SqlMapClientTemplate for this DAO,
* pre-initialized with the SqlMapClient or set explicitly.
*/
public final SqlMapClientTemplate getSqlMapClientTemplate() {
return this.sqlMapClientTemplate;
}
}
5.2.2 为什么SqlMapClientFactoryBean与SqlMapClient类型不同也可被注入?
果然看到了getSqlMapClient()和setSqlMapClient()方法。我们仍然根据 UserHandle和UserDAO的注入方式,可以知道UserHandle的属性userDao其实就是UserDAO类的实例注入。同样,配置文件中UserDAO的属性sqlMapClient 也应该是SqlMapClientFactoryBean类的实例注入才对。但 SqlMapClientFactoryBean并不是SqlMapClient类型,这怎么能注入呢?
这是因为Spring的机制的缘故。简单的说,如果一个bean实现了 FactoryBean接口,那么Spring就不会把该bean本身实例化并返回,而是返回该bean的getObject()返回的对象。这是Sprign的游戏规则。我们来看一眼 SqlMapClientFactoryBean的源码片段:
public class SqlMapClientFactoryBean implements FactoryBean,InitializingBean {
private SqlMapClient sqlMapClient;
protected SqlMapClient buildSqlMapClient(Resource configLocation,Properties properties) throws IOException {
InputStream is = configLocation.getInputStream();
if (properties != null) {
if (buildSqlMapClientWithInputStreamAndPropertiesMethodAvailable) {
return SqlMapClientBuilder.buildSqlMapClient(is,properties);
} else {
return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader(is), properties);
}
} else {
if (buildSqlMapClientWithInputStreamMethodAvailable) {
return SqlMapClientBuilder.buildSqlMapClient(is);
} else {
return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader(is));
}
}
}
//这里就是返回的、并会被注入到其它类里的对象
public Object getObject() {
return this.sqlMapClient;
}
}
Spring这种机制的官方说明:
public interface FactoryBean
Interface to be implemented by objects used within a BeanFactory which are themselves factories. If a bean implements
this interface, it is used as a factory for an object to expose, not directly as a bean instance that will be exposed
itself.
NB: A bean that implements this interface cannot be used as a normal bean. A FactoryBean is defined in a bean style, but
the object exposed for bean references (getObject() is always the object that it creates.
FactoryBeans can support singletons and prototypes, and can either create objects lazily on demand or eagerly on
startup. The SmartFactoryBean interface allows for exposing more fine-grained behavioral metadata.
This interface is heavily used within the framework itself, for example for the AOP ProxyFactoryBean or the
JndiObjectFactoryBean. It can be used for application components as well; however, this is not common outside of
infrastructure code.
NOTE: FactoryBean objects participate in the containing BeanFactory's synchronization of bean creation. There is usually
no need for internal synchronization other than for purposes of lazy initialization within the FactoryBean itself (or
the like).
Since:
08.03.2003
Author:
Rod Johnson, Juergen Hoeller
See Also:
BeanFactory, ProxyFactoryBean, JndiObjectFactoryBean
到此为止,我们已经知道UserDAO与SqlMapClient的关系,接下来看如何使用IBatis
5.2.3 如何使用IBatis
Ibatis是半自动化数据对象封装方案。我们看userDao怎么使用Ibatis
class UserDAO{
private SqlMapClientFactoryBean sqlMapClient;
public void setSqlMapClient(SqlMapClientFactoryBean sqlMapClient){
this.sqlMapClient = sqlMapClient;
}
public SqlMapClientFactoryBean getSqlMapClient(){
return this.sqlMapClient;
}
//这里是使用Ibatis的地方
public User getUser(String userid) {
return super.getSqlMapClientTemplate().queryForList("userid",
userid);
}
}
结合者段代码以及前面的配置文件的第三段,相信你已经大体明白了Ibatis的使用。这里就做一简单描述。先重新看一下配置文件的第三段:
<!--3-->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean" >
<property name="configLocation"><value>WEB-INF/sql-map-config.xml</value></property>
<property name="dataSource"><ref local="dataSource"/></property>
</bean>
注意这里有一个sql-map-config.xml文件。这个文件的内容如下:
<sqlMapConfig>
<sqlMap resource="user.xml"/>
</sqlMapConfig>
再看user.xml文件里是什么?
<sqlMap>
<select id="userid" resultclass="UserDAO" parameterClass="java.lang.String">
select * from user where userid=#userid#
</select>
</sqlMap>
现在,结合UserDAO类,和三个xml文件,应该容易知道它的原理: 逻辑层调用userDAO,userDAO通过Spring的粘合会去调用 Ibatis,Ibatis读取sql-map-config.xml文件,然后顺藤摸瓜,再读取user.xml文件,从中找到id="userid"的map,传递userid 给#userid#,得到完整的sql,执行sql,返回结果。详细的Ibatis的使用请参考相关手册。
6 Spring DAO
Spring DAO是Spring对JDBC的封装。主要利用Template模式实现。先看看它的用法:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("Update user set name='Jeff' where id=10");
Template模式配合回调的使用是JdbcTemplate的主要实现方式,下面是它的源码片段:
public int update(final String sql) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL update [" + sql + "]");
}
class UpdateStatementCallback implements StatementCallback,
SqlProvider {
public Object doInStatement(Statement stmt) //<------- 这个函数将被下面的方法回调
throws SQLException {
int rows = stmt.executeUpdate(sql);
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows
+ " rows");
}
return new Integer(rows);
}
public String getSql() {
return sql;
}
}
return ((Integer) execute(new UpdateStatementCallback()))
.intValue();
}
public Object execute(StatementCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this .nativeJdbcExtractor != null
&& this .nativeJdbcExtractor
.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this .nativeJdbcExtractor
.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this .nativeJdbcExtractor != null) {
stmtToUse = this .nativeJdbcExtractor
.getNativeStatement(stmt);
}
Object result = action.doInStatement(stmtToUse); //<------- 这里是回调的地方
handleWarnings(stmt.getWarnings());
return result;
} catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate(
"StatementCallback", getSql(action), ex);
} finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
不用回调,如果用顺调,会是怎样的? 以下是模拟代码:
public int update(final String sql) throws DataAccessException {
return execute(sql);
}
public int execute(String sql) throws DataAccessException {
Connection con = DataSourceUtils.getConnection();
Statement stmt = null;
stmt = con.createStatement();
int rows = stmt.executeUpdate(sql);
return rows;
//数据库连接关闭等代码略
}
这样做简单,但两个函数耦合强,execute方法的返回类型与参数都规定的太死,限制了execute方法的使用范围。比如,如下的代码就不容易去使用execute方法:
public User getUser(int id){
//无法使用int execute(String sql) 函数,因为它只返回一个int,不是想要的。
}
但如果是接口回调方式,则灵活得多
public User getUser(int id) throws DataAccessException {
class UpdateStatementCallback implements StatementCallback {
public Object doInStatement(Statement stmt) //<------- 这个函数将被下面的方法回调
throws SQLException {
String sql = "select * from User where id ="+id;
ResultSet rs = stmt.executeUpdate(sql);
//下面略的部分是把rs取回的数据封装进User对象里。
//...略;
User user = getUser(rs);
return user;
}
}
return execute(new UpdateStatementCallback());
}
可以看出,下面的这个方法可满足不同的需要
public Object execute(StatementCallback action) throws DataAccessException
而下面这种方法相对死板(但也简单了不少)
public int execute(String sql) throws DataAccessException
模板模式请参考:http://hi.baidu.com/dapplehou/blog/item/0830c3ce9ccd4a0f93457e6d.html
相信很容易明白Spring是如何把那些关闭连接的琐事用模板模式封装起来的。
7 多库/多数据源
无论由于性能原因对数据库拆分,还是本身就是分布式数据库,都涉及到对多个数据库的操作。这要求程序能动态定位所需数据库,实际上就是在多个数据源中动态定位一个。
可采用把定位库的策略封装进一个中间层来屏蔽程序对于多库的敏感。一般采用Proxy模式封装这种DB路由(即:定位)逻辑。Spring又显身手,它对多库也提供了方案:
public class DynamicDataSource extends AbstractRoutingDataSource {//1. 继承AbstractRoutingDataSource
static Logger log = Logger.getLogger("DynamicDataSource");
protected Object determineCurrentLookupKey() {
...................; //2. 你的路由逻辑
return dataSourceId; //3. 返回你要使用的datasourceId
}
}
相应的配置文件如下:
<bean id="dataSource0" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource" class="xxx.xxx.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.Integer">
<entry key="0" value-ref="dataSource0"/>
<entry key="1" value-ref="dataSource1"/>
<entry key="2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource0"/>
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:com/bitfone/smartdm/dao/sqlmap/sql-map-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="UserInfoDAO" class="com.bitfone.smartdm.dao.impl.UserInfoDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
当然,也可以不集成 AbstractRoutingDataSource . 而是实现DataSource接口,代码片段如下:
public class DynamicDataSource implements DataSource{
private static String dsDefault;
private Map dataSources;
public void setDsDefault(String dsDefault)
{
this.dsDefault = dsDefault;
}
public Connection getConnection() throws SQLException
{
DataSource targetDS = getDataSource();
return targetDS.getConnection();
}
public DataSource getDataSource()
{
..........
..........//你的路由逻辑
String dbName = ?;
DataSource ds = (DataSource)dataSources.get(dbName);
return ds;
}
......
}
实际上,AbstractRoutingDataSource 就是DataSource接口的一个实现,继承 AbstractRoutingDataSource,比自己实现DataSource要少很多工作量,因为 AbstractRoutingDataSource都帮你做了。AbstractRoutingDataSource内部的数据库路由原理和上面的代码一样。
8 结束
时间有限,先写到这里吧。关于DAO还有太多的东西可以写,还有深的内容可以挖掘,这些内容完全可以写本大厚书。这里主要是从一个Framework的角度来分析了一下大致的用法和实现思路。
http://hi.baidu.com/dapplehou/blog/item/6ba8034f5114e038aec3abdb.html
Table of Contents
1 言前
2 项目1 (久远,尚无SSH流行)
2.1 使用方式
2.2 原理
2.3 UML
2.4 优点
2.5 缺点
3 项目2
3.1 使用方式(步骤)
3.2 原理
3.3 UML
3.4 优点:
3.5 缺点
4 项目3(大约04年,hibernate)
4.1 使用方式
4.2 原理( session 的获取方式 )
4.3 UML
4.4 优缺点
4.5 两个对hibernate使用的例子
5 项目4 (struts+spring+ibatis)
5.1 架构说明
5.2 使用方式及其原理
5.2.1 UserDAO的实现以及如何使用第三方SqlMapClient.
5.2.2 为什么SqlMapClientFactoryBean与SqlMapClient类型不同也可被注入?
5.2.3 如何使用IBatis
6 Spring DAO
7 多库、多数据源
8 结束
1 言前
两、三年前说过要坚持写文剖析各种算法、面向对象、框架、架构及各种应用技术,然进展缓慢,一者力有不逮,二者工作变动,无暇它顾。如今咬牙还债,算给自己个交代。这次就从最常用的DAO开始吧,
J2EE应用开发中,数据库的使用非常频繁,大多公司里所使用的Framework都有对数据库操作进行封装的被称为DAO层的部分。下面,我结合自己在不同时期所参与的项目,就各种DAO层的封装方式进行一下比照。
2 项目1 (久远,尚无SSH流行)
2.1 使用方式
用户DAO继承BaseDAO
在业务模块初始化用户DAO,并传递给如下3个参数
- String dataSourceName 数据源名称(JNDI).
- boolean autoReleaseConnection 是否自动释放链接.
- boolean useStoreProcedure 是否使用存储过程.
在业务模块根据需要调用用户DAO对象的相应方法.
例子: 业务逻辑中调用:
UserDAO userDAO = new UserDAO(null,false,false);
List allUser = userDAO.getAllUser();
2.2 原理
主要实现了对SQL执行过程的封装。以一个查询SQL为例,它的书写只需要这么一句:
DataSet ds = execQuery(querySQL);
在execQuery(String sql)中,做了3件事:
获取连接
使用获取的连接执行SQL
把从数据库中取得的数据封装进DateSet对象中,返回给用户。程序员在用户DAO对象里面进行组装SQL的工作,然后调用父类提供的执行 execQuery方法,返回封装的DataSet对象,该对象以List方式存放着所有查询结果。
2.3 UML
2.4 优点
省却了婆婆妈妈的RecordSet, Statement的声明和繁琐重复的异常捕获。把查询结果放进缓存,不用一直维持着数据库的连接,可以减少数据库的数据连接压力。
2.5 缺点
把DAO的模式控制放进业务模块里不合适。
只支持单一数据库。
数据量太大的查询,会导致内存不足。
3 项目2
3.1 使用方式(步骤)
业务逻辑中声明用户自定义的DAO,它必须继承自framework中的 BaseDAO 比如
IndexDAO dao = new IndexDAO();
在业务逻辑中把请求数据封装为DataSet 比如:
DataSet dataset=this.getDataSet(request);
业务逻辑中使用DAO处理DataSet 比如
dao.addData(dataset,user);
在用户自定义DAO的addData方法中继续对DataSet对象进行补充,比如:
ds.set("userstatus", "0");
ds.tablename = "T_Pop_User";
设置完DataSet后,最关键的一步来了,就是DBOperator直接对DataSet 进行操作,比如:
dboperator.insertData(ds);
3.2 原理
对客户端(浏览器)数据进行自动封装
在服务端对封装的数据进行自动解析,解析成SQL,并执行.
实际上,这相当于建立了从客户端(一般而言是Form表单)到数据库表间的映射.
3.3 UML
3.4 优点:
使用过程中不但不需要写繁复的连接释放等代码,而且连SQL语句都不用写了,这使得整个代码显得清晰简洁。根据用户的DataSet对象动态、智能的生成SQL的工作都被隐藏到了框架内部.
把用户请求被自动封装为DataSet,免去了用户使用request对象方法一个一个的去接收数据。DAO层自动根据DataSet生成SQL去执行。在这个过程中,程序员做的工作被极大的简化。
支持多库。
3.5 缺点
被封装的DataSet里需要包含数据库字段信息,才能被程序自动解析为 SQL,这要求前端Form中的Field名称必须和数据库字段名称一致(严格的说,要求传递给Server端的数据的Field字段必须与数据库字段名称一致)。这或多或少限制了前端开发的灵活性。但考虑到整个过程的便利性,这点限制并非不值。
前端Form直接暴露数据库字段信息不安全。对于信息安全要求高的系统这样的做法会直接Pass掉。
4 项目3(大约04年,hibernate)
4.1 使用方式
基本上式是直接使用hibernate进行DAO层操作,未对hibernate进行二次封装;
在Action中使用如下方式得到User对象,
User user = new User();
user.setID(23);
user.setName("Dapple");
UserManager.updateUser(user);
在UserManager中,通过SessionManager直接使用session进行数据对象存取;
Session s = SessionManager.getSession();
s.update(buyer);
4.2 原理( session 的获取方式 )
在SessionManager中,session的获取方式如下,
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session s = sessionFactory.openSession();
这段代码会读取hibernate配置文件(hibernate.cfg.xml ), hibernate配置文件中有数据库连接信息,对象与数据库之间的映射文件的地址;
Hibernate的原理请参照hibernate相关文档;
4.3 UML
4.4 优缺点
其实就是hibernate的优缺点。这个大家可以参考已经非常丰富的hibernate 资料;从审美角度上看,hibernate使数据存取代码简洁、清晰。而简洁清晰的代码总是更容易使用。
4.5 两个对hibernate使用的例子
在下一个紧接着的项目中,有的模块对hibernate又进行了一层简单的封装,这样做的目的是为了使之扩展性更强。但实际上这也同时使得代码复杂度增加。这中间的平衡取舍往往不单单是技术问题。
下面我们这里举两种封装例子。
先看UML
从这个UML中,我们可以看出它的思路,就是尘归尘、土归土,就是user的业务处理归UserManager,user的数据处理归UserManagerDAO.两者互不干扰。假如现在需求有变,数据被要求存在文件里而非数据库中,要做哪些工作呢?
新写一个实现了UserManagerDAO接口的UserManagerDAOFileImpl实体类。
DAOFactory中需要增加一个生成负责文件存储的实现类的方法,我们暂叫它为方法B;(原先负责生成数据库存储实现类的方法叫做方法A;)
UserManager中需要把原来DAOFactory中调用方法A的代码修改为调用方法B;
可以看出,已有代码中修改了2处。这正是简单工厂模式的特点。
先看UML
它减少了Manager这一层,而让用户直接使用DAO.它使用的模式是工厂模式。假如需求也有变,要求把数据存进文件里而非数据库中,要做哪些工作呢?
新写两个类,即继承了DAOFactory抽象类的DAOFileFactory和实现了 UserDAO接口的UserDAOFileImpl实体类。
在实例化DAOHBFactory的地方(这里实际上是DAOFactory抽象类中有一个实现具体的实现方法)做出修改。该为实例化DAOFileFactory。
我们可以看出,这里对已有代码修改了一处。大家都知道OCP原则,即对扩展开放,对修改关闭。其本质就是对于已经存在的代码尽量不去动就能实现新的功能(扩展),其完全实现必然伴随形成热插拔的能力。
这里,仍然对已有代码做了一处修改,必然还需要重新编译。如果把实例化的控制开关放进配置文件中又配置人员来设定,就可以不需要再修改代码再编译,则基本可以看做可热插拔了。
5 项目4 (struts+spring+ibatis)
补充一点,所有项目的业务含义就不说了。因为那样做不好。
5.1 架构说明
架构采用 struts+spring+ibatis 组合。
请求被Action接收后, Action–>Logic–>Dao–>Ibatis. 符号"–>"代表调用. 关键的地方在于,所有的调用都是通过Spring的依赖注入实现,而不用程序员去写被调用对象的创建代码(比如写一个工厂类用来创建对象)。省了好多事哦。
Struts是最著名的MVC Framework,它的实现实质就是用一个Servlet统一接收请求,然后根据配置文件分发给不同的action,程序员在action中再调用自己的逻辑模块,处理完后,struts又根据配置文件把结果转发到相应的页面。这就完成了MVC的一个全过程。它是这么著名,以至于到处都是它的资料。所以欲知其详,请google.
Ibatis是半自动化数据封装方案。它的实现实质就是通过读取写在配置文件中的SQL语句自动生成数据对象.
Spring则发展的最火,因为它的确最棒。但要说它的实现实质,则也是通过读取含有class信息的配置文件,动态的生成类对象。多个class的依赖关系也写在配置文件中,Spring读取依赖关系、进行对象的构造,这就是依赖注入。
捎带提一句,要感谢夏昕为Spring的普及所做的贡献。正是2004年夏昕的 Spring Guide加速了Spring在众多的程序员中的普及进程。
配置文件的例子如下:
<bean id="user" class="test.User">
<property name="name"><value>张三</value></property>
<property name="age"><value>20</value></property>
</bean>
<bean id="department" class="test.Department">
<property name="user"><ref bean="user"/></property>
</bean>
这个配置文件中,Department类引用了User类(Spring会自动根据引用构建依赖关系,也可以手工指定依赖关系)。Spring读这些文件,然后通过反射就可以构造出user对象与Department对象。然后再在此基础上发展演化,变成了今天的Spring. 它的基本原理简单,但为什么这样的framework总是被老外开发出来呢?不是中国程序员笨,可能要从历史和大环境找原因吧。
不说闲话,回归正题。这个项目采用了struts+spring+ibatis做架构,我们就来看一下它的使用方式。
5.2 使用方式及其原理
这里用一个简单过程来说明。比如用户输入用户名和密码进行Logon,系统校验用户名密码是不是正确。(这是个演示例子,所以后面的代码一切从简)。
用户点击登陆,请求被struts送到了Logonaction,如下:
protected ActionForward doExecute(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception{
String name = request.getParameter("name");
String password = request.getParameter("password");
ServletContext sc = request.getSession().getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);
UserHandle userHandle = (UserHandle)ctx.getBean("userhandle"); //获取userhanle实例
boolean isSuccess = userHandle.check(name,password);
if(isSuccess){
return mapping.findForward("success");
}else{
return mapping.findForward("failed");
}
}
userHandle实例是通过容器(ApplicationContext)获取的,不需要自己写创建对象的代码了。容器实际上是通过读取配置文件加载UserHandle类的,而这个过程对于程序员是透明的。
然后我们再分析下面这一句:
boolean isSuccess = userHandle.check(name,password);
userHanle类的通常实现方法可能是这样,
class UserHandle{
public boolean check(String name,String password){
UserDAO userDao = DBOperator.getUserDAO(); //你自己的DB操作的封装类
return userDao.check(name,password);
}
}
然后就可以想象UserDAO里就是数据库连接,发送SQL的那一套。这样做,程序员就不得不写一个 DBOperator类,用来创建DAO. 而Spring就是用来解放这样的创建工作的,这样 DBOperator这样的类就没必要程序员劳神去写了,只需要这样做:
class UserHandle{
private UserDAO userDao;
public boolean check(String name,String password){
return userDao.check(name,password);
}
public void setUserDao(UserDAO userDao){
this.userDao = userDao;
}
public UserDAO getUserDao(){
return this.userDao;
}
}
Spring 会根据配置文件中的类的依赖关系,自动的把UserDAO对象注入到 UserHandle对象里,然后程序员去使用userDao就可以了。
既然是根据配置文件来注入的,那么配置文件是什么样子呢?如下:
<beans>
<!--4-->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>efmweb</value></property>
</bean>
<!--3-->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean" >
<property name="configLocation"><value>WEB-INF/sql-map-config.xml</value></property>
<property name="dataSource"><ref local="dataSource"/></property>
</bean>
<!--2-->
<bean id="userdao" class="UserDAO">
<property name="sqlMapClient"><ref bean="sqlMapClient"/></property>
</bean>
<!--1-->
<bean id="userhandle" class="UserHandle">
<property name="userDao"><ref bean="userdao" /></property>
</bean>
</beans>
从这个配置文件里我们可以看到它们的引用关系是:1–>2–>3–>4,即 UserHandle引用UserDAO, UserDAO引用 SqlMapClientFactoryBean, SqlMapClientFactoryBean引用 JndiObjectFactoryBean,JndiObjectFactoryBean负责根据jndiName获得 datasource.
通过这样层层引用的配置,spring就能自动生成对象,不但免了程序员手动写对象生成的代码,还很容易的把ibatis串连起来。
前面我们已经根据代码和配置文件知道了UserHandle是怎么引用和注入 UserDAO的方式,现在我们再看UserDAO是怎么引用 SqlMapClientFactoryBean 这个第三方类的.
5.2.1 UserDAO的实现以及如何使用第三方SqlMapClient.
根据UserHandle引用UserDAO的方式,我们也应该猜到UserDAO引用 SqlMapClientFactoryBean时应该大致是这样:
class UserDAO{
private SqlMapClientFactoryBean sqlMapClient;
public void setSqlMapClient(SqlMapClientFactoryBean sqlMapClient){
this.sqlMapClient = sqlMapClient;
}
public SqlMapClientFactoryBean getSqlMapClient(){
return this.sqlMapClient;
}
...;//你自己定义的其它业务相关userDao逻辑;
}
但实际上,第三方类已经把set get这样的方法封装好了,你只需要继承第三方类就可以了,如这样:
class UserDAO extends SqlMapClientDaoSupport{
...;//你自己定义的其它业务相关userDao逻辑;
}
这样就不用手工写那些set get方法了。原理也不难理解,那些set 和 get方法无非被挪进了SqlMapClientDaoSupport这个Spring提供的第三方类里面而已,我们可以看它的源码片段:
public abstract class SqlMapClientDaoSupport extends DaoSupport {
private SqlMapClientTemplate sqlMapClientTemplate = new SqlMapClientTemplate();
private boolean externalTemplate = false;
/**
* Set the JDBC DataSource to be used by this DAO.
* Not required: The SqlMapClient might carry a shared DataSource.
* @see #setSqlMapClient
*/
public final void setDataSource(DataSource dataSource) {
if (!this.externalTemplate) {
this.sqlMapClientTemplate.setDataSource(dataSource);
}
}
/**
* Return the JDBC DataSource used by this DAO.
*/
public final DataSource getDataSource() {
return this.sqlMapClientTemplate.getDataSource();
}
/**
* Set the iBATIS Database Layer SqlMapClient to work with.
* Either this or a "sqlMapClientTemplate" is required.
* @see #setSqlMapClientTemplate
*/
public final void setSqlMapClient(SqlMapClient sqlMapClient) {
if (!this.externalTemplate) {
this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient);
}
}
/**
* Return the iBATIS Database Layer SqlMapClient that this template works with.
*/
public final SqlMapClient getSqlMapClient() {
return this.sqlMapClientTemplate.getSqlMapClient();
}
/**
* Return the SqlMapClientTemplate for this DAO,
* pre-initialized with the SqlMapClient or set explicitly.
*/
public final SqlMapClientTemplate getSqlMapClientTemplate() {
return this.sqlMapClientTemplate;
}
}
5.2.2 为什么SqlMapClientFactoryBean与SqlMapClient类型不同也可被注入?
果然看到了getSqlMapClient()和setSqlMapClient()方法。我们仍然根据 UserHandle和UserDAO的注入方式,可以知道UserHandle的属性userDao其实就是UserDAO类的实例注入。同样,配置文件中UserDAO的属性sqlMapClient 也应该是SqlMapClientFactoryBean类的实例注入才对。但 SqlMapClientFactoryBean并不是SqlMapClient类型,这怎么能注入呢?
这是因为Spring的机制的缘故。简单的说,如果一个bean实现了 FactoryBean接口,那么Spring就不会把该bean本身实例化并返回,而是返回该bean的getObject()返回的对象。这是Sprign的游戏规则。我们来看一眼 SqlMapClientFactoryBean的源码片段:
public class SqlMapClientFactoryBean implements FactoryBean,InitializingBean {
private SqlMapClient sqlMapClient;
protected SqlMapClient buildSqlMapClient(Resource configLocation,Properties properties) throws IOException {
InputStream is = configLocation.getInputStream();
if (properties != null) {
if (buildSqlMapClientWithInputStreamAndPropertiesMethodAvailable) {
return SqlMapClientBuilder.buildSqlMapClient(is,properties);
} else {
return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader(is), properties);
}
} else {
if (buildSqlMapClientWithInputStreamMethodAvailable) {
return SqlMapClientBuilder.buildSqlMapClient(is);
} else {
return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader(is));
}
}
}
//这里就是返回的、并会被注入到其它类里的对象
public Object getObject() {
return this.sqlMapClient;
}
}
Spring这种机制的官方说明:
public interface FactoryBean
Interface to be implemented by objects used within a BeanFactory which are themselves factories. If a bean implements
this interface, it is used as a factory for an object to expose, not directly as a bean instance that will be exposed
itself.
NB: A bean that implements this interface cannot be used as a normal bean. A FactoryBean is defined in a bean style, but
the object exposed for bean references (getObject() is always the object that it creates.
FactoryBeans can support singletons and prototypes, and can either create objects lazily on demand or eagerly on
startup. The SmartFactoryBean interface allows for exposing more fine-grained behavioral metadata.
This interface is heavily used within the framework itself, for example for the AOP ProxyFactoryBean or the
JndiObjectFactoryBean. It can be used for application components as well; however, this is not common outside of
infrastructure code.
NOTE: FactoryBean objects participate in the containing BeanFactory's synchronization of bean creation. There is usually
no need for internal synchronization other than for purposes of lazy initialization within the FactoryBean itself (or
the like).
Since:
08.03.2003
Author:
Rod Johnson, Juergen Hoeller
See Also:
BeanFactory, ProxyFactoryBean, JndiObjectFactoryBean
到此为止,我们已经知道UserDAO与SqlMapClient的关系,接下来看如何使用IBatis
5.2.3 如何使用IBatis
Ibatis是半自动化数据对象封装方案。我们看userDao怎么使用Ibatis
class UserDAO{
private SqlMapClientFactoryBean sqlMapClient;
public void setSqlMapClient(SqlMapClientFactoryBean sqlMapClient){
this.sqlMapClient = sqlMapClient;
}
public SqlMapClientFactoryBean getSqlMapClient(){
return this.sqlMapClient;
}
//这里是使用Ibatis的地方
public User getUser(String userid) {
return super.getSqlMapClientTemplate().queryForList("userid",
userid);
}
}
结合者段代码以及前面的配置文件的第三段,相信你已经大体明白了Ibatis的使用。这里就做一简单描述。先重新看一下配置文件的第三段:
<!--3-->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean" >
<property name="configLocation"><value>WEB-INF/sql-map-config.xml</value></property>
<property name="dataSource"><ref local="dataSource"/></property>
</bean>
注意这里有一个sql-map-config.xml文件。这个文件的内容如下:
<sqlMapConfig>
<sqlMap resource="user.xml"/>
</sqlMapConfig>
再看user.xml文件里是什么?
<sqlMap>
<select id="userid" resultclass="UserDAO" parameterClass="java.lang.String">
select * from user where userid=#userid#
</select>
</sqlMap>
现在,结合UserDAO类,和三个xml文件,应该容易知道它的原理: 逻辑层调用userDAO,userDAO通过Spring的粘合会去调用 Ibatis,Ibatis读取sql-map-config.xml文件,然后顺藤摸瓜,再读取user.xml文件,从中找到id="userid"的map,传递userid 给#userid#,得到完整的sql,执行sql,返回结果。详细的Ibatis的使用请参考相关手册。
6 Spring DAO
Spring DAO是Spring对JDBC的封装。主要利用Template模式实现。先看看它的用法:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("Update user set name='Jeff' where id=10");
Template模式配合回调的使用是JdbcTemplate的主要实现方式,下面是它的源码片段:
public int update(final String sql) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL update [" + sql + "]");
}
class UpdateStatementCallback implements StatementCallback,
SqlProvider {
public Object doInStatement(Statement stmt) //<------- 这个函数将被下面的方法回调
throws SQLException {
int rows = stmt.executeUpdate(sql);
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows
+ " rows");
}
return new Integer(rows);
}
public String getSql() {
return sql;
}
}
return ((Integer) execute(new UpdateStatementCallback()))
.intValue();
}
public Object execute(StatementCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this .nativeJdbcExtractor != null
&& this .nativeJdbcExtractor
.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this .nativeJdbcExtractor
.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this .nativeJdbcExtractor != null) {
stmtToUse = this .nativeJdbcExtractor
.getNativeStatement(stmt);
}
Object result = action.doInStatement(stmtToUse); //<------- 这里是回调的地方
handleWarnings(stmt.getWarnings());
return result;
} catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate(
"StatementCallback", getSql(action), ex);
} finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
不用回调,如果用顺调,会是怎样的? 以下是模拟代码:
public int update(final String sql) throws DataAccessException {
return execute(sql);
}
public int execute(String sql) throws DataAccessException {
Connection con = DataSourceUtils.getConnection();
Statement stmt = null;
stmt = con.createStatement();
int rows = stmt.executeUpdate(sql);
return rows;
//数据库连接关闭等代码略
}
这样做简单,但两个函数耦合强,execute方法的返回类型与参数都规定的太死,限制了execute方法的使用范围。比如,如下的代码就不容易去使用execute方法:
public User getUser(int id){
//无法使用int execute(String sql) 函数,因为它只返回一个int,不是想要的。
}
但如果是接口回调方式,则灵活得多
public User getUser(int id) throws DataAccessException {
class UpdateStatementCallback implements StatementCallback {
public Object doInStatement(Statement stmt) //<------- 这个函数将被下面的方法回调
throws SQLException {
String sql = "select * from User where id ="+id;
ResultSet rs = stmt.executeUpdate(sql);
//下面略的部分是把rs取回的数据封装进User对象里。
//...略;
User user = getUser(rs);
return user;
}
}
return execute(new UpdateStatementCallback());
}
可以看出,下面的这个方法可满足不同的需要
public Object execute(StatementCallback action) throws DataAccessException
而下面这种方法相对死板(但也简单了不少)
public int execute(String sql) throws DataAccessException
模板模式请参考:http://hi.baidu.com/dapplehou/blog/item/0830c3ce9ccd4a0f93457e6d.html
相信很容易明白Spring是如何把那些关闭连接的琐事用模板模式封装起来的。
7 多库/多数据源
无论由于性能原因对数据库拆分,还是本身就是分布式数据库,都涉及到对多个数据库的操作。这要求程序能动态定位所需数据库,实际上就是在多个数据源中动态定位一个。
可采用把定位库的策略封装进一个中间层来屏蔽程序对于多库的敏感。一般采用Proxy模式封装这种DB路由(即:定位)逻辑。Spring又显身手,它对多库也提供了方案:
public class DynamicDataSource extends AbstractRoutingDataSource {//1. 继承AbstractRoutingDataSource
static Logger log = Logger.getLogger("DynamicDataSource");
protected Object determineCurrentLookupKey() {
...................; //2. 你的路由逻辑
return dataSourceId; //3. 返回你要使用的datasourceId
}
}
相应的配置文件如下:
<bean id="dataSource0" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource" class="xxx.xxx.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.Integer">
<entry key="0" value-ref="dataSource0"/>
<entry key="1" value-ref="dataSource1"/>
<entry key="2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource0"/>
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:com/bitfone/smartdm/dao/sqlmap/sql-map-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="UserInfoDAO" class="com.bitfone.smartdm.dao.impl.UserInfoDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
当然,也可以不集成 AbstractRoutingDataSource . 而是实现DataSource接口,代码片段如下:
public class DynamicDataSource implements DataSource{
private static String dsDefault;
private Map dataSources;
public void setDsDefault(String dsDefault)
{
this.dsDefault = dsDefault;
}
public Connection getConnection() throws SQLException
{
DataSource targetDS = getDataSource();
return targetDS.getConnection();
}
public DataSource getDataSource()
{
..........
..........//你的路由逻辑
String dbName = ?;
DataSource ds = (DataSource)dataSources.get(dbName);
return ds;
}
......
}
实际上,AbstractRoutingDataSource 就是DataSource接口的一个实现,继承 AbstractRoutingDataSource,比自己实现DataSource要少很多工作量,因为 AbstractRoutingDataSource都帮你做了。AbstractRoutingDataSource内部的数据库路由原理和上面的代码一样。
8 结束
时间有限,先写到这里吧。关于DAO还有太多的东西可以写,还有深的内容可以挖掘,这些内容完全可以写本大厚书。这里主要是从一个Framework的角度来分析了一下大致的用法和实现思路。
发表评论
-
spring 自定义注解
2015-06-01 11:53 654http://zhangwei-david.iteye.com ... -
SpringMVC中servletFileUpload.parseRequest(request)解析为空获取不到数据问题,upload.parsereques
2015-01-28 16:57 913SpringMVC中servletFileUpload.p ... -
spring学习
2014-11-05 17:29 0http://jinnianshilongnian.iteye ... -
spring mvc初学者
2014-11-04 16:58 0http://elf8848.iteye.com/blog/8 ... -
spring学习
2014-10-10 21:47 650http://www.iteye.com/topic/1124 ... -
使用 Spring Boot 快速构建 Spring 框架应用
2014-09-18 09:33 0http://www.ibm.com/developerwor ... -
spring容器初始化过程
2014-09-17 13:55 0http://blog.csdn.net/crave_shy/ ...
相关推荐
它通常发生在数据访问层代码中,如DAO层或Repository层。 #### 原因分析 导致 `org.springframework.dao.InvalidDataAccessApiUsageException` 的主要原因是Spring事务管理器默认将事务设置为只读模式。这意味着,...
在Struts2 + Spring + Hibernate(简称S2SH)框架中,数据访问层(DAO层)通常与视图层(如JSP页面)进行解耦设计。这种设计模式有助于提高系统的可维护性和扩展性。但在某些情况下,开发者可能需要在JSP页面直接...
在Spring框架中,数据服务层(通常指DAO层)主要用于存放数据,即与数据库的交互。Spring提供了多种方式来实现这一功能,例如使用JDBC模板、JPA、Hibernate等技术。 #### 四、Spring与其他框架的集成 1. **Struts...
- **拓展生态体系**:为了适应不同的应用场景,Play Framework 将逐步丰富其插件生态系统,提供更多实用工具和服务。 - **社区支持与贡献**:通过积极维护开源社区,吸引更多开发者参与贡献,共同推动框架的进步与...
通过这样的实例,你可以了解到在C#中如何将一个具体的业务功能分解到三层架构的不同部分。这种结构化的设计方式使得代码更易于管理和维护,同时也提高了代码的复用性。在实际开发中,你可以根据项目规模和复杂度调整...
BLL调用DAO层来执行实际的数据操作,并且对UI层提供服务,起到业务流程控制的作用。 3. 数据访问层(DAL):DAL是与数据库交互的接口,处理SQL语句的执行,如查询、插入、更新和删除数据。在C#中,可能使用ADO.NET...
2. **Data Access/Integration**:这部分涉及数据访问和集成,如JDBC抽象层、ORM支持(Hibernate、JPA等)、O/R Mapping和DAO支持,以及对NoSQL数据库的支持。 3. **Web**:Spring的Web模块包括了MVC框架,它提供了...
这一层通常使用ADO.NET或Entity Framework等技术实现,将业务逻辑与数据库操作分离,降低了数据库变更对上层的影响。DAL通过接口或数据访问对象(DAO)暴露其服务,使得业务逻辑层(BLL)可以无需关心具体的数据库...
DAO(Data Access ...通过分析和实践这些示例,开发者不仅能掌握DAO模式在ASP.NET中的应用,还能提高在实际项目中处理数据库的能力。同时,学习注释良好的代码也是一种良好的编程习惯,有助于团队协作和代码维护。
Spring 框架是 Java 开发中的一个核心组件,它为构建可维护、模块化且松耦合的应用程序提供了一种强大的...通过分析和运行这些示例,你可以深入理解 Spring 如何在实际项目中发挥作用,从而提升你的 Java 开发技能。
Spring Framework 2是Java开发中的一个关键框架,它极大地简化了企业级应用的构建和管理。这个框架的核心特性包括依赖注入(Dependency Injection, DI)、面向切面编程(Aspect-Oriented Programming, AOP)以及丰富...
《Spring Framework 4.1.0.RELEASE:深入解析与源码分析》 Spring Framework作为Java开发中的核心框架,以其强大的功能和灵活的设计理念,深受广大开发者喜爱。本篇文章将聚焦于Spring Framework 4.1.0.RELEASE版本...
在ASP.NET中,数据访问通常使用ADO.NET或者Entity Framework,它们提供了与SQL Server等数据库的接口。在这个实例中,可能有一个或多个数据访问对象(DAO)或数据访问实体(DTO),用于封装数据库操作。 三层架构的...
本文将深入探讨Spring Framework 2.0.2的主要功能,并分析其依赖关系。 首先,让我们了解Spring的核心理念:依赖注入(Dependency Injection,简称DI)。在2.0.2版本中,DI机制得到了进一步强化,使得对象之间的...
通常,这样的工具会根据数据库模式生成对应的实体类、DAO层、Service层以及Controller层代码,使得开发者可以快速构建出完整的CRUD操作。 在压缩包文件名称列表中,我们看到"zj-smart-framework-master",这可能是...
3. 数据访问层:使用ADO.NET、Entity Framework或其他ORM(对象关系映射)框架生成数据库操作的类,如DbContext、Repository或DAO(数据访问对象)。 使用C#三层代码生成器的优势在于: - 提高开发效率:开发者...
这一层通常包含了数据访问对象(DAO)或实体框架(Entity Framework)等技术,用于封装对数据库的操作,提供数据的CRUD(创建、读取、更新、删除)功能。通过将数据访问逻辑集中管理,数据访问层能够简化数据库操作...
提供的MySchool项目源码是学习三层开发的良好实例,初学者可以通过阅读和分析代码了解如何在实际项目中划分和组织这些层。源码中的每个类和方法都可能对应着特定的职责,这有助于理解各层之间的协作机制。 6. 实践...
1. **DAO操作**:使用Spring的JdbcTemplate或HibernateTemplate进行数据库操作,理解Spring如何简化数据访问层的编写。 2. **事务管理**:通过实例了解如何利用@Transactional注解实现声明式事务。 3. **AOP应用**:...