【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】
一、JDBC编程特点
静态代码+动态变量=JDBC编程。
静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口。
动态变量:用户名、密码、连接的数据库、表名、SQL语句等信息。
在spring中动态变量能够通过注入的形式给予。这样的变成方式适合包装成模板。静态代码构成了模板,而动态变量是需要传入的参数。
二、核心类JdbcTemplate
1.基于模板的设置。
2.完成了资源的创建和释放的工作。
3.简化了我们的JDBC操作。
4.完成了对JDBC的核心流程的工作,包括SQL语句的创建和执行。
5.仅仅需要传递DataSource就可以将其实例化。
6.JdbcTemplate只需要创建一次。
7.JdbcTemplate是线程安全类。
三、使用三种方式将DataSource对象传递给JdbcTemplate,创建JdbcTemplate对象。
1.第一种方法:继承org.springframework.jdbc.core.support.JdbcDaoSupport类
(1)首先配置数据源DataSource,这里使用c3p0数据库连接池
1 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 2 <constructor-arg index="0" value="namedconfig"></constructor-arg> 3 </bean>
1 <?xml version="1.0" encoding="UTF-8"?> 2 <c3p0-config> 3 <!-- 默认配置,只可以出现一次 --> 4 <default-config> 5 <!-- 连接超时设置30秒 --> 6 <property name="checkoutTimeout">30000</property> 7 <!-- 30秒检查一次connection的空闲 --> 8 <property name="idleConnectionTestPeriod">30</property> 9 <!--初始化的池大小 --> 10 <property name="initialPoolSize">2</property> 11 <!-- 最多的一个connection空闲时间 --> 12 <property name="maxIdleTime">30</property> 13 <!-- 最多可以有多少个连接connection --> 14 <property name="maxPoolSize">10</property> 15 <!-- 最少的池中有几个连接 --> 16 <property name="minPoolSize">2</property> 17 <!-- 批处理的语句--> 18 <property name="maxStatements">50</property> 19 <!-- 每次增长几个连接 --> 20 <property name="acquireIncrement">3</property> 21 <property name="driverClass">com.mysql.jdbc.Driver</property> 22 <property name="jdbcUrl"> 23 <![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]> 24 </property> 25 <property name="user">root</property> 26 <property name="password">5a6f38</property> 27 </default-config> 28 29 <named-config name="namedconfig"> 30 <!-- 连接超时设置30秒 --> 31 <property name="checkoutTimeout">30000</property> 32 <!-- 30秒检查一次connection的空闲 --> 33 <property name="idleConnectionTestPeriod">30</property> 34 <!--初始化的池大小 --> 35 <property name="initialPoolSize">2</property> 36 <!-- 最多的一个connection空闲时间 --> 37 <property name="maxIdleTime">30</property> 38 <!-- 最多可以有多少个连接connection --> 39 <property name="maxPoolSize">2</property> 40 <!-- 最少的池中有几个连接 --> 41 <property name="minPoolSize">2</property> 42 <!-- 批处理的语句--> 43 <property name="maxStatements">50</property> 44 <!-- 每次增长几个连接 --> 45 <property name="acquireIncrement">2</property> 46 <property name="driverClass">com.mysql.jdbc.Driver</property> 47 <property name="jdbcUrl"> 48 <![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]> 49 </property> 50 <property name="user">root</property> 51 <property name="password">5a6f38</property> 52 </named-config> 53 </c3p0-config>
(2)定义Dao
1 package com.kdyzm.spring.jdbc; 2 3 public interface CourseDao { 4 public Course getCourse(Long cid); 5 }
(3)实现Dao,这是非常关键的一步,因为需要继承JdbcDaoSupport类。
1 package com.kdyzm.spring.jdbc; 2 3 import java.sql.SQLException; 4 5 import org.apache.commons.dbutils.QueryRunner; 6 import org.apache.commons.dbutils.handlers.BeanHandler; 7 import org.springframework.jdbc.core.support.JdbcDaoSupport; 8 9 public class CourseDaoImpl extends JdbcDaoSupport implements CourseDao{ 10 @Override 11 public Course getCourse(Long cid) { 12 String sql="select cid,cname from course where cid = ?"; 13 QueryRunner run=new QueryRunner(this.getDataSource()); 14 Course course=null; 15 try { 16 course = run.query(sql,new BeanHandler<Course>(Course.class),1L); 17 } catch (SQLException e) { 18 e.printStackTrace(); 19 } 20 return course; 21 } 22 }
(4)在applicationContext.xml文件中进行配置,注意这里使用proerty标签相当于调用了JdbcDaoSupport的setDataSource方法。
<!-- 第一种方法:继承JdbcDaoSupport --> <bean id="courseDao" class="com.kdyzm.spring.jdbc.CourseDaoImpl"> <property name="dataSource"> <ref bean="dataSource"/> </property> </bean>
(5)测试代码:
1 ApplicationContext context=new ClassPathXmlApplicationContext("com/kdyzm/spring/jdbc/applicationContext.xml"); 2 CourseDao courseDao = (CourseDao) context.getBean("courseDao"); 3 Course course=courseDao.getCourse(1L); 4 System.out.println(course);
1 package com.kdyzm.spring.jdbc; 2 /* 3 * 课程类 4 */ 5 public class Course { 6 private Long cid; 7 private String cname; 8 9 public Course() { 10 } 11 @Override 12 public String toString() { 13 return "Course [cid=" + cid + ", cname=" + cname + "]"; 14 } 15 public Long getCid() { 16 return cid; 17 } 18 public void setCid(Long cid) { 19 this.cid = cid; 20 } 21 public String getCname() { 22 return cname; 23 } 24 public void setCname(String cname) { 25 this.cname = cname; 26 } 27 }
测试结果:
分析:继承了JdbcDaoSupport类为什么是关键?JdbcDaoSupport类中有一个非常关键的方法setDataSource,它有一个JdbcTemplate类型的成员变量,我们通过使用该成员变量来完成所有工作:
通过上面的流程图可以看到实际上JdbcDaoSupport将DataSource的引用传递给了JdbcTemplate,JdbcTemplate对象使用该DataSource对象完成对数据库的增删查改操作。
2.第二种方法:使用JdbcTemplate对象作为成员变量
(1)配置数据源
<!-- 引入DataSource --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <constructor-arg index="0" value="namedconfig"></constructor-arg> </bean>
(2)创建CourseDaoImpl1类,该类不再继承JdbcDaoSupport类,只是实现了CourseDao接口,但是新增加了成员变量JdbcTemplate jdbcTemplate,并提供了set、get方法。
1 package com.kdyzm.spring.jdbc; 2 /* 3 * 第二种实现方式:使用JdbcTemplate类作为成员变量 4 */ 5 import java.sql.SQLException; 6 7 import org.apache.commons.dbutils.QueryRunner; 8 import org.apache.commons.dbutils.handlers.BeanHandler; 9 import org.springframework.jdbc.core.JdbcTemplate; 10 11 public class CourseDaoImpl1 implements CourseDao{ 12 13 JdbcTemplate jdbcTemplate=null; 14 15 public JdbcTemplate getJdbcTemplate() { 16 return jdbcTemplate; 17 } 18 19 public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { 20 this.jdbcTemplate = jdbcTemplate; 21 } 22 23 public Course getCourse(Long cid) { 24 String sql="select cid,cname from course where cid = ?"; 25 QueryRunner run=new QueryRunner(this.getJdbcTemplate().getDataSource()); 26 Course course=null; 27 try { 28 course = run.query(sql,new BeanHandler<Course>(Course.class),cid); 29 } catch (SQLException e) { 30 e.printStackTrace(); 31 } 32 return course; 33 } 34 }
(3)配置applicationContext.xml文件
* 将JdbcTemplate对象纳入Spring容器管理
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg index="0"> <ref bean="dataSource"/> </constructor-arg> </bean>
* 将courseDao1纳入Spring容器管理
<bean id="courseDao1" class="com.kdyzm.spring.jdbc.CourseDaoImpl1"> <property name="jdbcTemplate"> <ref bean="jdbcTemplate"/> </property> </bean>
原理分析:实际上JdbcDaoSupport类的核心就是JdbcTemplate,同理,我们也可以将该类对象作为成员变量,效果是相同的。
3.第三种方式:继承JdbcTemplate类。
(1)配置数据源,同上
(2)新建CourseDaoImpl2,该类继承了JdbcTemplate类,并且实现了CourseDao接口。
1 package com.kdyzm.spring.jdbc; 2 /* 3 * 第二种实现方式:使用JdbcTemplate类作为成员变量 4 */ 5 import java.sql.SQLException; 6 7 import javax.sql.DataSource; 8 9 import org.apache.commons.dbutils.QueryRunner; 10 import org.apache.commons.dbutils.handlers.BeanHandler; 11 import org.springframework.jdbc.core.JdbcTemplate; 12 13 public class CourseDaoImpl2 extends JdbcTemplate implements CourseDao{ 14 public CourseDaoImpl2(DataSource dataSource) { 15 super(dataSource); 16 } 17 18 public Course getCourse(Long cid) { 19 String sql="select cid,cname from course where cid = ?"; 20 QueryRunner run=new QueryRunner(this.getDataSource()); 21 Course course=null; 22 try { 23 course = run.query(sql,new BeanHandler<Course>(Course.class),cid); 24 } catch (SQLException e) { 25 e.printStackTrace(); 26 } 27 return course; 28 } 29 }
(3)配置applicationContext.xml文件
<!-- 第三种方法:继承JdbcTemplate类 --> <bean id="courseDao2" class="com.kdyzm.spring.jdbc.CourseDaoImpl2"> <constructor-arg index="0"> <ref bean="dataSource"/> </constructor-arg> </bean>
测试略。原理分析:最根本的就是JdbcTemplate类,所以干脆继承该类。
四、spring中的事务管理
1.Spring的事务管理是声明式的事务管理。什么是"声明式"的事务管理?在配置文件中声明某个方法需要开启事务进行管理,然后被声明的这个方法就会开启事务。
Spring事务管理相对于Hibernate和JDBC的优越之处:
2.Spring的事务管理器
spring没有直接管理事务,而是将管理事务的责任委托给JTA或者相应的持久性机制所提供的某个特定平台的事务实现。
事务管理器 |
目标 |
org.springframework.transaction.PlatformTransactionManager |
事务管理器的顶级接口 |
org.springframework.transaction.support.AbstractPlatformTransactionManager |
事务管理器的超类 |
org.springframework.jdbc.datasource.DataSourceTransactionManager |
在单一的JDBC DataSource中的事务管理 |
org.springframework.orm.hibernate3.HibernateTransactionManager |
当持久化机制是hibernate的时候,使用它来管理事务 |
org.springframework.orm.jdo.JdoTransactionManager |
使用一个JTA实现来管理事务,当一个事务跨越多个资源的时候必须使用 |
3.Spring事务的传播属性
在实际工作当中,99%的使用过的是REQUIRED。
4.spring事务的隔离级别
在实际工作中,99%使用的是DEFAULT。
5.XML配置练习
(1)引入命名空间
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 8 xsi:schemaLocation=" 9 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 10 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd 11 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd 12 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd 13 "> 14 15 </beans>
(2)user表:
CREATE TABLE `user` ( `id` varchar(32) NOT NULL, `name` varchar(32) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
(3)新建类:
1 package com.kdyzm.spring.jdbc.transaction1; 2 3 public class User { 4 private String id; 5 private String name; 6 private Integer age; 7 public User() { 8 } 9 public String getId() { 10 return id; 11 } 12 public void setId(String id) { 13 this.id = id; 14 } 15 public String getName() { 16 return name; 17 } 18 public void setName(String name) { 19 this.name = name; 20 } 21 public Integer getAge() { 22 return age; 23 } 24 public void setAge(Integer age) { 25 this.age = age; 26 } 27 @Override 28 public String toString() { 29 return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; 30 } 31 }
1 package com.kdyzm.spring.jdbc.transaction1; 2 3 import java.sql.SQLException; 4 5 public interface UserDao { 6 public User updateUser(User user) throws SQLException; 7 }
非常重要的一个类:com.kdyzm.spring.jdbc.transaction1.UserDaoImpl
1 package com.kdyzm.spring.jdbc.transaction1; 2 3 import java.sql.SQLException; 4 5 import org.apache.commons.dbutils.QueryRunner; 6 import org.springframework.jdbc.core.JdbcTemplate; 7 8 public class UserDaoImpl implements UserDao{ 9 private JdbcTemplate jdbcTemplate; 10 11 public JdbcTemplate getJdbcTemplate() { 12 return jdbcTemplate; 13 } 14 15 public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { 16 this.jdbcTemplate = jdbcTemplate; 17 } 18 19 /** 20 * 使用该方法测试非跨dao事务回滚问题 21 * 一定不能使用dbutils工具,否则不能回滚!!!!!!!!!!!!! 22 * @throws SQLException 23 */ 24 @Override 25 public User updateUser(User user) throws SQLException { 26 String sql="update user set age=age+1 where id='0001'"; 27 // QueryRunner runner=new QueryRunner(this.jdbcTemplate.getDataSource()); 28 // runner.update(this.jdbcTemplate.getDataSource().getConnection(),sql,"0001"); 29 this.jdbcTemplate.execute(sql); 30 int a=1/0; 31 sql="update user set age=age+1 where id='0002'"; 32 this.jdbcTemplate.execute(sql); 33 // runner.update(this.jdbcTemplate.getDataSource().getConnection(),sql,"0002"); 34 return null; 35 } 36 }
使用该类应当注意:不能使用dbutils处理事务相关的问题,否则不能回滚!!
1 package com.kdyzm.spring.jdbc.transaction1; 2 3 import java.sql.SQLException; 4 5 public interface UserService { 6 public User updateUser(User user) throws SQLException; 7 }
1 package com.kdyzm.spring.jdbc.transaction1; 2 3 import java.sql.SQLException; 4 5 6 public class UserServiceImpl implements UserService{ 7 private UserDao userDao; 8 public UserDao getUserDao() { 9 return userDao; 10 } 11 12 public void setUserDao(UserDao userDao) { 13 this.userDao = userDao; 14 } 15 16 @Override 17 public User updateUser(User user) throws SQLException { 18 return userDao.updateUser(user); 19 } 20 21 }
使用的数据库连接池:c3p0,配置文件如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <c3p0-config> 3 <!-- 默认配置,只可以出现一次 --> 4 <default-config> 5 <!-- 连接超时设置30秒 --> 6 <property name="checkoutTimeout">30000</property> 7 <!-- 30秒检查一次connection的空闲 --> 8 <property name="idleConnectionTestPeriod">30</property> 9 <!--初始化的池大小 --> 10 <property name="initialPoolSize">2</property> 11 <!-- 最多的一个connection空闲时间 --> 12 <property name="maxIdleTime">30</property> 13 <!-- 最多可以有多少个连接connection --> 14 <property name="maxPoolSize">10</property> 15 <!-- 最少的池中有几个连接 --> 16 <property name="minPoolSize">2</property> 17 <!-- 批处理的语句--> 18 <property name="maxStatements">50</property> 19 <!-- 每次增长几个连接 --> 20 <property name="acquireIncrement">3</property> 21 <property name="driverClass">com.mysql.jdbc.Driver</property> 22 <property name="jdbcUrl"> 23 <![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]> 24 </property> 25 <property name="user">root</property> 26 <property name="password">5a6f38</property> 27 </default-config> 28 29 <named-config name="namedconfig"> 30 <!-- 连接超时设置30秒 --> 31 <property name="checkoutTimeout">30000</property> 32 <!-- 30秒检查一次connection的空闲 --> 33 <property name="idleConnectionTestPeriod">30</property> 34 <!--初始化的池大小 --> 35 <property name="initialPoolSize">2</property> 36 <!-- 最多的一个connection空闲时间 --> 37 <property name="maxIdleTime">30</property> 38 <!-- 最多可以有多少个连接connection --> 39 <property name="maxPoolSize">2</property> 40 <!-- 最少的池中有几个连接 --> 41 <property name="minPoolSize">2</property> 42 <!-- 批处理的语句--> 43 <property name="maxStatements">50</property> 44 <!-- 每次增长几个连接 --> 45 <property name="acquireIncrement">2</property> 46 <property name="driverClass">com.mysql.jdbc.Driver</property> 47 <property name="jdbcUrl"> 48 <![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]> 49 </property> 50 <property name="user">root</property> 51 <property name="password">5a6f38</property> 52 </named-config> 53 </c3p0-config>
最后,最重要的com.kdyzm.spring.jdbc.transaction1.applicationContext.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=" 8 http://www.springframework.org/schema/beans 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 <!-- 首先配置数据源 --> 14 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 15 <constructor-arg index="0" value="namedconfig"></constructor-arg> 16 </bean> 17 <!-- 配置JdbcTemplate模板 --> 18 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 19 <constructor-arg index="0"> 20 <ref bean="dataSource"/> 21 </constructor-arg> 22 </bean> 23 <!-- 配置JDBC事务管理器 --> 24 <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 25 <property name="dataSource"> 26 <ref bean="dataSource"/> 27 </property> 28 </bean> 29 30 31 <!-- 配置通知 --> 32 <tx:advice id="advice" transaction-manager="dataSourceTransactionManager"> 33 <tx:attributes > 34 <tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> 35 </tx:attributes> 36 </tx:advice> 37 38 <!-- 配置切入点 --> 39 <aop:config> 40 <!-- <aop:pointcut expression="execution(* com.kdyzm.spring.jdbc.transaction.*ServiceImpl.*(..))" id="perform"/> --> 41 <aop:pointcut expression="execution(* com.kdyzm.spring.jdbc.transaction1.UserServiceImpl.updateUser(..))" id="perform"/> 42 <aop:advisor advice-ref="advice" pointcut-ref="perform"/> 43 </aop:config> 44 45 <!-- 需要注意的是不需要配置切面,因为如果要配置切面的话就需要程序员完成(事务管理), 46 这样spring就没有意义了 --> 47 48 <!-- 程序员需要干的事情! --> 49 <!-- 需要纳入spring容器的 --> 50 <bean id="userDao" class="com.kdyzm.spring.jdbc.transaction1.UserDaoImpl"> 51 <property name="jdbcTemplate" ref="jdbcTemplate"></property> 52 </bean> 53 <bean id="userService" class="com.kdyzm.spring.jdbc.transaction1.UserServiceImpl"> 54 <property name="userDao" ref="userDao"></property> 55 </bean> 56 </beans>
(4)测试
ApplicationContext context=new ClassPathXmlApplicationContext("com/kdyzm/spring/jdbc/transaction1/applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); User user=new User(); userService.updateUser(user);
由于在UserDaoImpl.updateUser方法中存在/0的异常,所以方法会异常终止,但是spring会回滚方法中的事务。通过查看数据库中user的age字段可以得到结果。如果0001和0002并没有发生任何改变,则表示成功回滚;如果只有0001字段发生了自增长,则表示回滚失败。
6.小练习总结:
(1)事务的方法中不能使用dbutils工具,否则回滚失败!
(2)在service层控制事务,目标类是*ServiceImpl,目标方法是update方法。所以配置切入点表达式是:
<aop:pointcut expression="execution(* com.kdyzm.spring.jdbc.transaction1.UserServiceImpl.updateUser(..))" id="perform"/>
(3)事务管理器是Jdbc事务管理器,所以使用的类是DataSourceTransactionManager,创建对象的时候需要调用setDataSource方法传入DataSource参数。
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref bean="dataSource"/> </property> </bean>
(4)配置通知,需要制定事务管理器,这里的配置是一个模板,并不针对某个类中的方法,而是针对匹配到的所有方法:
<tx:advice id="advice" transaction-manager="dataSourceTransactionManager"> <tx:attributes > <tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> <tx:method name="*" read-only="true" isolation="DEFAULT" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
* isolation:制定事务的隔离界别,使用default表示使用数据库制定的隔离级别,spring不再另外指定。
* propagation:指定事务的传播属性,默认是REQUIRED,表示如果当前方法已经在一个事务中运行则加入该事务,否则创建一个新事务。