🏆今日学习目标:
🍀JDBC事务 Hibernate事务 EJB事务详解
✅创作者:林在闪闪发光
⏰预计时间:30分钟
🎉个人主页:林在闪闪发光的个人主页🍁林在闪闪发光的个人社区,欢迎你的加入: 林在闪闪发光的社区
目录
当你想要某种东西时,整个宇宙会合力助你实现愿望
《牧羊少年奇幻之旅》
一、JDBC事务
1.事务的概念
事务(Transaction),字面理解,一般是指要做的或所做的事情。
在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
上述解释来自百度百科,如果你学过操作系统,可以将事务理解成原语,事务中的每件不可分割的子任务可以视为一条指令。
在我们今天要讲的数据库中的事务,就可以理解为一条或一组SQL语句,这个事务成功的执行的必要条件就是其中的SQL全部执行成功,如果其中的任一SQL执行失败(没有达到预期效果),则此次事务执行失败,所有的SQL都不会对数据库中的数据产生变更。
事务具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做;
- 一致性(Consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的;
- 隔离性(Isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰;
- 持久性(Durability):持久性也称永久性(Permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
对于其中的隔离性,数据库服务器有时会为了提供更好的处理并发能力,会牺牲一定的隔离性。这也是正确性和性能之间的对抗。如MySQL默认的隔离级别为READ COMMITTED,即读提交,可以读取其他事务已经提交的内容,可以避免脏读,但是无法避免重复度和幻读。
2.JDBC中事务的相关方法
下面我们来看下,当使用JDBC操作数据库时,要如何使用事务。这里,我们要新介绍几个Connection接口中的几个方法,如下表所示:
当Connection自动提交模式为true的时候,即默认值,其所有SQL语句将作为单个事务执行并提交,每次SQL执行时会默认提交,不需手动触发;当自动提交模式为false时,其SQL语句会分组为事务(commit与commit之间的SQL或commit与rollback之间的SQL),这些事务通过调用commit
方法提交,或调用rollback
方法来进行回滚。
3.一个简单的示例
正常情况
public class TestTransaction1 {
public static void main(String[] args) {
Connection con = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
con = JDBCUtils.getConnection();
//关闭自动提交 自动会开启事务
con.setAutoCommit(false);//开启事务
// A 转 B 100元
String sql1 = "update account set money=money-100 where name='A'";
st = con.prepareStatement(sql1);
st.executeUpdate();
String sql2 = "update account set money=money+100 where name='B'";
st = con.prepareStatement(sql2);
st.executeUpdate();
//业务完毕,提交事务
con.commit();
System.out.println("A 转 B 100元 成功!");
} catch (SQLException e) {
e.printStackTrace();
try {
con.rollback(); //如果失败就回滚事务
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
JDBCUtils.release(con, st, rs);
}
}
}
异常情况
public class TestTransaction2 {
public static void main(String[] args) {
Connection con = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
con = JDBCUtils.getConnection();
//关闭自动提交 自动会开启事务
con.setAutoCommit(false);//开启事务
// A 转 B 100元
String sql1 = "update account set money=money-100 where name='A'";
st = con.prepareStatement(sql1);
st.executeUpdate();
//int x=1/0; //报错
String sql2 = "update account set money=money+100 where name='B'";
st = con.prepareStatement(sql2);
st.executeUpdate();
//业务完毕,提交事务
con.commit();
System.out.println("A 转 B 100元 成功!");
} catch (SQLException e) {
//如果失败则会默认回滚
e.printStackTrace();
/*try {
con.rollback(); //如果失败就回滚事务
} catch (SQLException ex) {
ex.printStackTrace();
}*/
} finally {
JDBCUtils.release(con, st, rs);
}
}
}
二、HIbernate中的事务
1.HIbernate中的事务管理
在Hibernate中,可以通过代码来操作管理事务,如通过“Transaction tx = session.beginTransaction();” 开启一个事务:持久化操作后,通过“tx.commit();” 提交事务;如果事务出现异常,又通过“tx.rollback();” 操作来撤销事务(事务回滚)。
除了在代码中对事务开启,提交和回滚操作外,还可以在Hibernate的配置文件中对事务进行配置。配置文件中,可以设置事务的隔离级别。具体的配置方法是在hibernate.cfg.xml 文件中的 标签元素中进行的。如下:
<!-- 指定hibernate操作数据库时的隔离级别
#hibernate.connection.isolation 1|2|4|8
0001 1 读未提交
0010 2 读已提交
0100 4 可重复读
1000 8 串行化
-->
<property name="hibernate.connection.isolation">4</property>
我们在真正进行事务管理的时候,需要考虑事务的应用场景,也就是说我们的事务控制不应该是在DAO层实现的,应该在Service层实现,并且在Service中调用多个DAO实现一个业务逻辑的操作。
具体操作如下显示:
其实最主要的是如何保证在Service中开启的事务时使用的Session对象和DAO中多个操作使用的是同一个Session对象。
其实由两种办法可以实现:
- 可以在业务层获取到Session,并将Session作为参数传递给DAO。
- 可以使用ThreadLocal 将业务层获取的Session绑定到当前线程中,然后在DAO中获取Session的时候,都从当前线程中获取。
其实使用第二种方式肯定是最优方案,那么具体的实现已经不用我们来完成了,Hibernate的内部已经帮我们将这个事情做完了。我们只需要完成一段配置即可。
Hibernate5 中自身提供了三种管理Session对象的方法:
- t h r e a d : S e s s i o n 对 象 的 生 命 周 期 与 本 地 线 程 绑 定 。 \color{red}{thread:Session对象的生命周期与本地线程绑定。} thread:Session对象的生命周期与本地线程绑定。
- jta:Session 对象的生命周期与 JTA 事务绑定。
- managed:Hibernate委托程序来管理Session对象的生命周期。
在hibernate.cfg.xml 中进行如下配置:
<!-- 指定session与当前线程绑定 -->
<property name="hibernate.current_session_context_class">thread</property>
Hibernate提供sessionFactory.getCurrentSession() 创建一个session 和ThreadLocal 绑定方法。
书写一个HibernateUtils工具类:
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtils {
private static SessionFactory sf;
static{
//1 创建,调用空参构造
Configuration conf = new Configuration().configure();
//2 根据配置信息,创建 SessionFactory对象
sf = conf.buildSessionFactory();
}
//获得session => 获得全新session
public static Session openSession(){
//3 获得session
Session session = sf.openSession();
return session;
}
//获得session => 获得与线程绑定的session
public static Session getCurrentSession(){
//3 获得session
Session session = sf.getCurrentSession();
return session;
}
}
而且Hibernate中提供的这个与线程绑定的session可以不用关闭,当线程执行结束后,就会自动关闭了。
测试方法:
import org.hibernate.Session;
import org.junit.Test;
import pers.zhang.utils.HibernateUtils;
//测试getCurrentSession
public class Demo {
@Test
//返回同一个与线程绑定的session
public void fun1(){
Session session1 = HibernateUtils.getCurrentSession();
Session session2 = HibernateUtils.getCurrentSession();
System.out.println(session1==session2);//true
}
@Test
//返回不同的session
public void fun2(){
Session session1 = HibernateUtils.openSession();
Session session2 = HibernateUtils.openSession();
System.out.println(session1==session2);//false
}
}
输出
true
false
三、EJB事务
在我们对事务的基本概念以及出现的问题和隔离级别有进一步的了解之后,接下来看看EJB是如何进行事务管理.
在EJB中有两种使用事务的方式。第一种方式通过容器管理的事务,叫CMT(Container-Managed Transaction),另一种通过Bean管理的事务叫BMT(Bean-Managed Transaction)。
如果使用容器来管理事务,那么EJB组件就不需要显式地给出begin 、commit 、abort 语句,EJB 容器会替我们考虑这些内容。EJB 容器会依据EJB组件提供者指定的事务行为来界定相应的事务边界。
在使用容器管理事务时,EJB 容器会拦截客户请求,并自动为EJB组建启动新的事务,也就是说,容器会通过begin 语句调用底层事务系统,从而启动事务。随后,容器会将业务请求委派给EJB组件,组件中的业务操作将运行在这一事务中。处于事务中的EJB 组件能够执行任何业务逻辑,如写入数据库、发送异步信息、调用其他的EJB组件等。一旦在处理业务过程中出现问题,则EJB 组建需要通知EJB 容器去回滚事务。当EJB 组建完成业务处理后,会将控制权交回给EJB 容器。随后,EJB容器能够通过commit 或abort 语句调用底层事务系统。
我们可以使用@TransactionAttribute注释或部署描述符来指定事务属性。EJB 容器通过分析事务属性便能够知道如何处理EJB 组件的事务需求。
如果用简短的话总结上面的内容就是,用CMT管理事务,事务都是被容器管理的,开发人员不需要对事务进行管理,需要做的就是配置事务属性.
EJB 事务属性的取值有以下几种:
(1 )Required ,如果EJB组件必须总是运行在事务中,则应该使用Required 模式。如果已经有事务在运行,则EJB 组件参与其中;如果没有事务运行,则EJB 容器会为EJB组件启动新的事务。
Required 是默认和最常使用的事务属性值。这个值指定必须在事务之内调用EJB方法。如果从非事务性客户端调用方法,那么容器会在调用方法之前开始事务,并且在方法返回时结束事务。另一方面,如果调用者从事务性上下文调用方法,那么方法会联结已有事务。在从客户段传播事务的情况下,如果我们的方法表示应该回滚事务,那么容器不仅回回滚整个事务,而且会向客户端抛出异常,从而让客户端知道它开始的事务已经被另一个方法回滚了。
(2 )Requires_New,当客户调用EJB 时,如果总是希望启动新的事务,则应该使用RequiresNew 事务属性,如果客户在调用EJB组件时已经存在事务,则当前事务会被挂起,进而容器启动新的事务,并将调用请求委派给EJB组件。也就是说,如果客户端已经有了事务,那么它暂停该事务,知道方法返回位置,新事务是成功还是失败都不会影响客户端已有的事务。EJB组件执行相应的业务操作,容器会提交或回滚事务,最终容器将恢复原有的事务,当然,如果客户在调用EJB 组件时不存在事务,则不需要执行事务的挂起或恢复操作。
RequiresNew 事务属性非常有用。如果EJB 组件需要事务的ACID属性,并且将EJB 组件运行在单个独立的工作单元中,从而不会将其他外部逻辑也包括在当前的事务中,则必须使用RequiredNew事务属性。如果需要事务,但是不希望事务的回滚影响客户端,就应该使用它。另外,当不希望客户端的回滚影响你的时候,也应该使用这个值。
(3 )Supports ,如果某个EJB组件使用了Supports 事务属性,则只有调用它的客户已经启用了事务时,这一EJB 组件才会运行在事务中。如果客户并没有运行在事务中,则EJB组建也不会运行在事务中。Supports 同Required 事务属性很相似,但是,Required 要求EJB 组件必须运行在事务中。如果使用Support事务属性,EJB 组建很可能没有运行在事务中。
(4 )Mandatory ,Mandatory事务属性要求调用EJB 组件的客户必须已经运行在事务中。如果从非事务性客户端调用使用Mandatory 属性的EJB方法,那么客户将接受到系统抛出的javax.ejb.EJBTransactionRequiredException 异常。EJB 组件使用Mandatory事务属性是非常安全的,它能够保证EJB 组建运行在事务中。如果客户没有运行在事务中,则不能够调用到应用了Mandatory 事务属性的EJB组件。但是,Mandatory 事务属性要求第3 方(及客户)在调用EJB 组件前必须启动了事务。EJB 容器并不会为Mandatory事务属性自动启动新事务,这是同Support 事务属性的最主要区别。
(5 )NotSupported ,如果EJB组件使用了NotSupport事务属性,它根本不会参与到事务中。如果调用者使用相关联的事务调用方法,容器就会暂停事务,调用方法,然后再方法返回时恢复事务。通常,此属性只用于非实物性的自动确认模式中,支持JMS提供者的MDB 。
(6 )Never ,如果EJB组件使用Never 事务属性,它就不能够参与到事务中,而且,如果调用它的客户已经处于事务中,则容器会将javax.ejb.EJBException异常抛给客户。
当然在上面所列出的属性中我们最常用的还是Required具体可以看下面的一段片段代码:
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless(name = "UserManager")
@Remote
@TransactionManagement(TransactionManagementType.CONTAINER)
public class UserManagerBean implements UserManager {
@PersistenceContext
private EntityManager em ;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void addUser(String name) {
User s = new User();
s.setName(name);
em.persist(s);
System.out.println("服务器端执行成功:保存姓名" + name);
}
}
上面的例子中
@TransactionManagement(TransactionManagementType.CONTAINER)
表示指定事务的类型。如果省略,默认为CMT方式。
@TransactionAttribute(TransactionAttributeType.REQUIRED)
通知容器如何管理事务,事务的属性控制了事务的使用范围,因为事务之间的关系非常的复杂,这个属性主要是用来处理事务与事务之间怎样来处理的的问题。
至此 事务已全部输出完毕 欢迎小伙伴补充