现在的位置: 首页 > 综合 > 正文

Spring JdbcTemplate 与 事务管理 学习

2013年03月28日 ⁄ 综合 ⁄ 共 21033字 ⁄ 字号 评论关闭

Spring的JDBC框架能够承担资源管理和异常处理的工作,从而简化我们的JDBC代码,
让我们只需编写从数据库读写数据所必需的代码。Spring把数据访问的样板代码隐藏到模板类之下,
结合Spring的事务管理,可以大大简化我们的代码.

Spring提供了3个模板类:

JdbcTemplate:Spring里最基本的JDBC模板,利用JDBC和简单的索引参数查询提供对数据库的简单访问。
NamedParameterJdbcTemplate:能够在执行查询时把值绑定到SQL里的命名参数,而不是使用索引参数。
SimpleJdbcTemplate:利用Java 5的特性,比如自动装箱、通用(generic)和可变参数列表来简化JDBC模板的使用。
具体使用哪个模板基本上取决于个人喜好。

使用Spring的JdbcTemplate来实现简单的增删改查,首先建立测试数据表person
create table person(
id int not null primary key auto_increment,
name varchar(20) not null
)

导入依赖的jar包,由于测试中数据源使用的是dbcp数据源,需要以下jar包支持:
commons-logging.jar
commons-pool.jar
commons-dbcp.jar
同时还必须导入数据库驱动jar包:mysql-connector-java-3.1.8-bin.jar

建立实体bean
Person.java

Java代码
复制代码

收藏代码
  1. package com.royzhou.jdbc; 
  2.  
  3. public class PersonBean { 
  4.     private int id; 
  5.     private String name; 
  6.  
  7.     public PersonBean() { 
  8.     } 
  9.      
  10.     public PersonBean(String name) { 
  11.         this.name = name; 
  12.     } 
  13.      
  14.     public PersonBean(int id, String name) { 
  15.         this.id = id; 
  16.         this.name = name; 
  17.     } 
  18.       
  19.     public int getId() { 
  20.         return id; 
  21.     } 
  22.  
  23.     public void setId(int id) { 
  24.         this.id = id; 
  25.     } 
  26.  
  27.     public String getName() { 
  28.         return name; 
  29.     } 
  30.  
  31.     public void setName(String name) { 
  32.         this.name = name; 
  33.     } 
  34.      
  35.     public String toString() { 
  36.         return this.id +
    ":" + this.name; 
  37.     } 

接口类:
PersonService.java

Java代码
复制代码

收藏代码
  1. package com.royzhou.jdbc; 
  2.  
  3. import java.util.List; 
  4.  
  5. public interface PersonService { 
  6.      
  7.     public void addPerson(PersonBean person); 
  8.      
  9.     public void updatePerson(PersonBean person); 
  10.      
  11.     public void deletePerson(int id); 
  12.      
  13.     public PersonBean queryPerson(int id); 
  14.      
  15.     public List<PersonBean> queryPersons(); 

实现类:
PersonServiceImpl.java

Java代码
复制代码

收藏代码
  1. package com.royzhou.jdbc; 
  2.  
  3. import java.util.List; 
  4.  
  5. import javax.sql.DataSource; 
  6. import java.sql.Types; 
  7.  
  8. import org.springframework.jdbc.core.JdbcTemplate; 
  9.  
  10. public class PersonServiceImpl
    implements PersonService { 
  11.  
  12.     private JdbcTemplate jdbcTemplate; 
  13.      
  14.     /**
  15.      * 通过Spring容器注入datasource
  16.      * 实例化JdbcTemplate,该类为主要操作数据库的类
  17.      * @param ds
  18.      */ 
  19.     public void setDataSource(DataSource ds) { 
  20.         this.jdbcTemplate = new JdbcTemplate(ds); 
  21.     } 
  22.      
  23.     public void addPerson(PersonBean person) { 
  24.         /**
  25.          * 第一个参数为执行sql
  26.          * 第二个参数为参数数据
  27.          * 第三个参数为参数类型
  28.          */ 
  29.         jdbcTemplate.update("insert into person values(null,?)",
    new Object[]{person.getName()},
    new int[]{Types.VARCHAR}); 
  30.     } 
  31.  
  32.     public void deletePerson(int id) { 
  33.         jdbcTemplate.update("delete from person where id = ?",
    new Object[]{id}, new
    int[]{Types.INTEGER}); 
  34.     } 
  35.  
  36.     public PersonBean queryPerson(int id) { 
  37.         /**
  38.          * new PersonRowMapper()是一个实现RowMapper接口的类,
  39.          * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
  40.          */ 
  41.         PersonBean pb = (PersonBean) jdbcTemplate.queryForObject("select id,name from person where id = ?",
    new Object[]{id}, new PersonRowMapper()); 
  42.         return pb; 
  43.     } 
  44.  
  45.     @SuppressWarnings("unchecked"
  46.     public List<PersonBean> queryPersons() { 
  47.         List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person",
    new PersonRowMapper()); 
  48.         return pbs; 
  49.     } 
  50.  
  51.     public void updatePerson(PersonBean person) { 
  52.         jdbcTemplate.update("update person set name = ? where id = ?",
    new Object[]{person.getName(), person.getId()},
    new int[]{Types.VARCHAR, Types.INTEGER}); 
  53.     } 

PersonRowMapper.java

Java代码
复制代码

收藏代码
  1. package com.royzhou.jdbc; 
  2.  
  3. import java.sql.ResultSet; 
  4. import java.sql.SQLException; 
  5.  
  6. import org.springframework.jdbc.core.RowMapper; 
  7.  
  8. public class PersonRowMapper
    implements RowMapper { 
  9.     //默认已经执行rs.next(),可以直接取数据 
  10.     public Object mapRow(ResultSet rs,
    int index) throws SQLException { 
  11.         PersonBean pb = new PersonBean(rs.getInt("id"),rs.getString("name")); 
  12.         return pb; 
  13.     } 

我们需要在bean.xml中配置DataSource,并且将datasource注入到我们的业务类中

Xml代码
复制代码

收藏代码
  1. <?xml
    version="1.0"
    encoding="UTF-8"?> 
  2. <beans
    xmlns="http://www.springframework.org/schema/beans" 
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4.        xmlns:context="http://www.springframework.org/schema/context"  
  5.        xmlns:aop="http://www.springframework.org/schema/aop" 
  6.        xsi:schemaLocation="http://www.springframework.org/schema/beans 
  7.            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
  8.            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd 
  9.            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> 
  10.  
  11.      <context:property-placeholder
    location="classpath:jdbc.properties"/> 
  12.      <bean
    id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close"> 
  13.         <property
    name="driverClassName"
    value="${driverClassName}"/> 
  14.         <property
    name="url"
    value="${url}"/> 
  15.         <property
    name="username"
    value="${username}"/> 
  16.         <property
    name="password"
    value="${password}"/> 
  17.          <!-- 连接池启动时的初始值 --> 
  18.          <property
    name="initialSize"
    value="${initialSize}"/> 
  19.          <!-- 连接池的最大值 --> 
  20.          <property
    name="maxActive"
    value="${maxActive}"/> 
  21.          <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --> 
  22.          <property
    name="maxIdle"
    value="${maxIdle}"/> 
  23.          <!--  最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 --> 
  24.          <property
    name="minIdle"
    value="${minIdle}"/> 
  25.      </bean> 
  26.      
  27. </beans> 

jdbc.properties

Java代码
复制代码

收藏代码
  1. driverClassName=org.gjt.mm.mysql.Driver 
  2. url=jdbc:mysql://localhost:3306/royzhou?useUnicode=true&characterEncoding=UTF-8 
  3. username=root 
  4. password=123456 
  5. initialSize=1 
  6. maxActive=500 
  7. maxIdle=2 
  8. minIdle=1 

编写我们的测试类:TestJdbcTemplate.java

Java代码
复制代码

收藏代码
  1. package com.royzhou.jdbc; 
  2.  
  3. import org.springframework.context.ApplicationContext; 
  4. import org.springframework.context.support.ClassPathXmlApplicationContext; 
  5.  
  6. public class TestJdbcTemplate { 
  7.     public static
    void main(String[] args) { 
  8.         ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml"); 
  9.         PersonService ps = (PersonService)ctx.getBean("personService"); 
  10.         ps.addPerson(new PersonBean("royzhou")); 
  11.         PersonBean pb = ps.queryPerson(1); 
  12.         System.out.println(pb); 
  13.         pb.setName("haha"); 
  14.         ps.updatePerson(pb); 
  15.         pb = ps.queryPerson(1); 
  16.         System.out.println(pb); 
  17.         ps.deletePerson(1); 
  18.         pb = ps.queryPerson(1); 
  19.         System.out.println(pb); 
  20.     } 

上面代码先插入一条记录,然后修改,之后删除,运行之后出现异常,异常信息:
EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

难道Spring的queryForObject在查找不到记录的时候会抛出异常,看了一下Spring的源代码 发现确实如此:

Java代码
复制代码

收藏代码
  1. public Object queryForObject(String sql, Object[] args,
    int[] argTypes, RowMapper rowMapper)
    throws DataAccessException { 
  2.         List results = (List) query(sql, args, argTypes,
    new
    RowMapperResultSetExtractor(rowMapper, 1)); 
  3.         return DataAccessUtils.requiredUniqueResult(results); 
  4.     } 
  5.  
  6.     public Object queryForObject(String sql, Object[] args, RowMapper rowMapper)
    throws DataAccessException { 
  7.         List results = (List) query(sql, args, new RowMapperResultSetExtractor(rowMapper,
    1)); 
  8.         return DataAccessUtils.requiredUniqueResult(results); 
  9.     } 
  10.  
  11.     public Object queryForObject(String sql, RowMapper rowMapper)
    throws DataAccessException { 
  12.         List results = query(sql, rowMapper); 
  13.         return DataAccessUtils.requiredUniqueResult(results); 
  14.     } 
  15.  
  16.     public static Object requiredUniqueResult(Collection results)
    throws IncorrectResultSizeDataAccessException { 
  17.         int size = (results !=
    null ? results.size() : 0); 
  18.         if (size == 0) { 
  19.             throw new EmptyResultDataAccessException(1);
    // 问题在这里 
  20.         } 
  21.         if (!CollectionUtils.hasUniqueObject(results)) { 
  22.             throw new IncorrectResultSizeDataAccessException(1, size); 
  23.         } 
  24.         return results.iterator().next(); 
  25.     } 

发现当查找不到记录是,requiredUniqueResult方法做了判断,抛出异常, 想不明白为什么Spring要在这里做这样的判断,为啥不返回null????

重新修改PersonServiceImple类,把queryPerson方法改为使用列表查询的方式再去根据index取
PersonServiceImpl.java

Java代码
复制代码

收藏代码
  1. package com.royzhou.jdbc; 
  2.  
  3. import java.util.List; 
  4.  
  5. import javax.sql.DataSource; 
  6. import java.sql.Types; 
  7.  
  8. import org.springframework.jdbc.core.JdbcTemplate; 
  9.  
  10. public class PersonServiceImpl
    implements PersonService { 
  11.  
  12.     private JdbcTemplate jdbcTemplate; 
  13.      
  14.     /**
  15.      * 通过Spring容器注入datasource
  16.      * 实例化JdbcTemplate,该类为主要操作数据库的类
  17.      * @param ds
  18.      */ 
  19.     public void setDataSource(DataSource ds) { 
  20.         this.jdbcTemplate = new JdbcTemplate(ds); 
  21.     } 
  22.      
  23.     public void addPerson(PersonBean person) { 
  24.         /**
  25.          * 第一个参数为执行sql
  26.          * 第二个参数为参数数据
  27.          * 第三个参数为参数类型
  28.          */ 
  29.         jdbcTemplate.update("insert into person values(null,?)",
    new Object[]{person.getName()},
    new int[]{Types.VARCHAR}); 
  30.     } 
  31.  
  32.     public void deletePerson(int id) { 
  33.         jdbcTemplate.update("delete from person where id = ?",
    new Object[]{id}, new
    int[]{Types.INTEGER}); 
  34.     } 
  35.  
  36.     @SuppressWarnings("unchecked"
  37.     public PersonBean queryPerson(int id) { 
  38.         /**
  39.          * new PersonRowMapper()是一个实现RowMapper接口的类,
  40.          * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
  41.          */ 
  42.         List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?",
    new Object[]{id}, new PersonRowMapper()); 
  43.         PersonBean pb = null
  44.         if(pbs.size()>0) { 
  45.             pb = pbs.get(0); 
  46.         } 
  47.         return pb; 
  48.     } 
  49.  
  50.     @SuppressWarnings("unchecked"
  51.     public List<PersonBean> queryPersons() { 
  52.         List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person",
    new PersonRowMapper()); 
  53.         return pbs; 
  54.     } 
  55.  
  56.     public void updatePerson(PersonBean person) { 
  57.         jdbcTemplate.update("update person set name = ? where id = ?",
    new Object[]{person.getName(), person.getId()},
    new int[]{Types.VARCHAR, Types.INTEGER}); 
  58.     } 

再次运行测试类,输出:
1:royzhou
1:haha
null

得到预期的结果.

从上面代码可以看出,使用Spring提供的JDBCTemplate类很大程度减少了我们的代码量,
比起以前我们写JDBC操作,需要先获取Connection,然后是PreparedStatement,再到Result,
使用Spring JDBCTemplate写出来的代码看起来更加简洁,开发效率也比较快.

在数据库的操作中,事务是一个重要的概念,举个例子:

大概每个人都有转账的经历。当我们从A帐户向B帐户转100元后,银行的系统会从A帐户上扣除100而在B帐户上加100,这是一般的正常现象。
但是一旦系统出错了怎么办呢,这里我们假设可能会发生两种情况:
(1)A帐户上少了100元,但是B帐户却没有多100元。
(2)B帐户多了100元钱,但是A帐户上却没有被扣钱。
这种错误一旦发生就等于出了大事,那么再假如一下,你要转账的是1亿呢?
所以上面的两种情况分别是你和银行不愿意看到的,因为谁都不希望出错。那么有没有什么方法保证一旦A帐户上没有被扣钱而B帐户上也没有被加钱;
或者A帐户扣了100元而B帐户准确无误的加上100元呢。也就是说要么转账顺利的成功进行,要么不转账呢?可以,这就是数据库事务机制所要起到的作用和做的事情。

Spring对事务的管理有丰富的支持,Spring提供了编程式配置事务和声明式配置事务:

声明式事务有以下两种方式
一种是使用Annotation注解的方式(官方推荐)
一种是基于Xml的方式

采用任何一种方式我们都需要在我们的bean.xml中添加事务支持:

Xml代码
复制代码

收藏代码
  1. <?xml
    version="1.0"
    encoding="UTF-8"?> 
  2. <beans
    xmlns="http://www.springframework.org/schema/beans" 
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4.     xmlns:context="http://www.springframework.org/schema/context" 
  5.     xmlns:aop="http://www.springframework.org/schema/aop" 
  6.     xmlns:tx="http://www.springframework.org/schema/tx" 
  7.     xsi:schemaLocation="http://www.springframework.org/schema/beans 
  8.            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
  9.            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd 
  10.            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd 
  11.            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 
  12.  
  13.     <context:property-placeholder
    location="classpath:jdbc.properties"
    /> 
  14.     <bean
    id="dataSource" 
  15.         class="org.apache.commons.dbcp.BasicDataSource" 
  16.         destroy-method="close"> 
  17.         <property
    name="driverClassName"
    value="${driverClassName}"
    /> 
  18.         <property
    name="url"
    value="${url}"
    /> 
  19.         <property
    name="username"
    value="${username}"
    /> 
  20.         <property
    name="password"
    value="${password}"
    /> 
  21.         <!-- 连接池启动时的初始值 --> 
  22.         <property
    name="initialSize"
    value="${initialSize}"
    /> 
  23.         <!-- 连接池的最大值 --> 
  24.         <property
    name="maxActive"
    value="${maxActive}"
    /> 
  25.         <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --> 
  26.         <property
    name="maxIdle"
    value="${maxIdle}"
    /> 
  27.         <!--  最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 --> 
  28.         <property
    name="minIdle"
    value="${minIdle}"
    /> 
  29.     </bean> 
  30.  
  31.     <bean
    id="personService" 
  32.         class="com.royzhou.jdbc.PersonServiceImpl"> 
  33.         <property
    name="dataSource"
    ref="dataSource"></property> 
  34.     </bean> 
  35.      
  36.     <bean
    id="txManager" 
  37.         class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
  38.         <property
    name="dataSource"
    ref="dataSource"
    /> 
  39.     </bean> 
  40.      
  41.     <tx:annotation-driven
    transaction-manager="txManager"
    />  
  42. </beans> 

<tx:annotation-driven transaction-manager="txManager" />  这句话的作用是注册事务注解处理器

定义好配置文件后我们只需要在我们的类上加上注解@Transactional,就可以指定这个类需要受Spring的事务管理
默认Spring为每个方法开启一个事务,如果方法发生运行期错误unchecked(RuntimeException),事务会进行回滚
如果发生checked Exception,事务不进行回滚.

例如在下面的例子中,我们为PersonServiceImpl添加事务支持.

Java代码
复制代码

收藏代码
  1. package com.royzhou.jdbc; 
  2.  
  3. import java.util.List; 
  4.  
  5. import javax.sql.DataSource; 
  6. import java.sql.Types; 
  7.  
  8. import org.springframework.jdbc.core.JdbcTemplate; 
  9. import org.springframework.transaction.annotation.Transactional; 
  10.  
  11. @Transactional 
  12. public class PersonServiceImpl
    implements PersonService { 
  13.  
  14.     private JdbcTemplate jdbcTemplate; 
  15.      
  16.     /**
  17.      * 通过Spring容器注入datasource
  18.      * 实例化JdbcTemplate,该类为主要操作数据库的类
  19.      * @param ds
  20.      */ 
  21.     public void setDataSource(DataSource ds) { 
  22.         this.jdbcTemplate = new JdbcTemplate(ds); 
  23.     } 
  24.      
  25.     public

抱歉!评论已关闭.