什么是事务?
数据库的事务,指的是一组操作的执行单元;其中的一组操作指的是一组SQL指令(增、删、改),执行单元指这一组操作全部执行成功,或者全部回滚;(什么是单元,一个才整体交一个单元)
简单理解一下,其实就是:事务是一组sql 语句的集合,这组sql语句要么全部执行成功,要么全部回滚
事务有四个基本要素,简称ACID
atomic(原子性):事务的所有操作,要么全部完成,要么全部回滚到事务开始前的状态
consistent(一致性):事务执行的结果必须是从一个一致的状态到另一个一致的状态,不管一个时间点上它有多少个并发事务
isolate(隔离性):一个事务的执行不能被其他事务干扰,
durable(持久性):事务一旦提交,事务对数据库的更改被永久性的保存在数据库中,
Spring提供的事务控制
- 编程式事务控制
在代码中控制事务的开始、回滚、和结束,可以自由的控制在哪个方法的哪一行中进行事务控制,其控制的颗粒度更细(相对于声明式事务控制);因为是写在代码中,由程序员自己控制,因此在团队合作中,事务的管理会比较混乱。下面是一个案例:
public int insertDB(){
Connection conn = null;
PreparedStatement pstmt = null;
int result = 0;
try {
conn = jdbcTemplate.getDataSource().getConnection();
conn.setAutoCommit(false); //默认是自动提交事务的,这个关闭自动提交
String sql = "insert into lsjm_user (uname,pwd) values (?,?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "801");
pstmt.setString(2, "编程式事务控制");
System.out.println("编程式事务控制");
result = pstmt.executeUpdate();
Object e = 3/0; // 创造一个异常,进入catch
conn.commit(); // 手动提交事务,不写这一行,前面的插入数据到数据库是不起作用的,也不报异常
} catch (Exception e) {
try {
conn.rollback(); // 发生了异常,进行事务回滚
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally{
try {
conn.setAutoCommit(true);
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return result ;
}
- 声明式事务控制
Spring没有直接管理事务,提供事务管理器 调用第三方组件完成事务控制
事务管理器 | 下面对应目标时使用 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 在单一的JDBC Datasource中管理事务 |
org.springframework.orm.hibernate3.HibernateTransactionManager | 当持久化机制是hibernate时,用它来管理事务 |
org.springframework.jdo.JdoTransactionManager | 当持久化机制是Jdo时,用它来管理事务 |
org.springframework.transaction.jta.JtaTransactionManager | 使用一个JTA实现来管理事务。在一个事务跨越多个资源时必须使用 |
org.springframework.orm.ojb.PersistenceBrokerTransactionManager | 当apache的ojb用作持久化机制时,用它来管理事务 |
特点:
- 配置在配置文件中,解除了和代码的耦合度,不需要事务管理时,去掉配置即可(注解也算是配置)
- 事务以bean组件的函数为单位,即一个函数正常执行完成后,这个函数中的数据库操作按照一次事务提交
因为事务以函数为单位,函数与函数之间肯定存在相互调用的关系,此时的事务怎么处理?
-- spring 提供了一组配置供开发者选择,配置的方式成为事务的传播策略,比如方法A 调用了 方法B
传播行为 | 意义 |
---|---|
REQUIRED | 业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务 |
NOT_SUPPORTED | 声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行 |
REQUIRESNEW | 属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行 |
MANDATORY | 该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。 |
SUPPORTS | 这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行 |
Never | 指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行 |
NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效 |
数据库的隔离级别(这个需要再研究一下,暂时贴上)
隔离级别 | 含义 |
---|---|
DEFAULT | 使用后端数据库默认的隔离级别(spring中的的选择项) |
READ_UNCOMMITED | 允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读 |
READ_COMMITTED | 允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生 |
REPEATABLE_READ | 对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。 |
SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。 |
Spring声明式事务控制的使用 (junit测试Demo)
1、准备工作
- 导jar包:
- 数据库驱动jar :mysql-connector-java......
- spring核心容器core、context;
- spring集成jdbc 的 jar:spring-jdbc..., 事务控制的jar:spring-tx..., AOP的jar:spring-aop...
<dependencies>
<!-- 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!-- spring核心容器core子模块的依赖信息 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring核心容器context子模块的依赖信息 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring核心容器context子模块的依赖jar包的依赖信息 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring 中的aop模块的jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring集成jdbc的jar,该包中提供了JdbcTemplate工具类 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring中事务控制的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
2、声明式事务控制配置,内容 和 注释都写在配置文件中
<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns指的是命名空间,下面存放着各个版本的约束文件 -->
<!-- xmlns:xsi指的是标签符合w3c的规范 -->
<!-- xsi:schemaLocation:约束文件的路径,命名空间与约束文件的全路径组成 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/lsjm?useUnicode=true&characterEncoding=utf8" />
<property name="username" value="admin" />
<property name="password" value="admin" />
</bean>
<!-- 继承JdbcTemplate类的配置方式 -->
<bean id="userDao1" class="com.chaol.dao.impl.LsjmUserDaoImpl1">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 加载spring的工具类JdbcTemplate为bean组件 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 通过spring的依赖注入设定Dao类JdbcTemplate属性 -->
<bean id="userDao" class="com.chaol.dao.impl.LsjmUserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 声明式事务控制配置, 分三步 -->
<!-- 第一步: 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步: 配置事务通知, 指定对bean组件的哪些方法 进行事务管理-->
<tx:advice id="ad" transaction-manager="transactionManager">
<tx:attributes>
<!-- name:指定方法名(可用通配符); isolation:指定数据库隔离级别; propagation:事务传播策略; read-only:只有查询的函数配置为true效率更高 -->
<tx:method name="insert*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 第三步:配置切入点,即配置哪些bean组件需要事务 -->
<aop:config>
<aop:pointcut expression="execution(* com.chaol.dao.impl.*.*(..))" id="targetPointcut"/>
<!-- advice-ref:第二步事务通知中的id, pointcut-ref:上一行切入点的id -->
<aop:advisor advice-ref="ad" pointcut-ref="targetPointcut"/>
</aop:config>
</beans>
在上面的配置 中,配置了对com.chaol.dao.impl 下的任意类中 形如 insert* 的方法 做事务管理,写一个方法测试一下:
// 这个方法是在 com.chaol.dao.impl.LsjmUserDaoImpl类中
public int insert(){
String sql = "insert into lsjm_user (uname,pwd) values (?,?)";
int result = jdbcTemplate.update(sql,new Object[]{"txTest2", "txTest2"});
Object e = 3/0; // 创造一个异常,测试会不会回滚
return result;
}
执行上面方法,取数据库中查看结果:
-- 可见函数中的数据库操作并没有保存到数据库中,函数发生异常,数据库操作全部回滚(没有抛出异常测试是好的,结果不贴了):
每一种能用配置写的 ,大都都可以用注解来配置,声明式事务控制的注解配置:
省去上面的第二步和第三部,然后加上:
<!-- 开启注解自动扫描 -->
<context:component-scan base-package="com.chaol.dao.impl"></context:component-scan>
<!-- 采用@Transactional注解方式使用事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 第一步: 配置事务管理器(这一步还是需要的) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
然后在需要事务控制的方法上用@Transactional 注解(测试结果不贴了):
@Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED,readOnly=false)
public int insert(){
String sql = "insert into lsjm_user (uname,pwd) values (?,?)";
int result = jdbcTemplate.update(sql,new Object[]{"txTest3", "txTest3"});
Object e = 3/0; // 创造一个异常,测试会不会回滚
return result;
}
在用junit测试的过程中遇到一个问题,下面贴一下我的junit代码:
public class LsjmUserDaoTest {
//LsjmUserDaoImpl lsjmUserDaoImpl
LsjmUserDao lsjmUserDaoImpl;
@Before
public void before(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
lsjmUserDaoImpl =(LsjmUserDao) ac.getBean("userDao");
//lsjmUserDaoImpl =(LsjmUserDaoImpl) ac.getBean("userDao");
}
@Test
public void test(){
System.out.println(lsjmUserDaoImpl.insert());
}
}
问题:
如下 使用 接口 LsjmUserDao 的实现类 LsjmUserDaoImpl 来转换对象时,会报错,信息如下:
LsjmUserDaoImpl lsjmUserDaoImpl = (LsjmUserDaoImpl) ac.getBean("userDao");
java.lang.ClassCastException: com.sun.proxy.$Proxy10 cannot be cast to com.chaol.dao.impl.LsjmUserDaoImpl
解决:
用接口来转换 ac.getBean("userDao") 获取得到的对象
LsjmUserDao lsjmUserDaoImpl = (LsjmUserDao) ac.getBean("userDao");
原因:不能用接口的实现类来转换Proxy的实现类,它们是同级,应该用共同的接口来转换(百度到的)