访问数据库事务导入
在我之前的文章《spring学习笔记(19)mysql读写分离后端AOP控制实例》中模拟数据库读写分离的例子,在访问数据库时使用的方法是:
public <E> E add(Object object) {
return (E) getSessionFactory().openSession().save(object);
}
通过直接开启session而后保存对象、查询数据等操作,是没有事务的。而如果我们的项目规模变大,业务逻辑日益复杂,我们在一个方法中进行大量的数据库操作,而没有事务管理的话,一旦中间哪一个操作环节出错,后果是严重的。比如,一个用户通过支付宝转100块到银行账户,于是用户的100块先转到了银行,但这时数据库异常中断,银行无法把100块转给用户账户,这时事务又没有回滚,那么可能用户的100块就白白损失掉了。
在spring中,有多种方式可以进行我们的事务配置,比如我们可以直接修改上面的方法,加上事务:
public <E> E add(Object object) {
Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
E id = (E) session.save(object);
tx.commit();
return id;
}
这样,我们就能为我们的add方法加上简单的事务了。
多数据库操作事务配置——引入Service层
但这样的话我们针对的只是dao层add这个方法,但在实际中,我们可能需要同时控制大量DAO的方法在同一个事务中,为此,我们可以创建service层来统一进行我们的业务逻辑处理。比如我们根据需求,需要先获取用户id,并修改用户名称,这里设计两个数据库操作,但我们在同一个service类方法中完成。
编程式事务模板类:TransactionTemplate
概念
在下例中,我们依然使用编程式事务,spring为此专门提供了模板类TransactionTemplate来满足我们的需求。TransactionTemplate是线程安全的,也即是说,我们可以在多个业务类中共享同一个TransactionTemplate实例进行事务管理。
常用属性
TransactionTemplate有很多常用的属性如:
1. isolationLevel:设置事务隔离级别
2. propagationBehavior:设置我们的事务传播行为
3. readOnly:设置为只读事务,即数据写操作会失败
4. timeout:设置链接过期时间,-1和默认为无超时限制
5. transactionManager:它是我们的IOC容器配置时的必要属性,设置我们的事务管理对象。在本例中用到hibernate,为HibernateTransactionManager。
核心方法
TransactionTemplate类在调用时主要用到的方法为execute(TransactionCallback
action)。
有返回值的回调接口
其中TransactionCallback为我们的回调接口,它只有一个方法:
T doInTransaction(TransactionStatus status),这个方法内是有事务的。通常我们的数据库查询操作就在这个方法里完成。
方法入参TransactionStatus接口
doInTransaction方法的唯一入参是TransactionStatus,它常用于查看我们当前的事务状态,它有两个常用的方法:
1. createSavepoint():创建一个记录点。
2. rollbackToSavepoint(savepoint):将事务回滚到特定记录点,这样从回滚处到记录点范围内所有的数据库操作都会失效。
无返回值的接口TransactionCallback
另外,doInTransaction是有返回值的,如果我们不需要返回值,我们可以使用TransactionCallback接口的一个子类TransactionCallbackWithoutResult,它对应的抽象方法doInTransactionWithoutResult(TransactionStatus status)是没有返回值的。
实例演示
下面开始我们的实例演示
1. service层配置
public class MyaseServiceImpl implements MyBaseService{
private MyBaseDao myBaseDao;
private TransactionTemplate transactionTemplate;
@Override//测试方法
public void queryUpdateUser(final Integer id,final String newName) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
User user = myBaseDao.queryUnique(User.class, id);//根据id获取用户
System.out.println(user);
user.setName(newName);//修改名称
myBaseDao.update(user);//更新数据库
System.out.println(user);
}
});
/*下面的方法是由返回值的,在这里我们假设为User。
User user = transactionTemplate.execute(new TransactionCallback<User>() {
@Override
public User doInTransaction(TransactionStatus status) {
User user = myBaseDao.queryUnique(User.class, id);
System.out.println(user);
user.setName(newName);
myBaseDao.update(user);
System.out.println(user);
return user;
}
});
*/
}
public void setMyBaseDao(MyBaseDao myBaseDao) {//set属性注入
this.myBaseDao = myBaseDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}
2. DAO层配置
对应的DAO层类和部分方法如下所示:
public class MyBaseDaoImpl implements MyBaseDao{
private SessionFactory sessionFactory;
private Session getCurrentSession (){//根据参数来选择创建一个新的session还是返回当前线程的已有session
return sessionFactory.getCurrentSession();
}
@Override
public <E> E queryUnique(Class<E> clazz, Integer entityId) {//查询唯一的对象
return (E) getCurrentSession().get(clazz, entityId);
}
@Override
public void update(Object object) {//更新对象
getCurrentSession().update(object);
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
3. Spring容器配置Bean依赖关系
如果对于Hibernate不太理解,可以先不管我们的方法实现原理,只需要知道对应的方法实现了什么功能即可。在这里。接下来我们要配置我们的spring容器,主要完成Bean之间的依赖配置:
<bean id="myBaseDao" class="com.yc.dao.MyBaseDaoImpl" >
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="myBaseServiceImpl" class="com.yc.service.MyBaseServiceImpl" >
<property name="myBaseDao" ref="myBaseDao" />
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>
关于数据源和sessionFactory的配置实例如下:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"><!-- 设置为close使Spring容器关闭同时数据源能够正常关闭,以免造成连接泄露 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/yc" />
<property name="username" value="yc" />
<property name="password" value="yc" />
<property name="defaultReadOnly" value="false" /><!-- 设置为只读状态,配置读写分离时,读库可以设置为true
在连接池创建后,会初始化并维护一定数量的数据库安连接,当请求过多时,数据库会动态增加连接数,
当请求过少时,连接池会减少连接数至一个最小空闲值 -->
<property name="initialSize" value="5" /><!-- 在启动连接池初始创建的数据库连接,默认为0 -->
<property name="maxActive" value="15" /><!-- 设置数据库同一时间的最大活跃连接默认为8,负数表示不闲置 -->
<property name="maxIdle" value="10"/><!-- 在连接池空闲时的最大连接数,超过的会被释放,默认为8,负数表示不闲置 -->
<property name="minIdle" value="2" /><!-- 空闲时的最小连接数,低于这个数量会创建新连接,默认为0 -->
<property name="maxWait" value="10000" /><!-- 连接被用完时等待归还的最大等待时间,单位毫秒,超出时间抛异常,默认为无限等待 -->
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<!-- MySQL的方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="javax.persistence.validation.mode">none</prop>
<!-- 必要时在数据库新建所有表格 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="current_session_context_class">thread</prop>
<!-- <prop key="hibernate.format_sql">true</prop> -->
</props>
</property>
<property name="packagesToScan" value="com.yc.model" />
</bean>
4. 测试方法和结果分析
配置完成后,就可以进行我们的测试了。在这里,我用到了Junit测试组件
public class Test1 {
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/spring-datasource.xml");
MyBaseServiceImpl myBaseServiceImpl = (MyBaseServiceImpl) ac.getBean("myBaseServiceImpl");
myBaseServiceImpl.queryUpdateUser(1, "newName");//在这里调用我们的service层方法
}
}
调用测试方法,我们会看到控制台输出如下相关信息:
DEBUG: org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtaining JDBC connection
DEBUG: org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtained JDBC connection
DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - begin————————这里我们的事务开始了
DEBUG: org.hibernate.loader.Loader - Loading entity: [com.yc.model.User#1]——————————读取id为1的用户
DEBUG: org.hibernate.loader.Loader - Done entity load//完成装载工作
User [id=1, name=zenghao]——————获得了我们的User信息
User [id=1, name=newName]——————完成了修改操作
DEBUG: org.springframework.orm.hibernate4.HibernateTransactionManager - Initiating transaction commit//初始化数据库提交
DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - committing————————这时候才完成了事务提交
观察打印信息,我们会发现我们像数据库发出了两次请求(分别为获取和更新)但事务才提交了一次。说明这两个请求在同一个事务中。这样我们就能确保多个数据库操作在同一个事务内完成,一旦中间出现异常,能立即回滚,取消前面的数据库操作。
很多人都知道我们的mvc模式将后端业务分成了三层(DAO,service,controller),从这里,我们也能略微看出DAO层和service层的功能职责了。DAO主要完成数据库查询的封装,而Service层则调用DAO层的数据库查询方法来完成我们的业务逻辑处理