`
Dapple
  • 浏览: 101042 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

不同Framework中DAO层的分析

阅读更多

http://hi.baidu.com/dapplehou/blog/item/6ba8034f5114e038aec3abdb.html

1 言前

两、三年前说过要坚持写文剖析各种算法、面向对象、框架、架构及各种应用技术,然进展缓慢,一者力有不逮,二者工作变动,无暇它顾。如今咬牙还债,算给自己个交代。这次就从最常用的DAO开始吧,

J2EE应用开发中,数据库的使用非常频繁,大多公司里所使用的Framework都有对数据库操作进行封装的被称为DAO层的部分。下面,我结合自己在不同时期所参与的项目,就各种DAO层的封装方式进行一下比照。

2 项目1 (久远,尚无SSH流行)

2.1 使用方式

  1. 用户DAO继承BaseDAO
  2. 在业务模块初始化用户DAO,并传递给如下3个参数
    - String dataSourceName 数据源名称(JNDI).
    - boolean autoReleaseConnection 是否自动释放链接.
    - boolean useStoreProcedure 是否使用存储过程.
  3. 在业务模块根据需要调用用户DAO对象的相应方法.
  4. 例子: 业务逻辑中调用:
    UserDAO userDAO = new UserDAO(null,false,false);
    List allUser = userDAO.getAllUser();

2.2 原理

  • 主要实现了对SQL执行过程的封装。以一个查询SQL为例,它的书写只需要这么一句:
    DataSet ds = execQuery(querySQL);

    在execQuery(String sql)中,做了3件事:

    1. 获取连接
    2. 使用获取的连接执行SQL
    3. 把从数据库中取得的数据封装进DateSet对象中,返回给用户。程序员在用户DAO对象里面进行组装SQL的工作,然后调用父类提供的执行 execQuery方法,返回封装的DataSet对象,该对象以List方式存放着所有查询结果。

2.3 UML


2.4 优点

省却了婆婆妈妈的RecordSet, Statement的声明和繁琐重复的异常捕获。把查询结果放进缓存,不用一直维持着数据库的连接,可以减少数据库的数据连接压力。

2.5 缺点

  1. 把DAO的模式控制放进业务模块里不合适。
  2. 只支持单一数据库。
  3. 数据量太大的查询,会导致内存不足。

3 项目2

3.1 使用方式(步骤)

  1. 业务逻辑中声明用户自定义的DAO,它必须继承自framework中的 BaseDAO 比如
    IndexDAO dao = new IndexDAO();
  2. 在业务逻辑中把请求数据封装为DataSet 比如:
    DataSet dataset=this.getDataSet(request);
  3. 业务逻辑中使用DAO处理DataSet 比如
    dao.addData(dataset,user);
  4. 在用户自定义DAO的addData方法中继续对DataSet对象进行补充,比如:
    ds.set("userstatus", "0");
    ds.tablename = "T_Pop_User";
  5. 设置完DataSet后,最关键的一步来了,就是DBOperator直接对DataSet 进行操作,比如:
    dboperator.insertData(ds);

3.2 原理

  1. 对客户端(浏览器)数据进行自动封装
  2. 在服务端对封装的数据进行自动解析,解析成SQL,并执行.
  3. 实际上,这相当于建立了从客户端(一般而言是Form表单)到数据库表间的映射.

3.3 UML

3.4 优点:

  1. 使用过程中不但不需要写繁复的连接释放等代码,而且连SQL语句都不用写了,这使得整个代码显得清晰简洁。根据用户的DataSet对象动态、智能的生成SQL的工作都被隐藏到了框架内部.
  2. 把用户请求被自动封装为DataSet,免去了用户使用request对象方法一个一个的去接收数据。DAO层自动根据DataSet生成SQL去执行。在这个过程中,程序员做的工作被极大的简化。
  3. 支持多库。

3.5 缺点

  1. 被封装的DataSet里需要包含数据库字段信息,才能被程序自动解析为 SQL,这要求前端Form中的Field名称必须和数据库字段名称一致(严格的说,要求传递给Server端的数据的Field字段必须与数据库字段名称一致)。这或多或少限制了前端开发的灵活性。但考虑到整个过程的便利性,这点限制并非不值。
  2. 前端Form直接暴露数据库字段信息不安全。对于信息安全要求高的系统这样的做法会直接Pass掉。

4 项目3(大约04年,hibernate)

4.1 使用方式

基本上式是直接使用hibernate进行DAO层操作,未对hibernate进行二次封装;

  1. 在Action中使用如下方式得到User对象,
    User user = new User();
    user.setID(23);
    user.setName("Dapple");
    UserManager.updateUser(user);
  2. 在UserManager中,通过SessionManager直接使用session进行数据对象存取;
    Session s = SessionManager.getSession();
    s.update(buyer);

4.2 原理( session 的获取方式 )

  1. 在SessionManager中,session的获取方式如下,
    SessionFactory  sessionFactory = new Configuration().configure().buildSessionFactory();
    Session s = sessionFactory.openSession();

这段代码会读取hibernate配置文件(hibernate.cfg.xml ), hibernate配置文件中有数据库连接信息,对象与数据库之间的映射文件的地址;

  1. Hibernate的原理请参照hibernate相关文档;

4.3 UML

4.4 优缺点

其实就是hibernate的优缺点。这个大家可以参考已经非常丰富的hibernate 资料;从审美角度上看,hibernate使数据存取代码简洁、清晰。而简洁清晰的代码总是更容易使用。

4.5 两个对hibernate使用的例子

在下一个紧接着的项目中,有的模块对hibernate又进行了一层简单的封装,这样做的目的是为了使之扩展性更强。但实际上这也同时使得代码复杂度增加。这中间的平衡取舍往往不单单是技术问题。

下面我们这里举两种封装例子。

  1. 先看UML

    从这个UML中,我们可以看出它的思路,就是尘归尘、土归土,就是user的业务处理归UserManager,user的数据处理归UserManagerDAO.两者互不干扰。假如现在需求有变,数据被要求存在文件里而非数据库中,要做哪些工作呢?

    1. 新写一个实现了UserManagerDAO接口的UserManagerDAOFileImpl实体类。
    2. DAOFactory中需要增加一个生成负责文件存储的实现类的方法,我们暂叫它为方法B;(原先负责生成数据库存储实现类的方法叫做方法A;)
    3. UserManager中需要把原来DAOFactory中调用方法A的代码修改为调用方法B;

      可以看出,已有代码中修改了2处。这正是简单工厂模式的特点。

  2. 先看UML

    它减少了Manager这一层,而让用户直接使用DAO.它使用的模式是工厂模式。假如需求也有变,要求把数据存进文件里而非数据库中,要做哪些工作呢?

    1. 新写两个类,即继承了DAOFactory抽象类的DAOFileFactory和实现了 UserDAO接口的UserDAOFileImpl实体类。
    2. 在实例化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的角度来分析了一下大致的用法和实现思路。

Author: Dapple Hou

mmonkeyer@163.com

0
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics