数据库事务概念:
百度百科:数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完整地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。数据库事务是作用:
它确保了数据库的数据一致性和安全性,尤其在对数据执行增删时,如果发生异常和错误它就会触发事务回滚,从而确保了我们数据的一致性和安全性。
http://www.cnblogs.com/rush/archive/2011/12/11/2284262.html
Spring控制事务:
网上很多Spring关于事务配置的,我的配置是:
applicationContext.xml文件中
<!-- 数据库配置文件位置 -->
<context:property-placeholder location="classpath:profile/db_config.properties" />
<!-- 配置dbcp数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!--这些数据库的连接参数(name值)与原来的不大一样 -->
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${jdbc.maxActive}" />
<property name="minIdle" value="${jdbc.minIdle}" />
<property name="validationQuery" value="${jdbc.validationQuery}" />
<property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.cache.provider_class">${hibernate.cache.provider_class}</prop>
<prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
<!--Hibernate4.0 的查询翻译器-->
<prop key="hibernate.query.factory_class">${hibernate.query.factory_class}</prop>
<!-- <prop key="current_session_context_class">thread</prop> -->
</props>
</property>
<!-- 可用mappingLocations 或者 mappingResources配置扫描-->
<property name="mappingLocations">
<list>
<value>
classpath:com/gooxia/main/model/mapping/*.hbm.xml
</value>
</list>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 方式(1)使用annotation注解方式配置事务 -->
<!-- <tx:annotation-driven transaction-manager="transactionManager" /> -->
<!-- 方式(2)声明式事务,不用在每个类的方法上面写注解,以一个全局的规则匹配 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="find*" propagation="REQUIRED"/>
<tx:method name="load*" propagation="REQUIRED"/>
<tx:method name="get*" propagation="REQUIRED"/>
<tx:method name="*" rollback-for="Throwable" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:aspectj-autoproxy/>
<!-- 切入点 -->
<aop:config>
<!-- 对main业务逻辑层控制事务 -->
<aop:pointcut id="mainServiceMethodsPointcut"
expression="execution(* com.<span style="font-family: Arial, Helvetica, sans-serif;">gooxia</span><span style="font-family: Arial, Helvetica, sans-serif;">.main.service.*.*(..))"/></span>
<aop:advisor advice-ref="txAdvice" pointcut-ref="mainServiceMethodsPointcut"/>
</aop:config>
其中db_config.properties文件配置为:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\://127.0.0.1\:3306/gooxia?useUnicode\=true&characterEncoding\=UTF-8
jdbc.username=root
jdbc.password=
jdbc.maxActive=100
jdbc.minIdle=5
jdbc.validationQuery=SELECT 1 from dual
jdbc.testOnBorrow=true
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=true
hibernate.cache.provider_class=org.hibernate.cache.NoCacheProvider
hibernate.query.factory_class=org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory
hibernate.cache.use_second_level_cache=true
这些是Spring和Hibernate整合配置文件中的主要配置。
这些都不是重点。重点在于下面的过程:
第一、简单介绍一下我的测试环境:
Mysql数据库中两张表gooxia_admins和gooxia_users,一张是MyISAM引擎,一张是InnoDB引擎,如图:
每个表都只有id和name两个字段。
第二、项目采用Spring3.2.0 +Hibernate4.2.8
第三、项目中两个实体类Admin 和Users
由于我们的事务是控制在service层的方法上。我们可以简单测试一下自己的配置是否有错误。
Spring 管理事务时session 是绑定到ThreadLocal的来确保事务的执行,根据这个特点就可以测试。
BaseDAO类
public class BaseDAO<T> implements DAO<T> {
@Autowired
protected SessionFactory sessionFactory;
@Autowired
private MysqlJdbcTemplate jdbcTemplate;
/**
* T泛型类
*/
protected Class<?> entityClass;
/**
* T泛型类字段
*/
protected Field[] fields;
public BaseDAO(){
this.entityClass = (Class<?>)((ParameterizedType)getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
this.fields = this.entityClass.getDeclaredFields();
}
public Session getCurrentSession(){
System.out.println(entityClass.getName()+" session hashCode:"+sessionFactory.getCurrentSession().hashCode());
return sessionFactory.getCurrentSession();
}
/**
* 保存实体对象
*/
public void save(T t){
this.getCurrentSession().save(t);
}
}
在每次获取到绑定在ThreadLocal的session的时候,输出获取得到session的hashCode();
UserServiceImpl实现类
public class UserServiceImpl extends BaseServiceImpl<User,UserDAO> implements UserService{
@Autowired
private AdminDAO adminDAO;
public void testTransactionManager() throws Throwable{
Admin admin = new Admin();
admin.setName("test");
this.adminDAO.save(admin);
User user = new User();
user.setName("test");
this.dao.save(user);
// throw new Error("测试事务");
}
}
Service类中两次调用DAO,就会两次获取session
IndexController类
@Controller
public class IndexController {
@Autowired
private UserService userService;
/**
* 匹配控制器匹配不了的路径
* 显示404页面
*/
@RequestMapping(value="/**/*")
public String notFound(){
return "404";
}
@RequestMapping(value="/test")
public @ResponseBody String test(){
try {
this.userService.testTransactionManager();
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "test";
}
}
测试结果:
可以看到两次获取到的hashCode是一样的。说明service中两次数据库操作是在同一个session中。
此时两张表中各一条记录。
测试事务:
清空表数据。将Service中testTransactionManager 的
throw new Error("测试事务");
注释去掉,使得在方法执行的最后抛出错误,看看事务是否有回滚。
最后抛出的异常。
再看看数据库
发现事务回滚失败了。因为service的这个方法一半操作数据库成功了,而另一半操作失败了。这并不是数据库事务控制想要的结果。
再来一次,我们在程序运行的时候取断点,看看怎么回事。
到这里保存了admin,此时数据库gooxia_admins表情况:
发现admin已经插入到数据库中了。
再往下
此时保存user,看数据库gooxia_user表情况:
发现user信息并没有插入。
抛出异常,全部运行完成后。就出现了以上的情况,一张表插入成功,一张表插入失败(回滚了)。
下面介绍如何真正正确的控制事务。
将gooxia_admins表引擎也改成InnoDB,同时清空表。
再次测试
// throw new Error("测试事务");
注释错误抛出,运行结果。
插入正常。
现在抛出错误运行结果:
发现两个表中数据都没有插入。说明事务控制成功。
大家都知道Mysql数据库MyISAM和InnoDB是最常用的表引擎。MyISAM不支持事务,而InnoDB支持事务,所以出现这样的结果。
总结说明:
这说明要Spring控制事务,首先就必须数据库支持事务。Spring利用AOP在配置的service方法前后加上事务控制,从表面上完全不用写事务控制,这就是AOP的魅力。