颠覆软件

关注 : 架构与设计,敏捷,快速开发,项目管理,执行力,SSH,RoR

[zt]SPRING数据访问对象(DAO)框架入门

Filed under: Spring — Alex at 9:52 pm on Friday, March 30, 2007

key words: spring,dao

转一篇写得很不错的文章,特别简单易懂,赞一个

come from here

摘要

J2EE应用程序中的业务组件通常使用JDBC API访问和更改关系数据库中的持久数据。这经常导致持久性代码与业务逻辑发生混合,这是一种不好的习惯。数据访问对象(DAO)设计模式通过把持久性逻辑分成若干数据访问类来解决这一问题。

本文是一篇关于DAO设计模式的入门文章,突出讲述了它的优点和不足之处。另外,本文还介绍了Spring 2.0 JDBC/DAO框架并示范了它如何妥善地解决传统DAO设计中的缺陷。

传统的DAO设计

数据访问对象(DAO)是一个集成层设计模式,如Core J2EE Design Pattern 图书所归纳。它将持久性存储访问和操作代码封装到一个单独的层中。本文的上下文中所提到的持久存储器是一个RDBMS。

这一模式在业务逻辑层和持久存储层之间引入了一个抽象层,如图1所示。业务对象通过数据访问对象来访问RDBMS(数据源)。抽象层改善了应用程序代码并引入了灵活性。理论上,当数据源改变时,比如更换数据库供应商或是数据库的类型时,仅需改变数据访问对象,从而把对业务对象的影响降到最低。

SPRING数据访问对象(DAO)框架入门图-1

  图1. 应用程序结构,包括DAO之前和之后的部分

讲解了DAO设计模式的基础知识,下面将编写一些代码。下面的例子来自于一个公司域模型。简而言之,这家公司有几位员工工作在不同的部门,如销售部、市场部以及人力资源部。为了简单起见,我们将集中讨论一个称作“雇员”的实体。

针对接口编程

DAO设计模式带来的灵活性首先要归功于一个对象设计的最佳实践:针对接口编程(P2I)。这一原则规定实体必须实现一个供调用程序而不是实体自身使用的接口。因此,可以轻松替换成不同的实现而对客户端代码只产生很小的影响。

我们将据此使用findBySalaryRange()行为定义Employee DAO接口,IEmployeeDAO。业务组件将通过这个接口与DAO交互:

import java.util.Map;
public interface IEmployeeDAO {
  //SQL String that will be executed
  public String FIND_BY_SAL_RNG = "SELECT EMP_NO, EMP_NAME, "
  + "SALARY FROM EMP WHERE SALARY >= ? AND SALARY <= ?";

  //Returns the list of employees who fall into the given salary
  //range. The input parameter is the immutable map object
  //obtained from the HttpServletRequest. This is an early
  //refactoring based on "Introduce Parameter Object"

  public List findBySalaryRange(Map salaryMap);
}

提供DAO实现类

接口已经定义,现在必须提供Employee DAO的具体实现,EmployeeDAOImpl:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import com.bea.dev2dev.to.EmployeeTO;

public class EmployeeDAOImpl implements IEmployeeDAO{

  public List findBySalaryRange(Map salaryMap)
  {
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    List empList = new ArrayList();
    //Transfer Object for inter-tier data transfer
    EmployeeTO tempEmpTO = null;
    try{
    //DBUtil - helper classes that retrieve connection from pool
      conn = DBUtil.getConnection();
      pstmt = conn.prepareStatement(FIND_BY_SAL_RNG);
      pstmt.setDouble(1, Double.valueOf( (String)
          salaryMap.get("MIN_SALARY") );
      pstmt.setDouble(2, Double.valueOf( (String)
          salaryMap.get("MIN_SALARY") );
      rs = pstmt.executeQuery();
      int tmpEmpNo = 0;
      String tmpEmpName = "";
      double tmpSalary = 0.0D;
      while (rs.next()){
        tmpEmpNo = rs.getInt("EMP_NO");
        tmpEmpName = rs.getString("EMP_NAME");
        tmpSalary = rs.getDouble("SALARY");
        tempEmpTO = new EmployeeTO(tmpEmpNo,
              tmpEmpName,
              tmpSalary);
        empList.add(tempEmpTO);
      }//end while
    }//end try
    catch (SQLException sqle){
      throw new DBException(sqle);
    }//end catch
    finally{
      try{
        if (rs != null){
          rs.close();
        }
      }
      catch (SQLException sqle){
        throw new DBException(sqle);
      }
      try{
        if (pstmt != null){
          pstmt.close();
        }
      }
      catch (SQLException sqle){
        throw new DBException(sqle);
      }
      try{
        if (conn != null){
          conn.close();
        }
      }
      catch (SQLException sqle){
        throw new DBException(sqle);
      }
    }//end of finally block
    return empList;
  }//end method findBySalaryRange
}

上面的清单说明了DAO方法的一些要点:

  • 它们封装了所有与JDBC API的交互。如果使用像Kodo或者Hibernate的O/R映射方案,则DAO类可以将这些产品的私有API打包。
  • 它们将检索到的数据打包到一个与JDBC API无关的传输对象中,然后将其返回给业务层作进一步处理。
  • 它们实质上是无状态的。唯一的目的是访问并更改业务对象的持久数据。
  • 在这个过程中,它们像SQLException一样捕获任何底层JDBC API或数据库报告的错误(例如,数据库不可用、错误的SQL句法)。DAO对象再次使用一个与JDBC无关的自定义运行时异常类DBException,通知业务对象这些错误。
  • 它们像Connection和PreparedStatement对象那样,将数据库资源释放回池中,并在使用完ResultSet游标之后,将其所占用的内存释放。

因此,DAO层将底层的数据访问API抽象化,为业务层提供了一致的数据访问API。

构建DAO工厂

DAO工厂是典型的工厂设计模式实现,用于为业务对象创建和提供具体的DAO实现。业务对象使用DAO接口,而不用了解实现类的具体情况。DAO工厂带来的依赖反转(dependency inversion)提供了极大的灵活性。只要DAO接口建立的约定未改变,那么很容易改变DAO实现(例如,从straight JDBC实现到基于Kodo的O/R映射),同时又不影响客户的业务对象:

public class DAOFactory {
  private static DAOFactory daoFac;

  static{
    daoFac = new DAOFactory();
  }

  private DAOFactory(){}

  public DAOFactory getInstance(){
    return daoFac;
  }

  public IEmployeeDAO getEmployeeDAO(){
    return new EmployeeDAOImpl();
  }
}

与业务组件的协作

现在该了解DAO怎样适应更复杂的情形。如前几节所述,DAO与业务层组件协作获取和更改持久业务数据。下面的清单展示了业务服务组件及其与DAO层的交互:

public class EmployeeBusinessServiceImpl implements
                                       IEmployeeBusinessService {

  public List getEmployeesWithinSalaryRange(Map salaryMap){

    IEmployeeDAO empDAO = DAOFactory.getInstance()
                                    .getEmployeeDAO();
    List empList = empDAO.findBySalaryRange(salaryMap);
    return empList;
  }
}

交互过程十分简洁,完全不依赖于任何持久性接口(包括JDBC)。

问题

DAO设计模式也有缺点:

  • 代码重复:从EmployeeDAOImpl清单可以清楚地看到,对于基于JDBC的传统数据库访问,代码重复(如上面的粗体字所示)是一个主要的问题。一遍又一遍地写着同样的代码,明显违背了基本的面向对象设计的代码重用原则。它将对项目成本、时间安排和工作产生明显的副面影响。
  • 耦合:DAO代码与JDBC接口和核心collection耦合得非常紧密。从每个DAO类的导入声明的数量可以明显地看出这种耦合。
  • 资源耗损:依据EmployeeDAOImpl类的设计,所有DAO方法必须释放对所获得的连接、声明、结果集等数据库资源的控制。这是危险的主张,因为一个编程新手可能很容易漏掉那些约束。结果造成资源耗尽,导致系统停机。
  • 错误处理:JDBC 驱动程序通过抛出SQLException来报告所有的错误情况。SQLException是检查到的异常,所以开发人员被迫去处理它,即使不可能从这类导致代码混乱的大多数异常中恢复过来。而且,从SQLException对象获得的错误代码和消息特定于数据库厂商,所以不可能写出可移植的DAO错误发送代码。
  • 脆弱的代码:在基于JDBC的DAO中,两个常用的任务是设置声明对象的绑定变量和使用结果集检索数据。如果SQL where子句中的列数目或者位置更改了,就不得不对代码执行更改、测试、重新部署这个严格的循环过程。

让我们看看如何能够减少这些问题并保留DAO的大多数优点。

进入Spring DAO

先识别代码中发生变化的部分,然后将这一部分代码分离出来或者封装起来,就能解决以上所列出的问题。Spring的设计者们已经完全做到了这一点,他们发布了一个超级简洁、健壮的、高度可伸缩的JDBC框架。固定部分(像检索连接、准备声明对象、执行查询和释放数据库资源)已经被一次性地写好,所以该框架的一部分内容有助于消除在传统的基于JDBC的DAO中出现的缺点。

图2显示的是Spring JDBC框架的主要组成部分。业务服务对象通过适当的接口继续使用DAO实现类。JdbcDaoSupport是JDBC数据访问对象的超类。它与特定的数据源相关联。Spring Inversion of Control (IOC)容器或BeanFactory负责获得相应数据源的配置详细信息,并将其与JdbcDaoSupport相关联。这个类最重要的功能就是使子类可以使用JdbcTemplate对象。

SPRING数据访问对象(DAO)框架入门图-2

  图2. Spring JDBC框架的主要组件

JdbcTemplate是Spring JDBC框架中最重要的类。引用文献中的话:“它简化了JDBC的使用,有助于避免常见的错误。它执行核心JDBC工作流,保留应用代码以提供SQL和提取结果。”这个类通过执行下面的样板任务来帮助分离JDBC DAO代码的静态部分:

  • 从数据源检索连接。
  • 准备合适的声明对象。
  • 执行SQL CRUD操作。
  • 遍历结果集,然后将结果填入标准的collection对象。
  • 处理SQLException异常并将其转换成更加特定于错误的异常层次结构。

利用Spring DAO重新编写

既然已基本理解了Spring JDBC框架,现在要重新编写已有的代码。下面将逐步讲述如何解决前几节中提到的问题。

第一步:修改DAO实现类- 现在从JdbcDaoSupport扩展出EmployeeDAOImpl以获得JdbcTemplate。

import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.core.JdbcTemplate;

public class EmployeeDAOImpl extends JdbcDaoSupport
                                     implements IEmployeeDAO{

  public List findBySalaryRange(Map salaryMap){

    Double dblParams [] = {Double.valueOf((String)
            salaryMap.get(”MIN_SALARY”))
              ,Double.valueOf((String)
            salaryMap.get(”MAX_SALARY”))
          };
    //The getJdbcTemplate method of JdbcDaoSupport returns an
    //instance of JdbcTemplate initialized with a datasource by the
    //Spring Bean Factory
    JdbcTemplate daoTmplt = this.getJdbcTemplate();
    return daoTmplt.queryForList(FIND_BY_SAL_RNG,dblParams);
  }
}

在上面的清单中,传入参数映射中的值存储在双字节数组中,顺序与SQL字符串中的位置参数相同。queryForList()方法以包含Map(用列名作为键,一项对应一列)的List(一项对应一行)的方式返回查询结果。稍后我会说明如何返回传输对象列表。

从简化的代码可以明显看出,JdbcTemplate鼓励重用,这大大削减了DAO实现中的代码。JDBC和collection包之间的紧密耦合已经消除。由于JdbcTemplate方法可确保在使用数据库资源后将其按正确的次序释放,所以JDBC的资源耗损不再是一个问题。

另外,使用Spring DAO时,不必处理异常。JdbcTemplate类会处理SQLException,并根据SQL错误代码或错误状态将其转换成特定于Spring异常的层次结构。例如,试图向主键列插入重复值时,将引发DataIntegrityViolationException。然而,如果无法从这一错误中恢复,就无需处理该异常。因为Spring DAO的根异常类DataAccessException是运行时异常类,所以可以这样做。值得注意的是Spring DAO异常独立于数据访问实现。如果实现是由O/R映射解决方案提供,就会抛出同样的异常。

第二步:修改业务服务- 现在业务服务实现了一个新方法setDao(),Spring容器使用该方法传递DAO实现类的引用。该过程称为“设置方法注入(setter injection)”,通过第三步中的配置文件告知Spring容器该过程。注意,不再需要使用DAOFactory,因为Spring BeanFactory提供了这项功能:

public class EmployeeBusinessServiceImpl
                         implements IEmployeeBusinessService {

  IEmployeeDAO empDAO;

  public List getEmployeesWithinSalaryRange(Map salaryMap){

    List empList = empDAO.findBySalaryRange(salaryMap);
    return empList;
  }
  public void setDao(IEmployeeDAO empDAO){
    this.empDAO = empDAO;
  }
}

请注意P2I的灵活性;即使极大地改动DAO实现,业务服务实现也只需少量更改。这是由于业务服务现在由Spring容器进行管理。

第三步:配置Bean Factory- Spring bean factory需要一个配置文件进行初始化并启动Spring框架。这个配置文件包含所有业务服务和带Spring bean容器的DAO实现类。除此之外,它还包含用于初始化数据源和JdbcDaoSupport的信息:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <!-- Configure Datasource -->
  <bean id="FIREBIRD_DATASOURCE"
    class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiEnvironment">
      <props>
        <prop key="java.naming.factory.initial">
          weblogic.jndi.WLInitialContextFactory
        </prop>
        <prop key="java.naming.provider.url">
          t3://localhost:7001
        </prop>
      </props>
    </property>
    <property name="jndiName">
      <value>
        jdbc/DBPool
      </value>
    </property>
  </bean>

  <!-- Configure DAO -->
  <bean id="EMP_DAO" class="com.bea.dev2dev.dao.EmployeeDAOImpl">
    <property name="dataSource">
      <ref bean="FIREBIRD_DATASOURCE"></ref>
    </property>
  </bean>

  <!-- Configure Business Service -->
  <bean id="EMP_BUSINESS"
  class="com.bea.dev2dev.sampleapp.business.EmployeeBusinessServiceImpl">
    <property name="dao">
      <ref bean="EMP_DAO"></ref>
    </property>
  </bean>
</beans>

这个Spring bean容器通过调用JdbcDaoSupport提供的setDataSource()方法,设置包含DAO实现的数据源对象。

第四步:测试- 最后是编写JUnit测试类。依照Spring的方式,需要在容器外部进行测试。然而,从第三步中的配置文件可以清楚地看到,我们一直在使用WebLogic Server连接池。

package com.bea.dev2dev.business;

import java.util.*;
import junit.framework.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class EmployeeBusinessServiceImplTest extends TestCase {
    private IEmployeeBusinessService empBusiness;
    private Map salaryMap;
    List expResult;

    protected void setUp() throws Exception {
        initSpringFramework();
        initSalaryMap();
        initExpectedResult();
    }
    private void initExpectedResult() {
        expResult = new ArrayList();
        Map tempMap = new HashMap();
        tempMap.put("EMP_NO",new Integer(1));
        tempMap.put("EMP_NAME","John");
        tempMap.put("SALARY",new Double(46.11));
        expResult.add(tempMap);
    }
    private void initSalaryMap() {
        salaryMap = new HashMap();
        salaryMap.put("MIN_SALARY","1");
        salaryMap.put("MAX_SALARY","50");
    }
    private void initSpringFramework() {
      ApplicationContext ac = new FileSystemXmlApplicationContext
		("C:/SpringConfig/Spring-Config.xml");
      empBusiness =
             (IEmployeeBusinessService)ac.getBean("EMP_BUSINESS");
    }
    protected void tearDown() throws Exception {
    }

    /**
     * Test of getEmployeesWithinSalaryRange method,
     * of class
     * com.bea.dev2dev.business.EmployeeBusinessServiceImpl.
     */
    public void testGetEmployeesWithinSalaryRange() {
      List result = empBusiness.getEmployeesWithinSalaryRange
					(salaryMap);
      assertEquals(expResult, result);
    }
}

使用绑定变量

到目前为止,我们搜索了工资介于最低值和最高值之间的雇员。假设在某种情形下,业务用户想要颠倒这一范围。DAO代码很脆弱,将不得不通过更改来满足要求的变化。这个问题在于使用了静态的位置绑定变量(用“?”表示)。Spring DAO通过支持命名的绑定变量来挽救这个情况。修改的IEmployeeDAO清单引入了命名的绑定变量(用“:<some name>”表示)。注意查询中的变化,如下所示:

import java.util.Map;
public interface IEmployeeDAO {

  //SQL String that will be executed
  public String FIND_BY_SAL_RNG = "SELECT EMP_NO, EMP_NAME, "
  + "SALARY FROM EMP WHERE SALARY >= :max AND SALARY <= :min";

  //Returns the list of employees falling into the given salary range
  //The input parameter is the immutable map object obtained from
  //the HttpServletRequest. This is an early refactoring based on
  //- "Introduce Parameter Object"

  public List findBySalaryRange(Map salaryMap);
}

多数JDBC驱动程序仅支持位置绑定变量。所以,Spring DAO在运行时将这个查询转换成位置绑定、基于变量的查询,并且设置正确的绑定变量。现在,为了完成这些任务,需要使用NamedParameterJdbcDaoSupport类和NamedParameterJdbcTemplate类,以代替JdbcDaoSupport和JdbcTemplate。下面就是修改后的DAO实现类:

import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport
    implements IEmployeeDAO{

  public List findBySalaryRange(Map salaryMap){

    NamedParameterJdbcTemplate tmplt =
                             this.getNamedParameterJdbcTemplate();
    return tmplt.queryForList(IEmployeeDAO.FIND_BY_SAL_RNG
				,salaryMap);
  }
}

NamedParameterJdbcDaoSupport的getNamedParameterJdbcTemplate()方法返回一个 NamedParameterJdbcTemplate实例,该实例由数据源句柄进行了预初始化。Spring Beanfactory执行初始化任务,从配置文件获得所有的详细信息。在执行时,一旦将命名的参数替换成位置占位符, NamedParameterJdbcTemplate就将操作委托给JdbcTemplate。可见,使用命名的参数使得DAO方法不受底层SQL声明任何更改的影响。

最后,如果数据库不支持自动类型转换,需要如下所示,对JUnit测试类中的initSalaryMap()方法稍做修改。

private void initSalaryMap() {
        salaryMap = new HashMap();
        salaryMap.put("MIN_SALARY",new Double(1));
        salaryMap.put("MAX_SALARY",new Double(50));
    }

Spring DAO回调函数

至此,已经说明为了解决传统DAO设计中存在的问题,如何封装和概括JdbcTemplate类中JDBC代码的静态部分。现在了解一下有关变量的问题,如设置绑定变量、结果集遍历等。虽然Spring DAO已经拥有这些问题的一般化解决方案,但在某些基于SQL的情况下,可能仍需要设置绑定变量。

在尝试向Spring DAO转换的过程中,介绍了由于业务服务及其客户机之间的约定遭到破坏而导致的隐蔽运行时错误。这个错误的来源可以追溯到原始的DAO。 dbcTemplate.queryForList()方法不再返回EmployeeTO实例列表。而是返回一个map表(每个map是结果集的一行)。

如您目前所知,JdbcTemplate基于模板方法设计模式,该模式利用JDBC API定义SQL执行工作流。必须改变这个工作流以修复被破坏的约定。第一个选择是在子类中更改或扩展工作流。您可以遍历 JdbcTemplate.queryForList()返回的列表,用EmployeeTO实例替换map对象。然而,这会导致我们一直竭力避免的静态代码与动态代码的混合。第二个选择是将代码插入JdbcTemplate提供的各种工作流修改钩子(hook)。明智的做法是在一个不同的类中封装传输对象填充代码,然后通过钩子链接它。填充逻辑的任何修改将不会改变DAO。

编写一个类,使其实现在Spring框架特定的接口中定义的方法,就可以实现第二个选择。这些方法称为回调函数,通过JdbcTemplate向框架注册。当发生相应的事件(例如,遍历结果集并填充独立于框架的传输对象)时,框架将调用这些方法。

第一步:传输对象

下面是您可能感兴趣的传输对象。注意,以下所示的传输对象是固定的:

package com.bea.dev2dev.to;

public final class EmployeeTO implements Serializable{

      private int empNo;
      private String empName;
      private double salary;

      /** Creates a new instance of EmployeeTO */
      public EmployeeTO(int empNo,String empName,double salary) {
          this.empNo = empNo;
          this.empName = empName;
          this.salary = salary;
      }
      public String getEmpName() {
          return this.empName;
      }
      public int getEmpNo() {
          return this.empNo;
      }
      public double getSalary() {
          return this.salary;
      }
      public boolean equals(EmployeeTO empTO){
          return empTO.empNo == this.empNo;
      }
}

第二步:实现回调接口

实现RowMapper接口,填充来自结果集的传输对象。下面是一个例子:

package com.bea.dev2dev.dao.mapper;

import com.bea.dev2dev.to.EmployeeTO;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;

public class EmployeeTOMapper implements RowMapper{

  public Object mapRow(ResultSet rs, int rowNum)
                                         throws SQLException{
      int empNo = rs.getInt(1);
      String empName = rs.getString(2);
      double salary = rs.getDouble(3);
      EmployeeTO empTo = new EmployeeTO(empNo,empName,salary);
      return empTo;
   }
}

注意实现类不应该对提供的ResultSet对象调用next()方法。这由框架负责,该类只要从结果集的当前行提取值就行。回调实现抛出的任何SQLException也由Spring框架处理。

第三步:插入回调接口

执行SQL查询时,JdbcTemplate利用默认的RowMapper实现产生map列表。现在需要注册自定义回调实现来修改 JdbcTemplate的这一行为。注意现在用的是NamedParameterJdbcTemplate的query()方法,而不是 queryForList()方法:

public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport
    implements IEmployeeDAO{

  public List findBySalaryRange(Map salaryMap){

    NamedParameterJdbcTemplate daoTmplt =
          getNamedParameterJdbcTemplate();
    return daoTmplt.query(IEmployeeDAO.FIND_BY_SAL_RNG, salaryMap,
          new EmployeeTOMapper());
  }
}

Spring DAO框架对执行查询后返回的结果进行遍历。它在遍历的每一步调用EmployeeTOMapper类实现的mapRow()方法,使用EmployeeTO传输对象填充最终结果的每一行。

第四步:修改后的JUnit类

现在要根据返回的传输对象测试这些结果。为此要对测试方法进行修改。

public class EmployeeBusinessServiceImplTest extends TestCase {

  private IEmployeeBusinessService empBusiness;
  private Map salaryMap;
      List expResult;

      // all methods not shown in the listing remain the
      // same as in the previous example
      private void initExpectedResult() {
          expResult = new ArrayList();
          EmployeeTO to = new EmployeeTO(2,"John",46.11);
          expResult.add(to);
      }

      /**
       * Test of getEmployeesWithinSalaryRange method, of
       * class com.bea.dev2dev.business.
       * EmployeeBusinessServiceImpl
       */
      public void testGetEmployeesWithinSalaryRange() {
          List result = empBusiness.
		getEmployeesWithinSalaryRange(salaryMap);
          assertEquals(expResult, result);
      }

      public void assertEquals(List expResult, List result){
          EmployeeTO expTO = (EmployeeTO) expResult.get(0);
          EmployeeTO actualTO = (EmployeeTO) result.get(0);
          if(!expTO.equals(actualTO)){
               throw new RuntimeException("** Test Failed **");
          }
      }
}

优势

Spring JDBC框架的优点很清楚。我们获益很多,并将DAO方法简化到只有几行代码。代码不再脆弱,这要感谢该框架对命名的参数绑定变量的“开箱即用”支持,以及在映射程序中将传输对象填充逻辑分离。

Spring JDBC的优点应该促使您向这一框架移植现有的代码。希望本文在这一方面能有所帮助。它会帮助您获得一些重构工具和知识。例如,如果您没有采用P2I Extract Interface,那么可以使用重构,从现有的DAO实现类创建接口。除此之外,查看本文的参考资料可以得到更多指导。

下载

可以下载本文用到的源代码。

结束语

在此篇文章中,我讲述了数据访问对象(DAO)设计模式的基础知识,并从正反两方面进行了讨论。引入Spring DAO或JDBC框架来克服传统DAO的不足。然后,根据Spring框架提供的“开箱即用”命名参数支持对脆弱的DAO代码进行了改进。最后,回调功能展示了如何在指定点修改框架行为。

参考资料

作者简介
  Dhrubojyoti Kayal 是Capgemini Consulting的高级顾问。在利用企业Java技术开发和设计应用程序和产品方面,拥有5年以上的经验。

[zt]Response.ContentType 详细列表

Filed under: HTML — Alex at 9:54 pm on Thursday, March 29, 2007

key words: response,contentType,meta

come from here

在经典同时看到两个关于ContentType的问题

http://bbs.blueidea.com/thread-2729935-1-1.html

http://bbs.blueidea.com/thread-2729945-1-1.html

所以查了下资料,copy了一份详细的ContentType的列表

不同的ContentType 会影响客户端所看到的效果.

默认的ContentType为 text/html  也就是网页格式.

代码如:

<% response.ContentType =“text/html” %>
<!–#i nclude virtual=”/ContentType.html” –>

显示的为网页,而

<% response.ContentType =“text/plain” %>
<!–#i nclude virtual=”/sscript/ContentType.html” –>

则会显示html原代码.

以下为一些常用的 ContentType

GIF images
<% response.ContentType =”image/gif” %>
<!–#i nclude virtual=”/myimage.gif” –>
JPEG images
<% response.ContentType =”image/jpeg” %>
<!–#i nclude virtual=”/myimage.jpeg” –>
TIFF images
<% response.ContentType =”image/tiff” %>
<!–#i nclude virtual=”/myimage.tiff” –>
MICROSOFT WORD document
<% response.ContentType =”application/msword” %>
<!–#i nclude virtual=”/myfile.doc” –>
RTF document
<% response.ContentType =”application/rtf” %>
<!–#i nclude virtual=”/myfile.rtf” –>
MICROSOFT EXCEL document
<% response.ContentType =”application/x-excel” %>
<!–#i nclude virtual=”/myfile.xls” –>
MICROSOFT POWERPOINT document
<% response.ContentType =”application/ms-powerpoint” %>
<!–#i nclude virtual=”/myfile.pff” –>
PDF document
<% response.ContentType =”application/pdf” %>
<!–#i nclude virtual=”/myfile.pdf” –>
ZIP document
<% response.ContentType =”application/zip” %>
<!–#i nclude virtual=”/myfile.zip” –>

下面是更详细的ContentType

application/andrew-inset ez
application/mac-binhex40 hqx
application/mac-compactpro cpt
application/mathml+xml mathml
application/msword doc
application/octet-stream bin dms lha lzh exe class so dll
application/oda oda
application/ogg ogg
application/pdf pdf
application/postscript ai eps ps
application/rdf+xml rdf
application/smil smi smil
application/srgs gram
application/srgs+xml grxml
application/vnd.mif mif
application/vnd.mozilla.xul+xml xul
application/vnd.ms-excel xls
application/vnd.ms-powerpoint ppt
application/vnd.wap.wbxml wbxml
application/vnd.wap.wmlc .wmlc wmlc
application/vnd.wap.wmlscriptc .wmlsc wmlsc
application/voicexml+xml vxml
application/x-bcpio bcpio
application/x-cdlink vcd
application/x-chess-pgn pgn
application/x-cpio cpio
application/x-csh csh
application/x-director dcr dir dxr
application/x-dvi dvi
application/x-futuresplash spl
application/x-gtar gtar
application/x-hdf hdf
application/x-httpd-php .php .php4 .php3 .phtml
application/x-httpd-php-source .phps
application/x-javascript js
application/x-koan skp skd skt skm
application/x-latex latex
application/x-netcdf nc cdf
application/x-pkcs7-crl .crl
application/x-sh sh
application/x-shar shar
application/x-shockwave-flash swf
application/x-stuffit sit
application/x-sv4cpio sv4cpio
application/x-sv4crc sv4crc
application/x-tar .tgz tar
application/x-tcl tcl
application/x-tex tex
application/x-texinfo texinfo texi
application/x-troff t tr roff
application/x-troff-man man
application/x-troff-me me
application/x-troff-ms ms
application/x-ustar ustar
application/x-wais-source src
application/x-x509-ca-cert .crt
application/xhtml+xml xhtml xht
application/xml xml xsl
application/xml-dtd dtd
application/xslt+xml xslt
application/zip zip
audio/basic au snd
audio/midi mid midi kar
audio/mpeg mpga mp2 mp3
audio/x-aiff aif aiff aifc
audio/x-mpegurl m3u
audio/x-pn-realaudio ram rm
audio/x-pn-realaudio-plugin rpm
audio/x-realaudio ra
audio/x-wav wav
chemical/x-pdb pdb
chemical/x-xyz xyz
image/bmp bmp
image/cgm cgm
image/gif gif
image/ief ief
image/jpeg jpeg jpg jpe
image/png png
image/svg+xml svg
image/tiff tiff tif
image/vnd.djvu djvu djv
image/vnd.wap.wbmp .wbmp wbmp
image/x-cmu-raster ras
image/x-icon ico
image/x-portable-anymap pnm
image/x-portable-bitmap pbm
image/x-portable-graymap pgm
image/x-portable-pixmap ppm
image/x-rgb rgb
image/x-xbitmap xbm
image/x-xpixmap xpm
image/x-xwindowdump xwd
model/iges igs iges
model/mesh msh mesh silo
model/vrml wrl vrml
text/calendar ics ifb
text/css css
text/html .shtml html htm
text/plain asc txt
text/richtext rtx
text/rtf rtf
text/sgml sgml sgm
text/tab-separated-values tsv
text/vnd.wap.wml .wml wml
text/vnd.wap.wmlscript .wmls wmls
text/x-setext etx
video/mpeg mpeg mpg mpe
video/quicktime qt mov
video/vnd.mpegurl mxu
video/x-msvideo avi
video/x-sgi-movie movie
x-conference/x-cooltalk ice

[zt]插入图片到数据库(BLOB大字段保存对象)

Filed under: java开发技术, 数据库 — Alex at 10:02 pm on Wednesday, March 28, 2007

[zt]用JSP实现上传文件的两种方法

Filed under: java开发技术 — Alex at 10:03 pm on Monday, March 26, 2007

key words: 文件上传,upload, cos.jar + uploadbean.jar + filemover.jar

以前用cos作文件上传,但是对于文件上传后的改名还需要借助其他的工具。

摘录如下:

在用Java开发企业器系统的使用,特别是涉及到与办公相关的软件开发的时候,文件的上传是客户经常要提到的要求.因此有 一套很好文件上传的解决办法也能方便大家在这一块的开发.

首先申明,该文章是为了自己记录一备以后开发需要的时候,不用手忙脚乱哈哈……..

现在在国内用的非常多的一般是两种方法解决来解决文件上传.

cos.jar + uploadbean.jar + filemover.jar
这个是用的非常普遍的,原因是因为他操作方便,是我们不必再去关注,那些文件的输入和输出流,使我们从底层的流中解脱出来.
UploadFile,UploadBean,MultipartFormDataRequest

<%@ page contentType=”text/html;charset=gb2312″ %>
<head>
<title>fbysss UploadBean 示例</title>
<!–meta http-equiv=”Content-Type” content=”text/html; charset=iso-8859-1″–>
<!–meta http-equiv=”Content-Type” content=”text/html; charset=gb2312″–>
</head>
<FORM name=”form1″ METHOD=”POST” ACTION=”sssupload.jsp” ENCTYPE=”multipart/form-data”>
<input name=”title” type= ”text” value=”中文字”>
<td class=”bodystyle”>附件</td>
<td class=”bodystyle”> <input name=”attach” type=”FILE” id=”attach” size=”50″ > </td>
<input name=”ok” type= ”submit” value=”提交”>
</form>

2.读取表单页面sssgetdata.jsp

<!–
//==========================================================================
//文件:UploadBean上传实例
//功能:解决中文乱码,完成文件上传,并提供上传改名解决方案
//作者:fbysss
//msn:jameslastchina@hotmail.com
//==========================================================================
–>
<%@ page contentType=”text/html;charset=GBK” %>
<%@ page language=”java” import=”com.jspsmart.upload.*”>
<%@ page import=”java.text.SimpleDateFormat”>
<%@ page import=”java.io.File”>
<%@ page import=”java.util.*”>
<%@ page import=”javazoom.upload.*”>
<%@ page import=”uploadutilities.FileMover”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=gb2312″>
</head>
<
request
.setCharacterEncoding(”GBK”);//设置编码格式,就不用一个个转码了。
FileMover fileMover 
= new FileMover();//你也可以使用自带的实例中jsp:useBean的形式。
UploadBean upBean 
= new UploadBean();
MultipartFormDataRequest mrequest 
= null;
Hashtable files = null;

if (MultipartFormDataRequest.isMultipartFormData(request))
{
mrequest 
= new MultipartFormDataRequest(request,null,100*1024*1024,MultipartFormDataRequest.COSPARSER,”GBK”);//注意这里也要设置编码参数
String sTt0 
= mrequest.getParameter(”title”);
out.println(”<br>Title0是:”+sTt0+”<br>“);
String sTt1 = new String(sTt0.getBytes(”ISO-8859-1″),”GBK”);
out.println(”
<br>Title1是:”+sTt1+”<br>“);
//这里用来测试title参数是否正确。调试的时候,加一句if (true)return;即可。
files = mrequest.getFiles();
}


//获取修改前的文件名
String sOldFileName =mrequest.getParameter(”oldfilename”);
out.println(”sOldFileName:”+sOldFileName);
String sWebRootPath = request.getRealPath(”/”);//得到你的web应用的根。
String sPath=sWebRootPath+”attach”;
int iFileCount = 0;
String sServerFileName=”";
String sLocalFileName = ”";
//文件获取
if ( (files != null) || (!files.isEmpty()) ) {

iFileCount = files.size();
UploadFile file = (UploadFile) files.get(”attach”);
sLocalFileName=file.getFileName();
out.println(”sLocalFileName:”+sLocalFileName);
int ii= sLocalFileName.indexOf(”.”); //取文件名的后缀
String sExt = sLocalFileName.substring(ii,sLocalFileName.length());
//得到不重复的文件名
java.util.Date dt = new java.util.Date(System.currentTimeMillis());
SimpleDateFormat fmt = new SimpleDateFormat(”yyyyMMddHHmmssSSS”);
sServerFileName= fmt.format(dt);
sServerFileName =sServerFileName + sExt;
//如果不存在该目录,则新建一个
File dir =new File(sPath);
if (!dir.exists()){
dir.mkdirs();
}
upBean.setFolderstore(sPath);//设置要上传的目录
upBean.addUploadListener(fileMover);//增加filMover监听
fileMover.setNewfilename(sServerFileName);//设置服务器上的文件名
upBean.store(mrequest, ”attach”);//上传
out.println(”file path is ”+sPath+”/”+sServerFileName);
}

%>

Demo注意事项:cos.jar,uploadbean.jar,filemover.jar这几个包必须有。
必备下载地址:
UploadBean1.5:http://wcarchive.cdrom.com/pub/simtelnet/winnt/java/uploadbean1_5.zip
FileMover1.3:http://www.javazoom.net/jzservlets/uploadbean/addons/filemover1.3.zip
参考下载地址:
ChinesUpload例子:http://www.javazoom.net/jzservlets/uploadbean/addons/ChineseUpload.zip
参考了fbysss的一篇文章
2. org.apache.struts.upload.FormFile
Struts1.1的org.apache.struts.upload.FormFile类。很方便,不用自己写。也不用写一个jsp调用jspsmartupload就可以搞定。

选择上传文件页面:selfile.jsp

——————————————————————————–
<%@ taglib uri=”/WEB-INF/struts-html.tld” prefix=”html”%>
<html:html>
<html:form action=”/uploadsAction.do” enctype=”multipart/form-data”>
<html:file property=”theFile”/>
<html:submit/>
</html:form>
</html:html>

——————————————————————————–
UpLoadAction.java
——————————————————————————–

import java.io.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.upload.FormFile;

/**
* <p>Title:UpLoadAction</p>
* <p>Description: QRRSMMS </p>
* <p>Copyright: Copyright (c) 2004 jiahansoft</p>
* <p>Company: jiahansoft</p>
@author wanghw
@version 1.0
*/


public class UpLoadAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
if (form instanceof uploadsForm) {//如果form是uploadsForm
String encoding = request.getCharacterEncoding();
if ((encoding != null&& (encoding.equalsIgnoreCase(utf-8)))
{
response.setContentType(
text/html; charset=gb2312);//如果没有指定编码,编码格式为gb2312
}

UpLoadForm theForm 
= (UpLoadForm ) form;
FormFile file 
= theForm.getTheFile();//取得上传的文件
try {
InputStream stream 
= file.getInputStream();//把文件读入
String filePath = request.getRealPath(/);//取当前系统路径
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream bos 
= new FileOutputStream(filePath + / +
file.getFileName());
//建立一个上传文件的输出流
//System.out.println(filePath+”/”+file.getFileName());
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ( (bytesRead = stream.read(buffer, 08192)) != -1{
bos.write(buffer, 
0, bytesRead);//将文件写入服务器
}

bos.close();
stream.close();
}
catch(Exception e){
System.err.print(e);
}

//request.setAttribute(”dat”,file.getFileName());
return mapping.findForward(display);
}

return null;
}

}



——————————————————————————–

UpLoadForm.java

——————————————————————————–

import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.*;
import org.apache.struts.upload.*;

/**
* <p>Title:UpLoadForm</p>
* <p>Description: QRRSMMS </p>
* <p>Copyright: Copyright (c) 2004 jiahansoft</p>
* <p>Company: jiahansoft</p>
@author wanghw
@version 1.0
*/


public class UpLoadForm extends ActionForm {
public static final String ERROR_PROPERTY_MAX_LENGTH_EXCEEDED = org.apache.struts.webapp.upload.MaxLengthExceeded;
protected FormFile theFile;
public FormFile getTheFile() {
return theFile;
}

public void setTheFile(FormFile theFile) {
this.theFile = theFile;
}

public ActionErrors validate(ActionMapping mapping, HttpServletRequest request)
{
ActionErrors errors 
= null;
//has the maximum length been exceeded?
Boolean maxLengthExceeded = (Boolean)
request.getAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);
if ((maxLengthExceeded != null&& (maxLengthExceeded.booleanValue()))
{
errors 
= new ActionErrors();
errors.add(ERROR_PROPERTY_MAX_LENGTH_EXCEEDED, 
new ActionError(maxLengthExceeded));
}

return errors;

}

}

//这是相对应的form,还有其他属性可以设置,具体可以参考struts的上传例子。

——————————————————————————–

struts-config.xml

——————————————————————————–

<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE struts-config PUBLIC “-//Apache Software Foundation//DTD Struts Configuration 1.1//EN” “http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd”;>
<struts-config>
<form-beans>
<form-bean name=”uploadsForm” type=”UpLoadForm” />
</form-beans>
<action-mappings>
<action name=”uploadsForm” type=”UpLoadAction” path=”/uploadsAction”>
<forward name=”display” path=”/display.jsp” />
</action>
</action-mappings>
</struts-config>
<!–display.jsp就是随便写一个成功页–>

jsp防盗链

Filed under: java开发技术 — Alex at 10:05 pm on Tuesday, March 13, 2007

key words : jsp防盗链 header

最近碰到盗链的问题,即复制一个url地址,在另一个地方也能访问。

index.jsp页面

<html>
  
<head><title>Simple jsp page</title></head>
  
<body>Place your content here

  here is index jsp
    get header info
  
<href=”a.jsp”>a.jsp</a>
  
</body>
</html>
a.jsp页面

<html>
  
<head><title>Simple jsp page</title></head>
  
<body>Place your content here

  here is a. jsp
    get header info
  
<%=request.getHeader(Referer)%>
  
<%if(null == request.getHeader(Referer) || request.getHeader(Referer).indexOf(yourdomain.com< 0){%>
     做人要厚道
  
<%}else{%>
  合法访问
  
<%}%>
  
</body>
</html>

即从内部访问可以,直接粘贴地址在另一个浏览器里访问禁止

真正理解面向接口编程

Filed under: java开发技术 — Alex at 10:06 pm on Monday, March 12, 2007

key words :面向接口编程

面向对象设计里有一点大家已基本形成共识,就是面向接口编程,我想大多数人对这个是没有什么觉得需要怀疑的。

问题是在实际的项目开发中我们是怎么体现的呢? 难道就是每一个实现都提供一个接口就了事了?反过来说,你有时候有没有觉得接口是多余的事? 又或者,你仅仅是觉得现在类似spring这样的框架已习惯用接口这种方式而心存当然。

设计模式解析里提到了面向对象设计考虑的几个视角,一个是概念层,一个是规约层,一个是实现层。我如果没有猜错的话,实际上我们大多数人的眼睛一直是盯着实现层的,而这正是面向对象设计所极力避免的,即你不要在一开始就关注这些细节,你要关注的是规约(接口).

对于实际项目开发来说,如果我们把实现的过程分为多个阶段的话我们不妨这么划分,第一阶段,根据client端的需要去设计我们的规约(interface),在这个阶段任何实现都没有,所有的任务就是定义接口所需要的职责,以及所需要的一些po,vo;第二阶段,实现前面定义的规约。而以前我是怎么做的呢? 我是交叉作的,即假模假样的定义一个接口(其实我心里在想这个东西有屁用),然后定义了一个方法,然后就立即去实现这个方法,再然后我又定义一个方法,继续去实现,我现在终于想通了,这样好累,效率很低,最重要的是,这不属于真正的设计。
现在我是怎么做的呢?比如一个list.jsp里需要查询,列表,然后看明细信息,然后增加信息,我会第一步在接口里定义完(这个过程会有整体设计的意识),毫不关心底层实现(数据库、事务),我的目标就是”我想要这个功能,我想要那个功能”,至于那个功能怎么实现在第一阶段我认为那不是我的事情(尽管这个事情最终还是由我来做) .大家看这个过程和前面的过程有什么本质的不同呢? 就是分层的概念更加明显,你的工作更有层次,每次都有先设计再实现的步骤,而前面那个过程很容易就让你不知不觉地陷入纯实现的陷阱中。

一点感想,欢迎大家拍砖。