Spring JdbcTemplate 与 事务管理 学习

时间:2022-09-04 19:10:10

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

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

接口类: 
PersonService.java

  1. package com.royzhou.jdbc;
  2. import java.util.List;
  3. public interface PersonService {
  4. public void addPerson(PersonBean person);
  5. public void updatePerson(PersonBean person);
  6. public void deletePerson(int id);
  7. public PersonBean queryPerson(int id);
  8. public List<PersonBean> queryPersons();
  9. }

实现类: 
PersonServiceImpl.java

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

PersonRowMapper.java

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

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

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

jdbc.properties

  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

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

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

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

  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. public Object queryForObject(String sql, Object[] args, RowMapper rowMapper) throws DataAccessException {
  6. List results = (List) query(sql, args, new RowMapperResultSetExtractor(rowMapper, 1));
  7. return DataAccessUtils.requiredUniqueResult(results);
  8. }
  9. public Object queryForObject(String sql, RowMapper rowMapper) throws DataAccessException {
  10. List results = query(sql, rowMapper);
  11. return DataAccessUtils.requiredUniqueResult(results);
  12. }
  13. public static Object requiredUniqueResult(Collection results) throws IncorrectResultSizeDataAccessException {
  14. int size = (results != null ? results.size() : 0);
  15. if (size == 0) {
  16. throw new EmptyResultDataAccessException(1); // 问题在这里
  17. }
  18. if (!CollectionUtils.hasUniqueObject(results)) {
  19. throw new IncorrectResultSizeDataAccessException(1, size);
  20. }
  21. return results.iterator().next();
  22. }

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

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

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

再次运行测试类,输出: 
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中添加事务支持:

  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. <context:property-placeholder location="classpath:jdbc.properties" />
  13. <bean id="dataSource"
  14. class="org.apache.commons.dbcp.BasicDataSource"
  15. destroy-method="close">
  16. <property name="driverClassName" value="${driverClassName}" />
  17. <property name="url" value="${url}" />
  18. <property name="username" value="${username}" />
  19. <property name="password" value="${password}" />
  20. <!-- 连接池启动时的初始值 -->
  21. <property name="initialSize" value="${initialSize}" />
  22. <!-- 连接池的最大值 -->
  23. <property name="maxActive" value="${maxActive}" />
  24. <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
  25. <property name="maxIdle" value="${maxIdle}" />
  26. <!--  最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
  27. <property name="minIdle" value="${minIdle}" />
  28. </bean>
  29. <bean id="personService"
  30. class="com.royzhou.jdbc.PersonServiceImpl">
  31. <property name="dataSource" ref="dataSource"></property>
  32. </bean>
  33. <bean id="txManager"
  34. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  35. <property name="dataSource" ref="dataSource" />
  36. </bean>
  37. <tx:annotation-driven transaction-manager="txManager" />
  38. </beans>

<tx:annotation-driven transaction-manager="txManager" />  这句话的作用是注册事务注解处理器 
定义好配置文件后我们只需要在我们的类上加上注解@Transactional,就可以指定这个类需要受Spring的事务管理 
默认Spring为每个方法开启一个事务,如果方法发生运行期错误unchecked(RuntimeException),事务会进行回滚 
如果发生checked Exception,事务不进行回滚.

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

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

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

编写测试类运行:

  1. package com.royzhou.jdbc;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class TestJdbcTemplate {
  5. public static void main(String[] args) {
  6. ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
  7. PersonService ps = (PersonService)ctx.getBean("personService");
  8. ps.addPerson(new PersonBean("royzhou"));
  9. }
  10. }

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

再次测试修改为抛出checked Exception

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

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

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

  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[i] = ps.executeUpdate();
  21. }
  22. return rowsAffected;
  23. }
  24. }
  25. finally {
  26. if (pss instanceof ParameterDisposer) {
  27. ((ParameterDisposer) pss).cleanupParameters();
  28. }
  29. }
  30. }
  31. });
  32. }

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

当然我们可可以修改Spring的默认配置,当发生RuntimeException我们也可以不让他进行事务回滚 
只需要加上一个@Transactional(noRollbackFor=RuntimeException.class) 
注意@Transactional只能针对public属性范围内的方法添加

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

运行测试类:

  1. package com.royzhou.jdbc;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class TestJdbcTemplate {
  5. public static void main(String[] args) {
  6. ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
  7. PersonService ps = (PersonService)ctx.getBean("personService");
  8. ps.addPerson(new PersonBean("royzhou"));
  9. }
  10. }

后台抛出异常,查看数据库,记录插入进去了,说明我们配置事务不对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技术实现:

首先把我们的业务类的注解去掉:

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

然后我们需要在bean.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. <context:property-placeholder location="classpath:jdbc.properties" />
  13. <bean id="dataSource"
  14. class="org.apache.commons.dbcp.BasicDataSource"
  15. destroy-method="close">
  16. <property name="driverClassName" value="${driverClassName}" />
  17. <property name="url" value="${url}" />
  18. <property name="username" value="${username}" />
  19. <property name="password" value="${password}" />
  20. <!-- 连接池启动时的初始值 -->
  21. <property name="initialSize" value="${initialSize}" />
  22. <!-- 连接池的最大值 -->
  23. <property name="maxActive" value="${maxActive}" />
  24. <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
  25. <property name="maxIdle" value="${maxIdle}" />
  26. <!--  最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
  27. <property name="minIdle" value="${minIdle}" />
  28. </bean>
  29. <bean id="personService"
  30. class="com.royzhou.jdbc.PersonServiceImpl">
  31. <property name="dataSource" ref="dataSource"></property>
  32. </bean>
  33. <bean id="txManager"
  34. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  35. <property name="dataSource" ref="dataSource" />
  36. </bean>
  37. <!-- 定义事务传播属性 -->
  38. <tx:advice id="txAdvice" transaction-manager="txManager">
  39. <tx:attributes>
  40. <tx:method name="query*" propagation="NOT_SUPPORTED" read-only="true"/>
  41. <tx:method name="*" propagation="REQUIRED"/>
  42. </tx:attributes>
  43. </tx:advice>
  44. <aop:config>
  45. <aop:pointcut id="transactionPointCut" expression="execution(* com.royzhou.jdbc..*.*(..))"/>
  46. <aop:advisor pointcut-ref="transactionPointCut" advice-ref="txAdvice"/>
  47. </aop:config>
  48. </beans>

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

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

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

Spring JdbcTemplate 与 事务管理 学习的更多相关文章

  1. spring事务管理学习

    spring事务管理学习 spring的事务管理和mysql自己的事务之间的区别 参考很好介绍事务异常回滚的文章 MyBatis+Spring 事务管理 spring中的事务回滚例子 这篇文章讲解了@ ...

  2. spring深入学习&lpar;五&rpar;-----spring dao、事务管理

    访问数据库基本是所有java web项目必备的,不论是oracle.mysql,或者是nosql,肯定需要和数据库打交道.一开始学java的时候,肯定是以jdbc为基础,如下: private sta ...

  3. Spring入门6事务管理2 基于Annotation方式的声明式事务管理机制

    Spring入门6事务管理2 基于Annotation方式的声明式事务管理机制 201311.27 代码下载 链接: http://pan.baidu.com/s/1kYc6c 密码: 233t 前言 ...

  4. Spring入门5&period;事务管理机制

    Spring入门5.事务管理机制 20131126 代码下载 : 链接: http://pan.baidu.com/s/1kYc6c 密码: 233t 回顾之前的知识,Spring 最为核心的两个部分 ...

  5. (转)使用Spring配置文件实现事务管理

    http://blog.csdn.net/yerenyuan_pku/article/details/52886207 前面我们讲解了使用Spring注解方式来管理事务,现在我们就来学习使用Sprin ...

  6. Spring中的事务管理

    事务简介: 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性 事务就是一系列的动作,它们被当作一个单独的工作单元.这些动作要么全部完成,要么全部不起作用 事务的四个关键属性( ...

  7. Spring中的事务管理详解

    在这里主要介绍Spring对事务管理的一些理论知识,实战方面参考上一篇博文: http://www.cnblogs.com/longshiyVip/p/5061547.html 1. 事务简介: 事务 ...

  8. XML方式实现Spring声明式事务管理

    1.首先编写一个实体类 public class Dept { private int deptId; private String deptName; public int getDeptId() ...

  9. 框架应用:Spring framework &lpar;四&rpar; - 事务管理

    事务控制 事务是什么?事务控制? 事务这个词最早是在数据库中进行应用,讲的用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位. 事务的管理是指一个事务的开启,内容添加, ...

随机推荐

  1. 2016huasacm暑假集训训练五 C-Common Subsequence

    题目链接:http://acm.hust.edu.cn/vjudge/contest/126708#problem/C 题意:这是一道求字符串的公共子串的最大长度的题目,用dp动态方程即可 if(a[ ...

  2. Jenkins部署到远程(Linux服务器)

    接着上次的说,上次只是实现了本地自动化部署,这种情况只是针对开发环境和部署环境在同一台机器时适用.不过,一般情况下,我们都会要把项目部署到远程Linux服务器上,所以这节的主要内容是: 1.部署开发环 ...

  3. 常用Maven插件介绍

    我们都知道Maven本质上是一个插件框架,它的核心并不执行任何具体的构建任务,所有这些任务都交给插件来完成,例如编译源代码是由maven- compiler-plugin完成的.进一步说,每个任务对应 ...

  4. 树莓派 HC-SRO4超声波测距模块的使用

    先上个图 这个模块的针脚跟之前玩的那三个有所区别,除了VCC和GND两个针脚,还多了两个Trig和Echo针脚,分别是输出和输入,Trig我接的是20针脚,Echo是21 该模块的工作原理为,先向TR ...

  5. python&lowbar;Opencv&lowbar;opencv2&period;4&period;4&plus;python配置问题

    下载numpy-1.8.1-win32-superpack-python2.7.exe.下载地址微博微盘:http://vdisk.weibo.com/s/aJcp4pI6mYEXg 必须安装nump ...

  6. 【SQL】查询语句中in和exists的区别

    in in可以分为三类: 一. 形如select * from t1 where f1 in ( &apos:a &apos:, &apos:b &apos:),应该和 ...

  7. 计算机学院大学生程序设计竞赛(2015’12) 1009 The Magic Tower

    #include<cmath> #include<cstdio> #include<cstring> #include<algorithm> using ...

  8. C&num;多功能DataGridView打印类&lpar;WinForm&rpar;

    ;                printPreviewDialog.ShowDialog();            }            catch            {         ...

  9. 16&period;0-uC&sol;OS-III同步

    同步 uC/OS-III中用于同步的两种机制:信号量和事件标志组 . 1.信号量 信号量最初用于控制共享资源的访问.信号量可用于ISR与任务间.任务与任务间的同步. “ N”表示信号量可以被累计.初始 ...

  10. 学习windows编程 day4 之视口和窗口

    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRU ...