**JAVA项目中的事务管理
事务:保证一组对数据库操作动作的完整性(一致性, 安全性),也就是说:这一组动作要么都成功,要么都失败
1.JDBC对事务的支持
- Java应用传统上使用JDBC(Java Database Connectivity)API来把数据持久到关系数据库中
- JDBC 事务是用 Connection 对象控制的
JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交(默认)和手动提交- 自动提交时(setAutoCommit(true)),每个独立SQL操作的执行完毕,事务会立即自动提交,也就是说每个SQL操作都是一个事务。
- 一个独立SQL操作什么时候算执行完毕?
对数据操作语言(DML,如insert,update,delete)和数据定义语言(如create,drop),语句一执行完就视为执行完毕。
对select语句,当与它关联的ResultSet对象关闭时,视为执行完毕。
对存储过程或其他返回多个结果的语句,当与它关联的所有ResultSet对象全部关闭,所有update count(update,delete等语句操作影响的行数)和output parameter(存储过程的输出参数)都已经获取之后,视为执行完毕。
例:
package com.ksxx.jdbc.pm;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import com.ksxx.jdbc.util.DBUtil;
/**
* 事务控制
* 雷锋 → rose 转账 500
* step1:雷锋 转出
step2:rose 收款
*/
public class TransactionDemo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConn();
conn.setAutoCommit(false);//关闭数据库连接的自动提交功能
//******雷锋向rose转账 start***********************************
String sql = "update userinfo set account = account - 500 "+
"where username = '活雷锋'"; //雷锋 转出
ps = conn.prepareStatement(sql);
ps.executeUpdate();
String str = null;//模拟异常出现
str.toString();
sql = "update userinfo set account = account + 500 " +
"where username = 'rose'"; // rose 收款
ps = conn.prepareStatement(sql);
ps.executeUpdate();
//******雷锋向rose转账 end*************************************
conn.commit();//如果事务执行完毕,手动提交。
} catch(Exception e){
try {
conn.rollback();//事务过程中,发生异常,数据回滚。
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally{
try {
//将数据库连接交还给连接池时,必须要重新开启事务的自动提交功能
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
DBUtil.closeConn(rs, ps, conn);//将数据库连接交还给连接池
}
}
}
2.MyBatis框架中事务处理
- MyBatis单独使用时:使用SqlSession来处理事务
DAO模块代码
public interface CostDao {
//Mapper映射器 本质就是一个接口的定义规则
//MyBatis是通过动态代理封装了实现类的代码
//接口中的方法名和sql文件中的id保持一致
//参数类型和sql文件中的parameterType指定类型一致
//返回值参考ResultType
public void update(Map<String, Object> map);
public void delete(int id);
}
CostMapper.xml文件
<mapper namespace="org.dao.CostDao">
<update id="update" parameterType="map">
update cost
set status = '1', startime = #{startime}
where cost_id = #{cost_id}
</update>
<delete id="delete" parameterType="java.lang.Integer">
delete from cost where cost_id = #{cost_id}
</delete>
</mapper>
SqlMapConfig.xml文件
<configuration>
<environments default="environment">
<environment id="environment">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value=
"oracle.jdbc.driver.OracleDriver" />
<property name="url" value=
"jdbc:oracle:thin:@localhost:1521:orcl"/>
<property name="username" value="ksxx_26" />
<property name="password" value="ksxx_26" />
</dataSource>
</environment>
</environments>
<!-- 加载sql文件 -->
<mappers>
<mapper resource="org/sql/CostMapper.xml" />
</mappers>
</configuration>
创建一个SqlSession工具类
public class MyBatisUtil {
public static SqlSession getSqlSession(){
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//加载sqlMapConfig.xml文件
InputStream is = MyBatisUtil.class.getClassLoader().
getResourceAsStream("SqlMapConfig.xml");
//创建SessionFactory对象
SqlSessionFactory sf = builder.build(is);
//创建session
SqlSession ss = sf.openSession();
return ss;
}
}
Junit单元测试
@Test
public void test3() throws SQLException{
SqlSession ss = MyBatisUtil.getSqlSession();
TransactionFactory transactionFactory =
new JdbcTransactionFactory();
//开启事务
Transaction newTransaction =
transactionFactory.newTransaction(ss.getConnection());
try {
Map<String, Object> map = new HashMap<String, Object>();
Timestamp time = new Timestamp(System.currentTimeMillis());
map.put("startime", time);
map.put("cost_id", 1);
int row = ss.update("update", map);//修改行数
ss.delete("delete", 147);
ss.commit();//执行完毕手动提交
} catch (Exception e) {
newTransaction.rollback();//出现异常回滚
e.printStackTrace();
} finally {
newTransaction.close();//关闭事务
ss.close();//关闭seesion连接
}
}
3.MyBatis和Spring整合后, 使用Spring的事务管理
- Spring提拱了编程式配置事务和声明式配置事务
编程式事务处理对代码有些侵入性,通常我们的事务需求并没有要求在事务的边界上进行如此精确的控制。因此一般采用声明式事务管理
声明式事务有两种方式:@Transactional注解方式和XML配置方式- Spring如何管理事务呢???
Spring会从datasource里面去获得连接
Spring需要操作事务,那必须要对Connection来进行设置(Connection对象里面有对应的方法,比如setAutoCommit,commit,rollback这些方法就是对事务的操作)
Spring的AOP可以拦截业务处理方法,并且也知道业务处理方法里面的DAO操作是从datasource里面获取Connection对象的
既然Spring得到了datasource内部的Connection对象,便可以对当前拦截的业务处理方法进行事务控制了
方式一: @Transactional注解方式配置AOP事务
DAO中代码
public interface EmpDao {
public void save(Emp emp);
}
EmpMapper.xml文件代码
<mapper namespace="org.ks.dao.EmpDao">
<!-- 主键映射
1.keyProperty ,实体对象对应的主键属性,
当查完序列后,会将序列值赋给主键属性 empno
2.order BEFORE:先执行selectKey,在执行insert
AFTER:执行顺序与BEFORE相反
-->
<insert id="save" parameterType="org.ks.entity.Emp">
<selectKey keyProperty="empno"
order="BEFORE" resultType="java.lang.Integer">
select emp_seq.nextval from dual
</selectKey>
insert into emp values(
#{empno},
#{ename,jdbcType=VARCHAR},
#{job,jdbcType=VARCHAR},
#{mgr,jdbcType=NUMERIC},
#{hiredate,jdbcType=DATE},
#{sal,jdbcType=NUMERIC},
#{comm,jdbcType=NUMERIC},
#{deptno,jdbcType=NUMERIC}
)
</insert>
applicationContext.xml文件配置
<util:properties id="jdbc" location="classpath:db.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="#{jdbc.driverName}"/>
<property name="url" value="#{jdbc.url}"/>
<property name="username" value="#{jdbc.userName}"/>
<property name="password" value="#{jdbc.password}"/>
</bean>
<!-- 1.声明事务管理组件:定义具体平台事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.
datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
2.开启事务组件注解扫描:注册事务注解处理器
如果方法发生运行期错误unchecked(RuntimeException),事务会进行回滚
如果发生checked Exception,事务不进行回滚.
-->
<tx:annotation-driven transaction-manager="txManager"
proxy-target-class="true"/>
Controller组件
@Controller
@RequestMapping("/emp")
@Transactional
public class EmpController {
@Resource
private EmpDao empDao;
/*
* 模拟批量添加员工
*/
@RequestMapping("/addemps.do")
public String addBatch(){
//插入第一个员工
Emp e1=new Emp();
e1.setEname("李梅");
e1.setJob("老师");
e1.setSal(1000.0);
e1.setEmpno(10);
empDao.save(e1);
//模拟异常
//Integer.valueOf("abc");//NumberFormatException
//插入第二个员工
Emp e2=new Emp();
e2.setEname("李刚");
e2.setJob("老师");
e2.setSal(2000.0);
e2.setEmpno(20);
empDao.save(e2);
return "emp/emp_list";
}
}
方式二:通过xml配置AOP事务
<!-- 1.声明事务管理组件:定义具体平台事务管理器 -->
<bean id="txManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2.基于xml配置的声明事务范围及类型 定义通知, 通知中要处理的就是事务 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 配置具体方法事务属性
isolation:事务隔离级别,默认为数据库隔离级别
propagation:事务传播行为:默认是同一事务
timeout="-1":事务超时时间,默认值使用数据库的超时时间
read-only="false":事务是否只读,默认是可读写
rollback-for:遇到那些异常就回滚,其他都不回滚
no-rollback-for:遇到那些异常不会滚, 其他都回滚
-->
<tx:method name="save" propagation="REQUIRED" isolation="DEFAULT"
read-only="false" rollback-for="java.lang.NullPointException"/>
<!-- 支持通配符 -->
<tx:method name="find*" read-only="true"/>
<tx:method name="add*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
</tx:attributes>
</tx:advice>
<!-- 3.配置aop -->
<aop:config proxy-target-class="true">
<!-- 切入点 -->
<aop:pointcut expression="within(org.ks.controller..*)" id="txPointcut"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<context:component-scan base-package="org.ks"/>
<mvc:annotation-driven/>
<bean id="viewResolver" class=
"org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 定义SqlSessionFactoryBean 将MyBatis中的主配置文件中的
重要参数引入spring配置文件中 ,这样MyBatis的主配置文件就可以不用了-->
<bean id="ssf"
class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入连接信息 -->
<property name="dataSource" ref="dataSource"/>
<!-- 注入sql定义文件信息,若是多个Mapper文件 可以使用*代替 -->
<property name="mapperLocations" value=
"classpath:org/ks/sql/*.xml"/>
</bean>
<!-- 配置MapperScannerConfigurer 按指定包扫描接口,批量生成
接口实例对象,默认接口名首字母小写当id -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描包,会扫描此包及其子包下的接口,多个包之间可以用“,"
隔开 -->
<property name="basePackage" value="org.ks.dao"/>
<!-- 注入sqlSessionFactoryBean 可以写也可不写,会自动注入 -->
<property name="sqlSessionFactory" ref="ssf"/>
</bean>