java内存模型概述

    技术2024-03-18  96

    开发人员,设计人员和架构师经常将交易模型与交易策略混为一谈。 我通常要求客户参与中的架构师或技术负责人描述他们项目的交易策略。 我通常会收到以下三个回应之一。 有时候这很安静,“哦,我们确实不在应用程序中使用事务。” 其他时候,我听到一个困惑的消息:“嗯,我真的不确定你的意思。” 但是,通常,我会很自信地回答“我们正在使用声明式事务”。 但是正如您将在本文中看到的那样, 声明性事务一词描述了事务模型 ,但绝不是事务策略 。

    关于本系列

    事务可提高数据的质量,完整性和一致性,并使您的应用程序更强大。 在Java应用程序中成功事务处理的实现不是一件容易的事,它与设计以及编码有关。 在这个新系列中 ,Mark Richards是您指导设计有效的交易策略的指南,适用于从简单应用程序到高性能交易处理的用例。

    Java平台支持的三种事务处理模型是:

    本地交易模型 程序交易模型 声明式交易模型

    这些模型描述了事务在Java平台中应如何表现以及如何实现的基础知识。 但是,它们仅提供事务处理的规则和语义。 交易模型的应用方式完全取决于您。 例如,什么时候应该使用REQUIRED与MANDATORY事务属性? 您应该在何时何地指定事务回滚指令? 什么时候应该考虑程序化交易模型与声明性交易模型? 您如何优化高性能系统的交易? 交易模型本身无法回答这些问题。 相反,您必须通过制定自己的交易策略或采用本文中介绍的四种主要交易策略之一来解决这些问题。

    正如您在本系列的第一篇文章中所看到的那样,许多常见的事务陷阱可能会影响事务行为,从而降低数据的完整性和一致性。 同样,缺乏有效的(或任何)交易策略将对数据的完整性和一致性产生负面影响。 我在本文中描述的交易模型是制定有效交易策略的基础。 了解模型之间的差异以及它们如何工作对于理解使用模型的交易策略至关重要。 在描述了三种事务处理模型之后,我将介绍适用于大多数业务应用程序的四种事务处理策略,从简单的Web应用程序到大型高速事务处理系统。 交易策略系列中的后续文章将详细介绍这些策略。

    本地交易模式

    本地事务模型的名称来自于以下事实:事务由基础数据库资源管理器管理,而不是由应用程序在其中运行的容器或框架管理。在此模型中,您管理连接而不是事务 。 从“ 了解事务陷阱 ”中了解到,当使用对象关系映射框架(例如Hibernate,TopLink或Java Persistence API(JPA))进行数据库更新时,将无法使用本地事务模型。 在使用数据访问对象(DAO)或基于JDBC的框架和数据库存储过程时,您仍然可以应用它。

    您可以通过以下两种方式之一使用Local Transaction模型:让数据库管理连接,或以编程方式管理连接。 为了让数据库管理连接,请将JDBC Connection对象上的autoCommit属性设置为true (默认值),该属性告诉基础数据库管理系统(DBMS)在完成插入,更新或删除操作后提交事务。 ,如果失败则回滚工作。 清单1中说明了这种技术,该技术将股票交易订单插入TRADE表中:

    清单1.一次更新的本地事务
    public class TradingServiceImpl { public void processTrade(TradeData trade) throws Exception { Connection dbConnection = null; try { DataSource ds = (DataSource) (new InitialContext()).lookup("jdbc/MasterDS"); dbConnection = ds.getConnection(); dbConnection.setAutoCommit(true); Statement sql = dbConnection.createStatement(); String stmt = "insert into TRADE ..."; sql.executeUpdate(stmt1); } finally { if (dbConnection != null) dbConnection.close(); } } }

    请注意,在清单1中, autoCommit值设置为true ,向DBMS指示应该在每个数据库语句之后提交本地事务。 如果您在逻辑工作单元(LUW)中只有一个数据库维护活动,则此技术可以正常工作。 但是,假设清单1中所示的processTrade()方法还更新了ACCT表中的余额以反映贸易订单的价值。 在这种情况下,这两个数据库操作将彼此独立,在更新ACCT表之前,将TRADE表的插入操作提交给数据库。 如果对ACCT表的更新失败,将没有机制将插入回滚到TRADE表,从而导致数据库中的数据不一致。

    这种情况导致第二种技术:以编程方式管理连接。 在这种技术中,您可以将Connection对象上的autoCommit属性设置为false然后手动提交或回滚连接。 清单2演示了这种技术:

    清单2.具有多个更新的本地事务
    public class TradingServiceImpl { public void processTrade(TradeData trade) throws Exception { Connection dbConnection = null; try { DataSource ds = (DataSource) (new InitialContext()).lookup("jdbc/MasterDS"); dbConnection = ds.getConnection(); dbConnection.setAutoCommit(false); Statement sql = dbConnection.createStatement(); String stmt1 = "insert into TRADE ..."; sql.executeUpdate(stmt1); String stmt2 = "update ACCT set balance..."; sql.executeUpdate(stmt2); dbConnection.commit(); } catch (Exception up) { dbConnection.rollback(); throw up; } finally { if (dbConnection != null) dbConnection.close(); } } }

    注意,在清单2中, autoCommit属性设置为false ,通知底层DBMS将在代码(而不是数据库)中管理连接。 在这种情况下,如果一切正常,则必须在Connection对象上调用commit()方法。 否则,如果发生异常,则调用rollback()方法。 通过这种方式,您可以在同一工作单元中协调两个数据库活动。

    尽管“本地交易”模型在当今时代似乎有些过时,但是对于我将在本文结尾处介绍的主要交易策略之一,它是一个重要的元素。

    程序交易模型

    程序性交易模型的名称来自开发人员负责管理交易的事实。 与本地事务模型不同,在程序化事务模型中,您管理事务并与基础数据库连接隔离。

    与清单2中的示例类似,使用该模型,开发人员负责从事务管理器获取事务,启动事务,提交事务,以及(如果发生异常)回滚事务。 您可能会猜到,这会导致很多容易出错的代码,这些代码往往会妨碍应用程序中的业务逻辑。 但是,某些交易策略要求使用程序化交易模型。

    尽管概念相同,但是Spring框架和EJB 3.0规范之间Programmatic Transaction模型的实现有所不同。 我将首先使用EJB 3.0演示该模型的实现,然后使用Spring Framework展示相同的数据库更新。

    EJB 3.0的程序化事务

    在EJB 3.0中,可以通过在javax.transaction.UserTransaction上执行Java命名和目录接口(JNDI)查找来从事务管理器(换句话说,容器)中获取事务。 一旦有了UserTransaction ,就可以在发生错误时调用begin()方法启动事务, commit()方法提交事务,以及rollback()方法回滚事务。 在此模型中,容器将不会自动提交或回滚事务。 开发人员可以通过执行数据库更新的Java方法对该行为进行编程。 清单3显示了使用JPA的EJB 3.0的Programmatic Transaction模型的示例:

    清单3.使用EJB 3.0的程序化事务
    @Stateless @TransactionManagement(TransactionManagementType.BEAN) public class TradingServiceImpl implements TradingService { @PersistenceContext(unitName="trading") EntityManager em; public void processTrade(TradeData trade) throws Exception { InitialContext ctx = new InitialContext(); UserTransaction txn = (UserTransaction)ctx.lookup("UserTransaction"); try { txn.begin(); em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId()); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals("BUY")) { acct.setBalance(currentBalance - tradeValue); } else { acct.setBalance(currentBalance + tradeValue); } txn.commit(); } catch (Exception up) { txn.rollback(); throw up; } } }

    在具有无状态会话Bean的Java平台企业版(Java EE)容器环境中使用程序化事务模型时,必须告诉容器您正在使用程序化事务。 您可以通过使用@TransactionManagement批注并将事务类型设置为BEAN 。 如果不使用此批注,则容器将假定您正在使用声明式事务管理( CONTAINER ),这是EJB 3.0的默认事务类型。 当您在无状态会话Bean上下文之外的客户端层中使用编程事务时,不需要设置事务类型。

    与Spring进行程序化交易

    Spring框架有两种实现程序化事务模型的方式。 一种方法是通过Spring TransactionTemplate ,另一种方法是直接使用Spring 平台事务管理器 。 因为我不喜欢匿名内部类和难以理解的代码,所以我将使用第二种技术来说明Spring中的Programmatic Transaction模型。

    Spring至少有九名平台事务管理器。 您最可能使用的最常见的是DataSourceTransactionManager , HibernateTransactionManager , JpaTransactionManager和JtaTransactionManager 。 我的代码示例使用JPA,因此我将显示JpaTransactionManager的配置。

    要在Spring中配置JpaTransactionManager ,只需使用org.springframework.orm.jpa.JpaTransactionManager类在应用程序上下文XML文件中定义Bean,然后添加对JPA实体管理器Factory Bean的引用。 然后,假设包含您的应用程序逻辑的类是由Spring管理的,将事务管理器注入到bean中,如清单4所示:

    清单4.定义Spring JPA事务管理器
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <bean id="tradingService" class="com.trading.service.TradingServiceImpl"> <property name="txnManager" ref="transactionManager"/> </bean>

    如果Spring不管理应用程序类,则可以通过在Spring上下文中使用getBean()方法来获取对方法中事务管理器的引用。

    在源代码中,您现在可以使用平台管理器来获取交易。 完成所有更新后,您可以调用commit()方法来提交事务,或调用rollback()方法来回滚事务。 清单5演示了这种技术:

    清单5.使用Spring JPA事务管理器
    public class TradingServiceImpl { @PersistenceContext(unitName="trading") EntityManager em; JpaTransactionManager txnManager = null; public void setTxnManager(JpaTransactionManager mgr) { txnManager = mgr; } public void processTrade(TradeData trade) throws Exception { TransactionStatus status = txnManager.getTransaction(new DefaultTransactionDefinition()); try { em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId()); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals("BUY")) { acct.setBalance(currentBalance - tradeValue); } else { acct.setBalance(currentBalance + tradeValue); } txnManager.commit(status); } catch (Exception up) { txnManager.rollback(status); throw up; } } }

    注意清单5中的Spring Framework和EJB 3.0之间的区别。 在Spring中,通过在平台事务管理器上调用getTransaction()方法来检索(并因此开始)事务。 匿名DefaultTransactionDefinition类包含有关事务及其行为的详细信息,包括事务名称,隔离级别,传播模式(事务属性)和事务超时(如果有)。 在这种情况下,我仅使用默认值,即名称的空字符串,基础DBMS的默认隔离级别(通常为READ_COMMITTED ),transaction属性的PROPAGATION_REQUIRED以及DBMS的默认超时。 还要注意, commit()和rollback()方法是使用平台事务管理器而不是事务来调用的(EJB就是这种情况)。

    声明式交易模型

    声明性事务模型(也称为容器管理事务 (CMT))是Java平台中最常见的事务模型。 在此模型中,容器环境负责启动,提交和回滚事务。 开发人员仅负责指定交易行为。 本系列第一篇文章中讨论的大多数交易陷阱都与声明式交易模型相关。

    Spring Framework和EJB 3.0均使用批注来指定事务行为。 Spring使用@Transactional批注,而EJB 3.0使用@TransactionAttribute批注。 当您使用声明式事务模型时,容器不会根据检查的异常自动回滚事务。 开发人员必须指定发生受检查的异常时回退事务的位置和时间。 在Spring框架中,您可以使用@Transactional批注上的rollbackFor属性来指定它。 在EJB中,可以通过在SessionContext上调用setRollbackOnly()方法来指定它。

    清单6说明了EJB的声明式事务模型的用法:

    清单6.使用EJB 3.0的声明式事务
    @Stateless public class TradingServiceImpl implements TradingService { @PersistenceContext(unitName="trading") EntityManager em; @Resource SessionContext ctx; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void processTrade(TradeData trade) throws Exception { try { em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId()); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals("BUY")) { acct.setBalance(currentBalance - tradeValue); } else { acct.setBalance(currentBalance + tradeValue); } } catch (Exception up) { ctx.setRollbackOnly(); throw up; } } }

    清单7展示了Spring框架中声明式事务模型的使用:

    清单7.使用Spring的声明式事务
    public class TradingServiceImpl { @PersistenceContext(unitName="trading") EntityManager em; @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class) public void processTrade(TradeData trade) throws Exception { em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId()); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals("BUY")) { acct.setBalance(currentBalance - tradeValue); } else { acct.setBalance(currentBalance + tradeValue); } } }

    交易属性

    除了回滚指令之外,还必须指定transaction属性 ,该属性定义事务的行为方式。 Java平台支持六种事务属性,无论您使用的是EJB还是Spring框架:

    Required Mandatory RequiresNew Supports NotSupported Never

    在描述这些事务属性中的每一个时,我将使用一种应用了该事务属性的虚拟方法methodA() 。

    如果为methodA()指定了Required事务属性,并且在现有事务的范围内调用methodA() ,则将使用现有事务范围。 否则, methodA()将启动新事务。 如果事务是由methodA()开始的,那么它也必须由methodA()终止(提交或回滚methodA() 。 这是最常用的事务属性,并且是EJB 3.0和Spring的默认属性。 不幸的是,在许多情况下,它的使用不正确,从而导致数据完整性和一致性问题。 对于本系列后续文章中涉及的每种交易策略,我将更详细地讨论该交易属性的使用。

    如果为methodA()指定了Mandatory事务属性,并且在现有事务的作用域下调用methodA() ,则将使用现有事务作用域。 但是,如果在没有事务上下文的情况下调用methodA() ,则将引发TransactionRequiredException ,指示在调用methodA()之前必须存在事务。 此事务属性在本文下一节介绍的“ 客户编排”事务策略中使用。

    RequiresNew事务属性是一个有趣的属性。 我经常发现此属性被滥用或误解。 如果为methodA()指定了RequiresNew事务属性,并且在有或没有事务上下文的情况下调用methodA()则新的事务将始终由methodA()启动(和终止methodA() 。 这意味着,如果在另一个事务(例如称为Transaction1 )的上下文中调用methodA() ,则Transaction1将被挂起,而新的事务(称为Transaction2 )将被启动。 一旦methodA()结束, Transaction2然后提交或回滚,并且Transaction1继续。 这显然违反了交易的ACID(原子性,一致性,隔离性,持久性)属性(特别是原子性)。 换句话说,所有数据库更新不再包含在单个工作单元中。 如果要回滚Transaction1 ,则由Transaction2提交的更改将保持提交状态。 如果是这样,此交易属性有什么用? 如本系列第一篇文章所述,此事务属性仅应用于独立于基础事务(在本例中为Transaction1 )的数据库操作(如审核或日志记录)。

    我发现大多数开发人员都不完全理解或赞赏Supports事务属性。 如果Supports用于指定事务属性methodA()和methodA()是一个现有事务,的范围内调用methodA()该事务的范围下将被执行。 但是,如果在没有事务上下文的情况下调用methodA() ,则不会启动任何事务。 此属性主要用于对数据库的只读操作。 在这种情况下,为什么不指定NotSupported事务属性(在下一段中描述)呢? 毕竟,该属性保证该方法将在没有事务的情况下运行。 答案很简单。 在现有事务的上下文中调用查询操作将导致从数据库事务日志中读取数据(换句话说,更新的数据),而在没有事务作用域的情况下运行将使查询从表中读取未更改的数据。 例如,如果您要在TRADE表中插入新的交易订单,然后(在同一交易中)检索所有交易订单的列表,则未提交的交易将出现在列表中。 但是,如果改用NotSupported事务属性之类的方法,则会导致数据库查询从表而不是事务日志中读取。 因此,在前面的示例中,您不会看到未提交的交易。 这不一定是一件坏事。 这取决于您的用例和业务逻辑。

    NotSupported事务属性指定,无论是否存在,被调用的方法都不会使用或启动事务。 如果为methodA()指定了NotSupported事务属性,并且在事务上下文中调用methodA() ,则该事务将挂起,直到methodA()结束。 methodA()结束时,然后恢复原始事务。 此事务属性只有很少的用例,它们主要涉及数据库存储过程。 如果您尝试在现有事务上下文的范围内调用数据库存储过程,并且该数据库存储过程包含BEGIN TRANS或者对于Sybase(在Sybase中,它以非链模式运行),将引发异常,表明新事务无法如果已经存在则启动。 (换句话说,不支持嵌套事务。)几乎所有容器都将Java事务服务(JTS)用作JTA的默认事务实现。 不支持嵌套事务的是JTS(本身不是Java平台)。 如果无法更改数据库存储过程,则可以使用NotSupported属性来挂起现有事务上下文,以避免此致命异常。 但是,这样做的影响是,您不再在同一LUW中对数据库进行原子更新。 这是一个折衷方案,但可以使您快速摆脱困境。

    Never事务属性可能是最有趣的。 它的行为与NotSupported事务属性相同,但有一个重要区别:如果使用Never事务属性调用某个方法时,如果存在事务上下文,则会引发异常,指示您调用该方法时不允许事务。 我能够针对该事务属性提出的唯一用例是进行测试。 当您调用特定方法时,它提供了一种快速简便的方法来验证事务是否存在。 如果使用Never事务属性,并且在调用有问题的方法时收到异常,则说明存在事务。 如果允许执行该方法,则说明不存在事务。 这是确保您的交易策略稳定可靠的好方法。

    交易策略

    本文中描述的交易模型构成了我将要介绍的交易策略的基础。 在您建立交易策略之前,必须充分了解模型之间的差异以及它们如何工作,这一点很重要。 可以在大多数业务应用程序场景中使用的主要交易策略是:

    客户编排交易策略 API层交易策略 高并发交易策略 高速处理交易策略

    我将在这里总结每种策略,并在本系列的后续文章中详细讨论它们。

    当来自客户端层的多个基于服务器或基于模型的调用完成单个工作单元时,将使用“ 客户端编排”事务策略。 在这方面,客户端层可以引用从Web框架,门户应用程序,桌面系统或在某些情况下从工作流产品或业务流程管理(BPM)组件发出的调用。 本质上,客户端层拥有完成特定请求所需的处理流程和“步骤”。 例如,要下达交易订单,假设您需要将交易插入数据库中,然后更新客户的帐户余额以反映交易的价值。 如果应用程序的API层粒度太细,则必须从客户端层调用这两种方法。 在这种情况下,事务性工作单元必须驻留在客户端层中,以确保原子工作单元。

    当您具有粗粒度的方法作为后端功能的主要入口点时,将使用API层事务策略。 (打电话给他们服务 ,如果你想。)在这种情况下,客户(无论是基于网络的,基于Web的服务,基于消息的,甚至桌面)作出后端单一呼叫来执行特定的请求。 使用上一段中的贸易订单方案,在这种情况下,您将拥有客户端层调用的单个入口点方法(例如,称为processTrade() )。 然后,该单一方法将包含插入交易订单和更新帐户所需的业务流程。 我给这个策略起了个名字,因为在大多数情况下,后端处理功能是通过使用接口或API向客户端应用程序公开的。 这是最常见的交易策略之一。

    高并发事务策略是API层事务策略的变体,用于不能支持API层中长时间运行的事务的应用程序(通常是由于性能或可伸缩性需求)。 顾名思义,从用户角度来看,此策略主要用于支持高度并发性的应用程序中。 在Java平台中,事务相当昂贵。 根据您使用的数据库,它们可能导致数据库锁定,占用资源,从吞吐量的角度降低应用程序的速度,甚至在某些情况下甚至导致数据库死锁。 此事务处理策略背后的主要思想是缩短事务处理范围,以便您最小化数据库中的锁,同时仍为任何给定的客户端请求维护原子工作单元。 在某些情况下,您可能需要重构应用程序逻辑以支持此事务策略。

    高速处理交易策略可能是最极端的交易策略。 当您需要从应用程序中获得绝对最快的处理时间(从而获得吞吐量)并且仍在处理过程中保持某种程度的事务原子性时,可以使用它。 尽管从数据完整性和一致性的角度来看,此策略会带来少量风险,但是如果正确实施,它是Java平台中最快的事务策略。 在这里介绍的四种策略中,这可能也是最困难,最麻烦的交易策略。

    结论

    从该概述中可以看出,制定有效的交易策略并不总是一件容易的事。 解决数据完整性和一致性问题需要考虑许多因素,选项,模型,框架,配置和技术。 在我从事应用程序和事务处理的多年中,我发现尽管模型,选项,设置和配置的组合看起来令人麻木,而且相当压倒性,但实际上,只有几种选项和设置的组合才有意义。大多数用例。 我已经开发并将在本系列后续文章中详细讨论的四种交易策略应该涵盖您在Java平台上可能会遇到的大多数业务应用程序开发场景。 不过请注意:这些策略不是简单的“银弹”嵌入式解决方案。 在某些情况下,可能需要进行源代码重构或应用程序重新设计才能实现其中的某些功能。 当出现这种情况时,您只需要问自己:“我的数据的完整性和一致性有多重要?” 在大多数情况下,与不良数据相关的风险和成本相比,重构工作显得苍白无力。


    翻译自: https://www.ibm.com/developerworks/java/library/j-ts2/index.html

    相关资源:微信小程序源码-合集6.rar
    Processed: 0.012, SQL: 10