关于事务的代码编写注意事项

以下内容为自己总结,望讨论更正

一、基础概念

SQL Server 数据库引擎将忽略内部事务的提交。根据最外部事务结束时采取的操作,将提交或者回滚内部事务。如果提交外部事务,也将提交内部嵌套事务。如果回滚外部事务,也将回滚所有内部事务,不管是否单独提交过内部事务。

嵌套事务的程序执行过程应该是:随着事务的执行,嵌套层数由一递增,之后逐渐递减到第一层。整个事务是否提交取决于最后一层是运行Commit(提交)还是Rollback(回滚)。

1、对 COMMIT TRANSACTION的每个调用都应用于最后执行的 BEGIN TRANSACTION。如果嵌套 BEGIN TRANSACTION 语句,那么 COMMIT 语句只应用于最后一个嵌套的事务,也就是在最内部的事务。即使嵌套事务内部的 COMMIT TRANSACTION transaction_name 语句引用外部事务的事务名称,该提交也只应用于最内部的事务。

2、ROLLBACK TRANSACTION 语句的 transaction_name 参数引用一组命名嵌套事务的内部事务是非法的,transaction_name 只能引用最外部事务的事务名称。如果在一组嵌套事务的任意级别执行使用外部事务名称的 ROLLBACK TRANSACTION transaction_name 语句,那么所有嵌套事务都将回滚。如果在一组嵌套事务的任意级别执行没有 transaction_name 参数的ROLLBACK TRANSACTION 语句,那么所有嵌套事务都将回滚,包括最外部事务。(此段意思表示:只允许嵌套层第一层时使用ROLLBACK回滚)

3、@@TRANCOUNT 函数记录当前事务的嵌套级别。每个 BEGIN TRANSACTION 语句使 @@TRANCOUNT 增加 1。每个 COMMIT TRANSACTION语句使 @@TRANCOUNT 减去 1。

A、没有事务名称的ROLLBACK TRANSACTION 语句将回滚所有嵌套事务,并使 @@TRANCOUNT 减小到 0。

B、使用一组嵌套事务中最外部事务的事务名称的 ROLLBACK TRANSACTION 将回滚所有嵌套事务,并使 @@TRANCOUNT 减小到 0。

C、在无法确定是否已经在事务中时,可以用 SELECT @@TRANCOUNT 确定 @@TRANCOUNT 是等于 1 还是大于 1。如果 @@TRANCOUNT 等于 0,则表明不在事务中。

注:以上内容摘自-- http://msdn.microsoft.com/zh-cn/library/ms189336.aspx

二、示例说明:

1、创建一个表格,用于插入数据的实验

create table TranTestTable(

T datetime

)

2、没有使用事务,程序运行会出错。但第一条语句还是插入了一条记录,第二条没有插入记录。

alter PROC TranTest2(@errorTag int output)

AS

BEGIN

       insert into TranTestTable values(getdate())

       insert into TranTestTable values('....')

END

3、见下面代码,存在两层事务的嵌套使用,TranTest1嵌套调用TranTest2,根据“一”中的1、2可以得出以下代码。调用TranTest1运行正常,没有插入任何记录。

alter PROC TranTest1(@errorTag int output)

AS

BEGIN

    set @errorTag = 0

    Begin transaction

    declare @errorTag2  int

    exec TranTest2 @errorTag2 output ----调用TranTest2存储过程

    if @errorTag2<0

    BEGIN

       set @errorTag = -1

       ROLLBACK TRANSACTION

       RETURN

    END

    IF @@TRANCOUNT > 0

       COMMIT TRANSACTION

END

--------- TranTest2代码------------

alter PROC TranTest2(@errorTag int output)

AS

BEGIN

    set @errorTag = 0

    Begin transaction

       insert into TranTestTable values(getdate())

       insert into TranTestTable values('....')

    if @@error <> 0

    BEGIN

       set @errorTag = -1

       COMMIT TRANSACTION

       RETURN

    END

    IF @@TRANCOUNT > 0

       COMMIT TRANSACTION

END

4、但TranTest2是一个独立的存储过程,也可能被直接嗲用(外层不嵌套事务),TranTest2被TranTest1调用和直接被调用的区别在于事务嵌套级别不同。直接被调用(不存在嵌套调用)时,此时TranTest2中就是事务的第一层,碰到异常情况应该运行ROLLBACK TRANSACTION使得整个事务回滚;被TranTest1调用(存在嵌套调用)时,TranTest2的层级(@@TRANCOUNT的值)为2,此时出现异常情况应该运行COMMIT TRANSACTION,将嵌套层数减1,并将事务交给上一层处理,最后这个事务是否提交取决于第一层最后是提交还是回滚;

    为了使得TranTest2存储过程具有通用性,可以根据嵌套层数决定是调用COMMIT还是ROLLBACK。如果是大于1,说明上面还有事务包着,此时事务应该交给上一层处理。如果是1,则需要回滚整个事务,因为没有其他地方可以将事务回滚了。

alter PROC TranTest2(@errorTag int output)

AS

BEGIN

    set @errorTag = 0

    Begin transaction

       insert into TranTestTable values(getdate())

       insert into TranTestTable values('....')

    if @@error <> 0

    BEGIN

       set @errorTag = -1

       IF @@TRANCOUNT > 1

           COMMIT TRANSACTION

       ELSE

           ROLLBACK TRANSACTION

       RETURN

    END

    IF @@TRANCOUNT > 0

       COMMIT TRANSACTION

END

5、最后将TranTest1像TranTest2一样改成具有通用性,因为TranTest1也不知道会被谁调用。

alter PROC TranTest1(@errorTag int output)

AS

BEGIN

    set @errorTag = 0

    Begin transaction

    declare @errorTag2  int

    exec TranTest2 @errorTag2 output

    if @errorTag2<0

    BEGIN

       set @errorTag = -1

       IF @@TRANCOUNT > 1

           COMMIT TRANSACTION

       ELSE

           ROLLBACK TRANSACTION

       RETURN

    END

    IF @@TRANCOUNT > 0

       COMMIT TRANSACTION

END 

三、总结整理

 

在存储过程中启动事务:

遵守一个原则,启动一个事务时@@TRAN COUNT的值和结束该层事务时的@@TRAN COUNT的值是相等的。在上一层的事务中是提交还是回滚,是根据内层返回的回滚提交标志决定的。

1、在事务的正常完成结束点。事务正常结束时@@TRAN COUNT减去1。

       IF @@TRANCOUNT > 0

              COMMIT TRANSACTION

A、如果外层没有嵌套事务,则@@TRANCOUNT=1正常提交;

B、如果外层嵌套了事务,则@@TRANCOUNT>1,运行COMMIT使得@ @TRANCOUNT-1,并且返回正确系统运行正常信息,事务的真正提交交给外层事务。

2、在事务中途要退出事务,并且返回错误标志(回滚标志)。

       IF @@TRANCOUNT > 1

              COMMIT TRANSACTION

       ELSE

              ROLLBACK TRANSACTION

       RETURN

A、如果外层嵌套了事务,则满足IF @@TRANCOUNT > 1条件,运行COMMIT使得@@TRANCOUNT-1,并且返回错误信息,真正的回滚交给最上层事务(由上层根据这个错误信息决定回滚)。

B、如果外层没有嵌套事务,则@@TRANCOUNT=1,满足else,直接回滚;

<think>好的,我现在需要帮助用户解答关于Java代码实现员工工资计算时需要注意的问题。用户提到参考了两个引用,一个是员工工资管理系统的源码,另一个是Java基础中的抽象类和Calendar的使用。首先,我需要结合这两个引用内容,分析在编写工资计算代码时可能涉及的注意事项。 首先,引用[1]中的工资明细管理提到了多个工资组成部分,如基本工资、各种补贴、扣发事项等。这意味着在代码中需要精确处理各个字段的计算,避免遗漏或错误。例如,每个员工的工资可能由多个部分组成,需要正确累加和扣除。同时,引用[2]中提到了抽象类的使用,可能涉及到设计模式的应用,比如使用抽象类来定义工资计算的通用方法,让子类实现具体细节。 接下来,我需要考虑数据类型的选择。工资涉及金额,必须使用精确的数据类型,比如BigDecimal,避免使用float或double导致的精度丢失。这是常见的注意事项,必须提到。 然后,异常处理也是关键。例如,扣发工资不能超过应发总额,否则会出现负数,这需要校验。此外,空值处理也很重要,比如某个补贴字段可能为null,需要处理以避免NullPointerException。 业务规则校验方面,引用[1]提到了是否支付的状态,可能涉及到支付流程的控制,比如只有计算完成并确认无误后才能标记为已支付。同时,扣发事项可能有多种原因,需要灵活处理,可能涉及策略模式或规则引擎。 设计模式方面,引用[2]中的抽象类可以用来定义工资计算流程,比如抽象类定义计算步骤,具体子类实现各部分的具体计算逻辑,如基本工资、补贴、扣发等。这样提高代码的可扩展性和维护性。 并发问题也需要考虑,特别是在多线程环境下计算工资时,需确保数据的一致性,比如使用同步机制或线程安全的数据结构。 Calendar的使用在引用[2]中提到,可能用于计算工龄补贴或考勤相关的全勤补贴。处理日期时要注意时区和闰年等问题,确保计算准确。 权限控制方面,引用[1]中的系统可能有不同角色,如HR和普通员工,计算和修改工资的权限需要分开,避免越权操作。 日志记录和审计也是必要的,方便追踪工资计算的修改记录,特别是在出现问题时能够追溯。 最后,可配置化的参数,如税率、补贴标准等,应设计为可配置的,避免硬编码,方便后期调整。 总结以上几点,我需要将这些注意事项分门别类,逐一解释,确保用户能清晰理解每个注意事项的原因和解决方法。同时,要结合用户提供的引用内容,让回答更具参考价值,并生成相关问题,引导用户深入思考。</think>在Java代码实现员工工资计算时,需重点关注以下10个方面及对应的技术实现要点: 1. **数据类型精度控制** - 必须使用`BigDecimal`处理金额计算,避免浮点型精度丢失 ```java BigDecimal baseSalary = new BigDecimal("5000.00"); //基本工资 BigDecimal allowance = baseSalary.multiply(new BigDecimal("0.1")); //10%补贴 ``` 2. **业务规则校验**(参考引用[1]的扣发逻辑) - 实现参数校验框架: ```java if (deduction.compareTo(totalIncome) > 0) { throw new IllegalArgumentException("扣发金额不能超过应发总额"); } ``` 3. **领域模型设计**(参考引用[2]的抽象类应用) - 使用抽象类定义工资计算框架: ```java public abstract class SalaryCalculator { public abstract BigDecimal calculateSubsidy(Employee employee); protected final BigDecimal calculateInsurance(BigDecimal income) { // 统一五险一金计算逻辑 } } ``` 4. **并发处理** - 对共享数据使用线程安全结构: ```java ConcurrentHashMap<String, BigDecimal> departmentBonusMap = new ConcurrentHashMap<>(); ``` 5. **日期计算**(参考引用[2]的Calendar使用) - 工龄计算示例: ```java Calendar hireDate = Calendar.getInstance(); hireDate.set(2018, Calendar.JANUARY, 1); long years = ChronoUnit.YEARS.between(hireDate.toInstant(), Instant.now()); ``` 6. **事务管理** - 工资发放事务注解: ```java @Transactional(rollbackFor = Exception.class) public void processPayment(Long employeeId) { // 更新支付状态和账户余额 } ``` 7. **审计追踪** - 使用AOP记录操作日志: ```java @Aspect public class SalaryAuditAspect { @AfterReturning("execution(* calculate*(..))") public void logCalculation(JoinPoint jp) { // 记录计算参数和结果 } } ``` 8. **权限控制**(参考引用[1]的支付状态管理) - Spring Security权限注解: ```java @PreAuthorize("hasRole('HR')") public void updateSalary(Employee employee) { // 工资修改逻辑 } ``` 9. **配置化管理** - 将补贴系数存入数据库: ```java @Value("${subsidy.position.manager}") private BigDecimal managerPositionSubsidy; ``` 10. **异常处理体系** - 自定义异常处理: ```java public class SalaryCalculationException extends RuntimeException { public SalaryCalculationException(String message) { super("工资计算异常: " + message); } } ``` **特别注意事项**: 1. 社保公积金计算应遵循最新政策,建议建立独立策略类 2. 个税计算需实现累进税率算法,可使用责任链模式 3. 批量计算时采用分页处理,避免内存溢出 4. 数值比较必须使用`compareTo()`方法而非运算符
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值