spring jdbc Template2

本文介绍Spring框架如何简化数据库操作,特别是事务管理。通过Spring JdbcTemplate减少代码量,提高开发效率,并详细解析Spring支持的编程式及声明式事务配置。

从上面代码可以看出,使用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>  

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd ">

    <context:property-placeholder location="classpath:jdbc.properties" />
    <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="${driverClassName}" />
        <property name="url" value="${url}" />
        <property name="username" value="${username}" />
        <property name="password" value="${password}" />
        <!-- 连接池启动时的初始值 -->
        <property name="initialSize" value="${initialSize}" />
        <!-- 连接池的最大值 -->
        <property name="maxActive" value="${maxActive}" />
        <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
        <property name="maxIdle" value="${maxIdle}" />
        <!--  最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
        <property name="minIdle" value="${minIdle}" />
    </bean>

    <bean id="personService"
        class="com.royzhou.jdbc.PersonServiceImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="txManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
    <tx:annotation-driven transaction-manager="txManager" />
</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 void addPerson(PersonBean person) {  
  26.         /**
  27.          * 第一个参数为执行sql
  28.          * 第二个参数为参数数据
  29.          * 第三个参数为参数类型
  30.          */  
  31.         jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});  
  32.         throw new RuntimeException("运行期例外");  
  33.     }  
  34.  
  35.     public void deletePerson(int id) {  
  36.         jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER});  
  37.     }  
  38.  
  39.     @SuppressWarnings("unchecked")  
  40.     public PersonBean queryPerson(int id) {  
  41.         /**
  42.          * new PersonRowMapper()是一个实现RowMapper接口的类,
  43.          * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
  44.          */  
  45.         List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper());  
  46.         PersonBean pb = null;  
  47.         if(pbs.size()>0) {  
  48.             pb = pbs.get(0);  
  49.         }  
  50.         return pb;  
  51.     }  
  52.  
  53.     @SuppressWarnings("unchecked")  
  54.     public List<PersonBean> queryPersons() {  
  55.         List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper());  
  56.         return pbs;  
  57.     }  
  58.  
  59.     public void updatePerson(PersonBean person) {  
  60.         jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});  
  61.     }  
  62. }  

package com.royzhou.jdbc;

import java.util.List;

import javax.sql.DataSource;
import java.sql.Types;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

@Transactional
public class PersonServiceImpl implements PersonService {

    private JdbcTemplate jdbcTemplate;
    
    /**
     * 通过Spring容器注入datasource
     * 实例化JdbcTemplate,该类为主要操作数据库的类
     * @param ds
     */
    public void setDataSource(DataSource ds) {
        this.jdbcTemplate = new JdbcTemplate(ds);
    }
    
    public void addPerson(PersonBean person) {
        /**
         * 第一个参数为执行sql
         * 第二个参数为参数数据
         * 第三个参数为参数类型
         */
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});
        throw new RuntimeException("运行期例外");
    }

    public void deletePerson(int id) {
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER});
    }

    @SuppressWarnings("unchecked")
    public PersonBean queryPerson(int id) {
        /**
         * new PersonRowMapper()是一个实现RowMapper接口的类,
         * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
         */
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper());
        PersonBean pb = null;
        if(pbs.size()>0) {
            pb = pbs.get(0);
        }
        return pb;
    }

    @SuppressWarnings("unchecked")
    public List<PersonBean> queryPersons() {
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper());
        return pbs;
    }

    public void updatePerson(PersonBean person) {
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});
    }
}



在addPerson方法中我们抛出了一个运行期例外,以此来检查Spring的事务管理.

编写测试类运行:
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.     }  
  12. }  

package com.royzhou.jdbc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestJdbcTemplate {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
        PersonService ps = (PersonService)ctx.getBean("personService");
        ps.addPerson(new PersonBean("royzhou"));
    }
}


运行测试类,后台输出异常:java.lang.RuntimeException: 运行期例外
查看数据库发现数据没有插入,说明事务进行了回滚.

再次测试修改为抛出checked Exception
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 void addPerson(PersonBean person) throws Exception {  
  26.         /**
  27.          * 第一个参数为执行sql
  28.          * 第二个参数为参数数据
  29.          * 第三个参数为参数类型
  30.          */  
  31.         jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});  
  32.         throw new Exception("checked 例外");  
  33.     }  
  34.  
  35.     public void deletePerson(int id) {  
  36.         jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER});  
  37.     }  
  38.  
  39.     @SuppressWarnings("unchecked")  
  40.     public PersonBean queryPerson(int id) {  
  41.         /**
  42.          * new PersonRowMapper()是一个实现RowMapper接口的类,
  43.          * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
  44.          */  
  45.         List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper());  
  46.         PersonBean pb = null;  
  47.         if(pbs.size()>0) {  
  48.             pb = pbs.get(0);  
  49.         }  
  50.         return pb;  
  51.     }  
  52.  
  53.     @SuppressWarnings("unchecked")  
  54.     public List<PersonBean> queryPersons() {  
  55.         List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper());  
  56.         return pbs;  
  57.     }  
  58.  
  59.     public void updatePerson(PersonBean person) {  
  60.         jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});  
  61.     }  
  62. }  

package com.royzhou.jdbc;

import java.util.List;

import javax.sql.DataSource;
import java.sql.Types;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

@Transactional
public class PersonServiceImpl implements PersonService {

    private JdbcTemplate jdbcTemplate;
    
    /**
     * 通过Spring容器注入datasource
     * 实例化JdbcTemplate,该类为主要操作数据库的类
     * @param ds
     */
    public void setDataSource(DataSource ds) {
        this.jdbcTemplate = new JdbcTemplate(ds);
    }
    
    public void addPerson(PersonBean person) throws Exception {
        /**
         * 第一个参数为执行sql
         * 第二个参数为参数数据
         * 第三个参数为参数类型
         */
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});
        throw new Exception("checked 例外");
    }

    public void deletePerson(int id) {
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER});
    }

    @SuppressWarnings("unchecked")
    public PersonBean queryPerson(int id) {
        /**
         * new PersonRowMapper()是一个实现RowMapper接口的类,
         * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
         */
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper());
        PersonBean pb = null;
        if(pbs.size()>0) {
            pb = pbs.get(0);
        }
        return pb;
    }

    @SuppressWarnings("unchecked")
    public List<PersonBean> queryPersons() {
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper());
        return pbs;
    }

    public void updatePerson(PersonBean person) {
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});
    }
}


后台输出异常:java.lang.Exception: checked 例外
查看数据库发现数据插入,说明事务没有进行了回滚.

说明了Spring的事务支持默认只对运行期异常(RuntimeException)进行回滚,这里可能有个疑问,我们执行sql操作的时候会发生sql异常,不属于运行期异常,那Spring是怎么进行事务回滚的呢 ????
查看了一下JdbcTemplate的源代码发现,JdbcTemplate的处理方法如下:
Java代码

   1. public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {  
   2.     if (logger.isDebugEnabled()) {  
   3.         logger.debug("Executing SQL batch update [" + sql + "]");  
   4.     }  
   5.     return (int[]) execute(sql, new PreparedStatementCallback() {  
   6.         public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {  
   7.             try {  
   8.                 int batchSize = pss.getBatchSize();  
   9.                 if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) {  
  10.                     for (int i = 0; i < batchSize; i++) {  
  11.                         pss.setValues(ps, i);  
  12.                         ps.addBatch();  
  13.                     }  
  14.                     return ps.executeBatch();  
  15.                 }  
  16.                 else {  
  17.                     int[] rowsAffected = new int[batchSize];  
  18.                     for (int i = 0; i < batchSize; i++) {  
  19.                         pss.setValues(ps, i);  
  20.                         rowsAffected = ps.executeUpdate();  
  21.                     }  
  22.                     return rowsAffected;  
  23.                 }  
  24.             }  
  25.             finally {  
  26.                 if (pss instanceof ParameterDisposer) {  
  27.                     ((ParameterDisposer) pss).cleanupParameters();  
  28.                 }  
  29.             }  
  30.         }  
  31.     });  
  32. }  

    public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {
        if (logger.isDebugEnabled()) {
            logger.debug("Executing SQL batch update [" + sql + "]");
        }
        return (int[]) execute(sql, new PreparedStatementCallback() {
            public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
                try {
                    int batchSize = pss.getBatchSize();
                    if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
                        for (int i = 0; i < batchSize; i++) {
                            pss.setValues(ps, i);
                            ps.addBatch();
                        }
                        return ps.executeBatch();
                    }
                    else {
                        int[] rowsAffected = new int[batchSize];
                        for (int i = 0; i < batchSize; i++) {
                            pss.setValues(ps, i);
                            rowsAffected = ps.executeUpdate();
                        }
                        return rowsAffected;
                    }
                }
                finally {
                    if (pss instanceof ParameterDisposer) {
                        ((ParameterDisposer) pss).cleanupParameters();
                    }
                }
            }
        });
    }



在 代码中捕获了SQLException然后抛出一个 org.springframework.dao.DataAcceddException,该异常继承自 org.springframework.core.NestedRuntimeException,NestedRuntimeException
是 一个继承自RuntimeException的抽象类,Spring jdbcTemplate处理发生异常处理后抛出来得异常基本上都会继承NestedRuntimeException,看完之后才确信了Spring默 认只对RuntimeException进行回滚

当然我们可可以修改Spring的默认配置,当发生RuntimeException我们也可以不让他进行事务回滚
只需要加上一个@Transactional(noRollbackFor=RuntimeException.class)
注意@Transactional只能针对public属性范围内的方法添加
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.     @Transactional(noRollbackFor=RuntimeException.class)  
  26.     public void addPerson(PersonBean person) {  
  27.         /**
  28.          * 第一个参数为执行sql
  29.          * 第二个参数为参数数据
  30.          * 第三个参数为参数类型
  31.          */  
  32.         jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});  
  33.         throw new RuntimeException("运行期例外");  
  34.     }  
  35.  
  36.     public void deletePerson(int id) {  
  37.         jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER});  
  38.     }  
  39.  
  40.     @SuppressWarnings("unchecked")  
  41.     public PersonBean queryPerson(int id) {  
  42.         /**
  43.          * new PersonRowMapper()是一个实现RowMapper接口的类,
  44.          * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
  45.          */  
  46.         List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper());  
  47.         PersonBean pb = null;  
  48.         if(pbs.size()>0) {  
  49.             pb = pbs.get(0);  
  50.         }  
  51.         return pb;  
  52.     }  
  53.  
  54.     @SuppressWarnings("unchecked")  
  55.     public List<PersonBean> queryPersons() {  
  56.         List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper());  
  57.         return pbs;  
  58.     }  
  59.  
  60.     public void updatePerson(PersonBean person) {  
  61.         jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});  
  62.     }  
  63. }  

package com.royzhou.jdbc;

import java.util.List;

import javax.sql.DataSource;
import java.sql.Types;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

@Transactional
public class PersonServiceImpl implements PersonService {

    private JdbcTemplate jdbcTemplate;
    
    /**
     * 通过Spring容器注入datasource
     * 实例化JdbcTemplate,该类为主要操作数据库的类
     * @param ds
     */
    public void setDataSource(DataSource ds) {
        this.jdbcTemplate = new JdbcTemplate(ds);
    }
    
    @Transactional(noRollbackFor=RuntimeException.class)
    public void addPerson(PersonBean person) {
        /**
         * 第一个参数为执行sql
         * 第二个参数为参数数据
         * 第三个参数为参数类型
         */
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});
        throw new RuntimeException("运行期例外");
    }

    public void deletePerson(int id) {
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER});
    }

    @SuppressWarnings("unchecked")
    public PersonBean queryPerson(int id) {
        /**
         * new PersonRowMapper()是一个实现RowMapper接口的类,
         * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
         */
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper());
        PersonBean pb = null;
        if(pbs.size()>0) {
            pb = pbs.get(0);
        }
        return pb;
    }

    @SuppressWarnings("unchecked")
    public List<PersonBean> queryPersons() {
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper());
        return pbs;
    }

    public void updatePerson(PersonBean person) {
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});
    }
}



运行测试类:
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.     }  
  12. }  

package com.royzhou.jdbc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestJdbcTemplate {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
        PersonService ps = (PersonService)ctx.getBean("personService");
        ps.addPerson(new PersonBean("royzhou"));
    }
}



后台抛出异常,查看数据库,记录插入进去了,说明我们配置事务不对RuntimeException回滚生效了.
既然可以配置不对RuntimeException回滚,那我们也可以配置对Exception进行回滚,主要用到的是
@Transactional(rollbackFor=Exception.class)

对于一些查询工作,因为不需要配置事务支持,我们配置事务的传播属性:
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

readOnly=true表示事务中不允许存在更新操作.

关于事务的传播属性有下面几种配置:
REQUIRED:业务方法需要在一个事务中运行,如果方法运行时,已经处于一个事务中,那么加入到该事务中,否则自己创建一个新的事务.(Spring默认的事务传播属性)
NOT_SUPPORTED:声明方法不需要事务,如果方法没有关联到一个事务,容器不会为它开启事务,如果方法在一个事务中被调用,该事务被挂起,在方法调用结束后,原先的事务便会恢复执行
REQUIRESNEW:不管是否存在事务,业务方法总会为自己发起一个新的事务,如果方法运行时已经存在一个事务,则该事务会被挂起,新的事务被创建,知道方法执行结束,新事务才结束,原先的事务才恢复执行.
MANDATORY:指定业务方法只能在一个已经存在的事务中执行,业务方法不能自己发起事务,如果业务方法没有在事务的环境下调用,则容器会抛出异常
SUPPORTS:如果业务方法在事务中被调用,则成为事务中的一部分,如果没有在事务中调用,则在没有事务的环境下执行
NEVER:指定业务方法绝对不能在事务范围内运行,否则会抛出异常.
NESTED:如果业务方法运行时已经存在一个事务,则新建一个嵌套的事务,该事务可以有多个回滚点,如果没有事务,则按REQUIRED属性执行. 注意:业务方法内部事务的回滚不会对外部事务造成影响,但是外部事务的回滚会影响内部事务


关于使用注解的方式来配置事务就到这里,
我们还可以使用另外一种方式实现事务的管理,通过xml文件的配置,主要通过AOP技术实现:

首先把我们的业务类的注解去掉:
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.         throw new RuntimeException("运行期例外");  
  31.     }  
  32.  
  33.     public void deletePerson(int id) {  
  34.         jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER});  
  35.     }  
  36.      
  37.     @SuppressWarnings("unchecked")  
  38.     public PersonBean queryPerson(int id) {  
  39.         /**
  40.          * new PersonRowMapper()是一个实现RowMapper接口的类,
  41.          * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
  42.          */  
  43.         List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper());  
  44.         PersonBean pb = null;  
  45.         if(pbs.size()>0) {  
  46.             pb = pbs.get(0);  
  47.         }  
  48.         return pb;  
  49.     }  
  50.  
  51.     @SuppressWarnings("unchecked")  
  52.     public List<PersonBean> queryPersons() {  
  53.         List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper());  
  54.         return pbs;  
  55.     }  
  56.  
  57.     public void updatePerson(PersonBean person) {  
  58.         jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});  
  59.     }  
  60. }  

package com.royzhou.jdbc;

import java.util.List;

import javax.sql.DataSource;
import java.sql.Types;

import org.springframework.jdbc.core.JdbcTemplate;

public class PersonServiceImpl implements PersonService {

    private JdbcTemplate jdbcTemplate;
    
    /**
     * 通过Spring容器注入datasource
     * 实例化JdbcTemplate,该类为主要操作数据库的类
     * @param ds
     */
    public void setDataSource(DataSource ds) {
        this.jdbcTemplate = new JdbcTemplate(ds);
    }
    
    public void addPerson(PersonBean person) {
        /**
         * 第一个参数为执行sql
         * 第二个参数为参数数据
         * 第三个参数为参数类型
         */
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});
        throw new RuntimeException("运行期例外");
    }

    public void deletePerson(int id) {
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER});
    }
    
    @SuppressWarnings("unchecked")
    public PersonBean queryPerson(int id) {
        /**
         * new PersonRowMapper()是一个实现RowMapper接口的类,
         * 执行回调,实现mapRow()方法将rs对象转换成PersonBean对象返回
         */
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper());
        PersonBean pb = null;
        if(pbs.size()>0) {
            pb = pbs.get(0);
        }
        return pb;
    }

    @SuppressWarnings("unchecked")
    public List<PersonBean> queryPersons() {
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper());
        return pbs;
    }

    public void updatePerson(PersonBean person) {
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});
    }
}



然后我们需要在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.     <!-- 定义事务传播属性 -->  
  42.     <tx:advice id="txAdvice" transaction-manager="txManager">  
  43.         <tx:attributes>  
  44.             <tx:method name="query*" propagation="NOT_SUPPORTED" read-only="true"/>  
  45.             <tx:method name="*" propagation="REQUIRED"/>  
  46.         </tx:attributes>  
  47.     </tx:advice>  
  48.      
  49.     <aop:config>  
  50.         <aop:pointcut id="transactionPointCut" expression="execution(* com.royzhou.jdbc..*.*(..))"/>  
  51.         <aop:advisor pointcut-ref="transactionPointCut" advice-ref="txAdvice"/>  
  52.     </aop:config>  
  53. </beans>  

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd ">

    <context:property-placeholder location="classpath:jdbc.properties" />
    <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="${driverClassName}" />
        <property name="url" value="${url}" />
        <property name="username" value="${username}" />
        <property name="password" value="${password}" />
        <!-- 连接池启动时的初始值 -->
        <property name="initialSize" value="${initialSize}" />
        <!-- 连接池的最大值 -->
        <property name="maxActive" value="${maxActive}" />
        <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
        <property name="maxIdle" value="${maxIdle}" />
        <!--  最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
        <property name="minIdle" value="${minIdle}" />
    </bean>

    <bean id="personService"
        class="com.royzhou.jdbc.PersonServiceImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="txManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!-- 定义事务传播属性 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="query*" propagation="NOT_SUPPORTED" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:pointcut id="transactionPointCut" expression="execution(* com.royzhou.jdbc..*.*(..))"/>
        <aop:advisor pointcut-ref="transactionPointCut" advice-ref="txAdvice"/>
    </aop:config>
</beans>



从新运行测试类
后台抛出RuntimeException异常,查看数据库,没有插入数据,说明事务进行回滚,XML方式的配置也生效了.


另外还有一个编程式的事务处理,但是它有些侵入性。通常我们的事务需求并没有要求在事务的边界上进行如此精确的控制。
我们一般采用"声明式事务"。

总结一下:
事务是企业应用开发的重要组成部分,他使软件更加可靠。它们确保一种要么全有要么全无的行为,防止数据不一致而导致的不可预测的错误发生。
它们同时也支持并发,防止并发应用线程在操作同一数据时互相影响。以前我们写Jdbc代码的时候,可能需要自己手动去开启事务,然后方法执行结束之后
再去提交事务,全部都嵌套在我们的业务代码之中,具有很强的侵入性....
使用Spring提供事务管理机制,我们只需要配置XML或使用Annotion进行注解就可以实现事务的管理和配置,减少了代码之间的耦合,配置也很方便,很大程度上提升了我们的开发效率.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值