【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

时间:2022-01-27 22:30:35

一、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数据库连接池

 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<constructor-arg index="0" value="namedconfig"></constructor-arg>
</bean>
 <?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 默认配置,只可以出现一次 -->
<default-config>
<!-- 连接超时设置30秒 -->
<property name="checkoutTimeout">30000</property>
<!-- 30秒检查一次connection的空闲 -->
<property name="idleConnectionTestPeriod">30</property>
<!--初始化的池大小 -->
<property name="initialPoolSize">2</property>
<!-- 最多的一个connection空闲时间 -->
<property name="maxIdleTime">30</property>
<!-- 最多可以有多少个连接connection -->
<property name="maxPoolSize">10</property>
<!-- 最少的池中有几个连接 -->
<property name="minPoolSize">2</property>
<!-- 批处理的语句-->
<property name="maxStatements">50</property>
<!-- 每次增长几个连接 -->
<property name="acquireIncrement">3</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
<![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]>
</property>
<property name="user">root</property>
<property name="password">5a6f38</property>
</default-config> <named-config name="namedconfig">
<!-- 连接超时设置30秒 -->
<property name="checkoutTimeout">30000</property>
<!-- 30秒检查一次connection的空闲 -->
<property name="idleConnectionTestPeriod">30</property>
<!--初始化的池大小 -->
<property name="initialPoolSize">2</property>
<!-- 最多的一个connection空闲时间 -->
<property name="maxIdleTime">30</property>
<!-- 最多可以有多少个连接connection -->
<property name="maxPoolSize">2</property>
<!-- 最少的池中有几个连接 -->
<property name="minPoolSize">2</property>
<!-- 批处理的语句-->
<property name="maxStatements">50</property>
<!-- 每次增长几个连接 -->
<property name="acquireIncrement">2</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
<![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]>
</property>
<property name="user">root</property>
<property name="password">5a6f38</property>
</named-config>
</c3p0-config>

c3p0-config.xml

    (2)定义Dao

 package com.kdyzm.spring.jdbc;

 public interface CourseDao {
public Course getCourse(Long cid);
}

com.kdyzm.spring.jdbc.CourseDao

    (3)实现Dao,这是非常关键的一步,因为需要继承JdbcDaoSupport类。

 package com.kdyzm.spring.jdbc;

 import java.sql.SQLException;

 import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.jdbc.core.support.JdbcDaoSupport; public class CourseDaoImpl extends JdbcDaoSupport implements CourseDao{
@Override
public Course getCourse(Long cid) {
String sql="select cid,cname from course where cid = ?";
QueryRunner run=new QueryRunner(this.getDataSource());
Course course=null;
try {
course = run.query(sql,new BeanHandler<Course>(Course.class),1L);
} catch (SQLException e) {
e.printStackTrace();
}
return course;
}
}

    (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)测试代码:

 ApplicationContext context=new ClassPathXmlApplicationContext("com/kdyzm/spring/jdbc/applicationContext.xml");
CourseDao courseDao = (CourseDao) context.getBean("courseDao");
Course course=courseDao.getCourse(1L);
System.out.println(course);
 package com.kdyzm.spring.jdbc;
/*
* 课程类
*/
public class Course {
private Long cid;
private String cname; public Course() {
}
@Override
public String toString() {
return "Course [cid=" + cid + ", cname=" + cname + "]";
}
public Long getCid() {
return cid;
}
public void setCid(Long cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
}

com.kdyzm.spring.jdbc.Course

      测试结果:

      【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

    分析:继承了JdbcDaoSupport类为什么是关键?JdbcDaoSupport类中有一个非常关键的方法setDataSource,它有一个JdbcTemplate类型的成员变量,我们通过使用该成员变量来完成所有工作:

      【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

    通过上面的流程图可以看到实际上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方法。

 package com.kdyzm.spring.jdbc;
/*
* 第二种实现方式:使用JdbcTemplate类作为成员变量
*/
import java.sql.SQLException; import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.jdbc.core.JdbcTemplate; public class CourseDaoImpl1 implements CourseDao{ JdbcTemplate jdbcTemplate=null; public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
} public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
} public Course getCourse(Long cid) {
String sql="select cid,cname from course where cid = ?";
QueryRunner run=new QueryRunner(this.getJdbcTemplate().getDataSource());
Course course=null;
try {
course = run.query(sql,new BeanHandler<Course>(Course.class),cid);
} catch (SQLException e) {
e.printStackTrace();
}
return course;
}
}

    (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接口。

 package com.kdyzm.spring.jdbc;
/*
* 第二种实现方式:使用JdbcTemplate类作为成员变量
*/
import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.jdbc.core.JdbcTemplate; public class CourseDaoImpl2 extends JdbcTemplate implements CourseDao{
public CourseDaoImpl2(DataSource dataSource) {
super(dataSource);
} public Course getCourse(Long cid) {
String sql="select cid,cname from course where cid = ?";
QueryRunner run=new QueryRunner(this.getDataSource());
Course course=null;
try {
course = run.query(sql,new BeanHandler<Course>(Course.class),cid);
} catch (SQLException e) {
e.printStackTrace();
}
return course;
}
}

    (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的优越之处:

      【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

  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事务的传播属性

    【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

      在实际工作当中,99%的使用过的是REQUIRED。

  4.spring事务的隔离级别

    【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

      在实际工作中,99%使用的是DEFAULT。

  5.XML配置练习

    (1)引入命名空间

 <?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
"> </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)新建类:

 package com.kdyzm.spring.jdbc.transaction1;

 public class User {
private String id;
private String name;
private Integer age;
public User() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}

com.kdyzm.spring.jdbc.transaction1.User

 package com.kdyzm.spring.jdbc.transaction1;

 import java.sql.SQLException;

 public interface UserDao {
public User updateUser(User user) throws SQLException;
}

com.kdyzm.spring.jdbc.transaction1.UserDao

非常重要的一个类:com.kdyzm.spring.jdbc.transaction1.UserDaoImpl

 package com.kdyzm.spring.jdbc.transaction1;

 import java.sql.SQLException;

 import org.apache.commons.dbutils.QueryRunner;
import org.springframework.jdbc.core.JdbcTemplate; public class UserDaoImpl implements UserDao{
private JdbcTemplate jdbcTemplate; public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
} public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
} /**
* 使用该方法测试非跨dao事务回滚问题
* 一定不能使用dbutils工具,否则不能回滚!!!!!!!!!!!!!
* @throws SQLException
*/
@Override
public User updateUser(User user) throws SQLException {
String sql="update user set age=age+1 where id='0001'";
// QueryRunner runner=new QueryRunner(this.jdbcTemplate.getDataSource());
// runner.update(this.jdbcTemplate.getDataSource().getConnection(),sql,"0001");
this.jdbcTemplate.execute(sql);
int a=1/0;
sql="update user set age=age+1 where id='0002'";
this.jdbcTemplate.execute(sql);
// runner.update(this.jdbcTemplate.getDataSource().getConnection(),sql,"0002");
return null;
}
}

    使用该类应当注意:不能使用dbutils处理事务相关的问题,否则不能回滚!!

 package com.kdyzm.spring.jdbc.transaction1;

 import java.sql.SQLException;

 public interface UserService {
public User updateUser(User user) throws SQLException;
}

com.kdyzm.spring.jdbc.transaction1.UserService

 package com.kdyzm.spring.jdbc.transaction1;

 import java.sql.SQLException;

 public class UserServiceImpl implements UserService{
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
} public void setUserDao(UserDao userDao) {
this.userDao = userDao;
} @Override
public User updateUser(User user) throws SQLException {
return userDao.updateUser(user);
} }

com.kdyzm.spring.jdbc.transaction1.UserServiceImpl

  使用的数据库连接池:c3p0,配置文件如下:

 <?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 默认配置,只可以出现一次 -->
<default-config>
<!-- 连接超时设置30秒 -->
<property name="checkoutTimeout">30000</property>
<!-- 30秒检查一次connection的空闲 -->
<property name="idleConnectionTestPeriod">30</property>
<!--初始化的池大小 -->
<property name="initialPoolSize">2</property>
<!-- 最多的一个connection空闲时间 -->
<property name="maxIdleTime">30</property>
<!-- 最多可以有多少个连接connection -->
<property name="maxPoolSize">10</property>
<!-- 最少的池中有几个连接 -->
<property name="minPoolSize">2</property>
<!-- 批处理的语句-->
<property name="maxStatements">50</property>
<!-- 每次增长几个连接 -->
<property name="acquireIncrement">3</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
<![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]>
</property>
<property name="user">root</property>
<property name="password">5a6f38</property>
</default-config> <named-config name="namedconfig">
<!-- 连接超时设置30秒 -->
<property name="checkoutTimeout">30000</property>
<!-- 30秒检查一次connection的空闲 -->
<property name="idleConnectionTestPeriod">30</property>
<!--初始化的池大小 -->
<property name="initialPoolSize">2</property>
<!-- 最多的一个connection空闲时间 -->
<property name="maxIdleTime">30</property>
<!-- 最多可以有多少个连接connection -->
<property name="maxPoolSize">2</property>
<!-- 最少的池中有几个连接 -->
<property name="minPoolSize">2</property>
<!-- 批处理的语句-->
<property name="maxStatements">50</property>
<!-- 每次增长几个连接 -->
<property name="acquireIncrement">2</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
<![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]>
</property>
<property name="user">root</property>
<property name="password">5a6f38</property>
</named-config>
</c3p0-config>

c3p0-config.xml

  最后,最重要的com.kdyzm.spring.jdbc.transaction1.applicationContext.xml文件:

 <?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
">
<!-- 首先配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<constructor-arg index="0" value="namedconfig"></constructor-arg>
</bean>
<!-- 配置JdbcTemplate模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg index="0">
<ref bean="dataSource"/>
</constructor-arg>
</bean>
<!-- 配置JDBC事务管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>
<!-- 配置通知 -->
<tx:advice id="advice" transaction-manager="dataSourceTransactionManager">
<tx:attributes >
<tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 配置切入点 -->
<aop:config>
<!-- <aop:pointcut expression="execution(* com.kdyzm.spring.jdbc.transaction.*ServiceImpl.*(..))" id="perform"/> -->
<aop:pointcut expression="execution(* com.kdyzm.spring.jdbc.transaction1.UserServiceImpl.updateUser(..))" id="perform"/>
<aop:advisor advice-ref="advice" pointcut-ref="perform"/>
</aop:config> <!-- 需要注意的是不需要配置切面,因为如果要配置切面的话就需要程序员完成(事务管理),
这样spring就没有意义了 --> <!-- 程序员需要干的事情! -->
<!-- 需要纳入spring容器的 -->
<bean id="userDao" class="com.kdyzm.spring.jdbc.transaction1.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="userService" class="com.kdyzm.spring.jdbc.transaction1.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
</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,表示如果当前方法已经在一个事务中运行则加入该事务,否则创建一个新事务。