1.事务加在DAO层还是service层?
service中可能多涉及多种DAO的操作,比如存了一个User之后,需要保存一条日志信息;如果在DAO中分别设置事务的话,一个DAO下面方法抛出异常了,但是不会影响到另一个DAO下面的方法,这是两个事务;因此事务要加在Service层;
2.需求:存一个user的同时,记录一个日志,说这个user被存了;
3.代码实现:--这边是spring管理hibernate下的transaction;
1)DAO实现:UserDAOImpl.java --保存User
package com.cy.dao.impl; import javax.annotation.Resource; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.stereotype.Component; import com.cy.dao.UserDAO; import com.cy.model.User; @Component public class UserDAOImpl implements UserDAO { @Resource private SessionFactory sessionFactory; //保存User public void save(User user) { Session s = sessionFactory.getCurrentSession(); s.save(user); } }
2)DAO实现:LogDAOImpl.java --保存Log
package com.cy.dao.impl; import javax.annotation.Resource; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.stereotype.Component; import com.cy.dao.LogDAO; import com.cy.model.Log; @Component("logDAO") public class LogDAOImpl implements LogDAO { @Resource private SessionFactory sessionFactory; //保存日志信息 public void save(Log log) { Session s = sessionFactory.getCurrentSession(); s.save(log); throw new RuntimeException("error!"); } }
3)User实体 和 Log实体:
User.java:
package com.cy.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class User { private int id; private String username; private String password; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
Log.java:
package com.cy.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; /** * * @author chengyu * 表名和我们的类名不一样,使用@Table(name="xxx"); */ @Entity @Table(name="t_log") public class Log { private int id; private String msg; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
4)UserService中保存User后,插入一条日志记录:
package com.cy.service; import javax.annotation.Resource; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import com.cy.dao.LogDAO; import com.cy.dao.UserDAO; import com.cy.model.Log; import com.cy.model.User; @Component("userService") public class UserService { @Resource private UserDAO userDAO; @Resource private LogDAO logDAO; public void init() { System.out.println("init"); } /** * 插入一个用户的同时,记录一条日志 * @Transactional 默认情况:如果catch到任何的RuntimeException,自动回滚 */ @Transactional public void add(User user) { userDAO.save(user); Log log = new Log(); log.setMsg("a user saved!"); logDAO.save(log); } public UserDAO getUserDAO() { return userDAO; } public void setUserDAO( UserDAO userDAO) { this.userDAO = userDAO; } public void destroy() { System.out.println("destroy"); } }
5)beans.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"> <context:annotation-config /> <context:component-scan base-package="com.cy"/> <!-- 如果下面使用到了占位符的方式,请在locations下面去找 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:jdbc.properties</value> </property> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 初始化SessionFactory annotatedClasses : 接收对那些类做了注解 --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="annotatedClasses"> <list> <value>com.cy.model.User</value> <value>com.cy.model.Log</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.show_sql">true</prop> </props> </property> </bean> <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <tx:annotation-driven transaction-manager="txManager"/> </beans>
6)测试代码:
package com.cy.service; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.cy.model.User; public class UserServiceTest { @Test public void testAdd() throws Exception { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); UserService service = (UserService)ctx.getBean("userService"); System.out.println(service.getClass()); service.add(new User()); ctx.destroy(); } }
测试结果:
因为在LogDAOImpl中插入日志时,throw new RuntimeException("error!");
User和Log都插入失败,User插入后回滚了;
4. @Transactional settings的配置:
事务的传播特性:在当前的执行环境里头,到底有没有事务,另外根据这个事务的配置情况执行到我这个当前方法的时候,当前这个方法怎么样来处理这个事务;
1.propagation: transaction的产生过程;事务的传播方式;
REQUIRED:@Transactional注解的默认值; 如果有就使用原来的,如果没有就创建一个新的;@Transactional(propagation=Propagation.REQUIRED)
MANDATORY: 强制的,必须的;当前这个方法要执行,必须得有一个Transaction;如果没有transaction会抛异常;
比如UserService中的add方法上面@Transactional(propagation=Propagation.MANDATORY),那么测试方法中调用此add方法之前,就必须得有一个transaction;
NESTED:在一个内嵌的transaction里面执行;
原来有一个transaction了,相当于在这个里面内嵌了一个transaction,不在原来的transaction里面执行了,而是在内嵌的transaction里面执行, 内部的transaction回滚的时候不会影响外面的
transaction;
NEVER: 我这个方法要执行,必须不能有事务;要是有事务,抛异常;
NOT_SUPPORTED: 这个方法要想执行必须不能有事务;要是有事务把这个事务挂起,执行完我这个方法,原来的事务再继续;
REQUIRES_NEW:创建一新的transaction,如果当前有transaction,将此transaction挂起;
SUPPORTS: 支持当前的transaction,要是有我就有,要是没有我也就没有,就不在事务里执行;
2.isolation: 隔离级别;
3.readOnly: 如果设置为true,在transaction里面没有update。insert、delete这方面操作;比如上面的例子,add方法中要设置为@Transactional(readOnly=true)的话,就会报错:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed;
如果确认没有写的操作,可以设置为true,这时候spring会使用只读的Connection,readOnly的connection比读写的Connection执行效率要高;可以提高性能;
再者,如果这个方法里面规定只能读,那么可以设置为true,防止别人进行更改操作;
4.timeout: 一个事务如果时间太长的话,就把它终止掉;
5.rollbackFor: 默认情况下,RuntimeException会导致回滚;HibernateException本身就是一种运行期异常;
可以在rollbackFor里面写清楚,什么情况下会回滚,比如写自己定义的MyException,如果抛出MyException,就会回滚;
6.noRollbackFor: 在某些异常下不会回滚,其他的会回滚;
required图解:
在执行了method1,method1里面调用了method2,如果说在method1里面已经有transaction了,那么method2里面就不需要再创建新的transaction了;
---------------------