java 事务异常处理

java 事务异常处理

问题称述

​ 一次在开发过程中,我需要用到事务;由于工程架构规则导致不能按照常用的事务处理方式。

Controller层代码示例

	@RequestMapping(value = "/api/abc",metabchod=RequestMethod.POST)
	public @ResponseBody ReturnModel postabc(@RequestBody PostabcModel account) {
		return service.postabc(account,getIpAddr(requestThreadLocal.get()));
	}

Service层代码示例

@Override
	public ReturnModel postAccount(PostabcModel account,String ip) { 
		ReturnModel model = new ReturnModel();
		try {
			//	业务逻辑代码
		} catch (Exception e) {
			log("异常", e, model); 
			String content = "异常" + e.toString();
			sendMessgeToDingGroup(content,1);
		}
		return model;
	}

保持项目格式不变,Controller不能处理异常,但是我们知道,声明式事务 (基于注解@Transactional)的回滚是必须抛出运行异常(抛出的异常为RuntimeException的子类(Errors也会导致事务回滚))

问题分析

  • 若回滚,必须抛出异常(抛出的异常为RuntimeException的子类(Errors也会导致事务回滚))
  • Controller不能处理异常;
  • 抛出的异常必须是运行是异常;

提出解决方案

方案一:

​ 将业务代码单独抽出,写一个方法在service方法里面处理异常;代码如下:

@Override
public ReturnModel postAccount(PostabcModel account,String ip) { 
	ReturnModel model = new ReturnModel();
	try {
		transactionProcessing()
	} catch (Exception e) {
		log("异常", e, model); 
		String content = "异常" + e.toString();
		sendMessgeToDingGroup(content,1);
	}
	return model;
}
@Transactional
public void transactionProcessing()throws Exception {
        try {
			//	业务逻辑代码
		} catch (Exception e) {
			logger.error("发生异常啦: ", e);
			throw e;
		}
}
方案二:

​ 手动回滚

@Override
@Transactional
public ReturnModel postAccount(PostabcModel account,String ip) { 
	ReturnModel model = new ReturnModel();
	try {
		//	业务逻辑代码
	} catch (Exception e) {
		log("异常", e, model); 
		String content = "异常" + e.toString();
        //手动回滚
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}
	return model;
}

总结问题及拓展

事务配置:
包扫描
<context:component-scan base-package="com.coinMall.service"/>


<!-- 事务管理 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
  	<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />


1.必须是public修饰
2.必须是service层接口实现类(不能调方法)
3.必须抛出运行时异常(RuntimeException……)
4.不需要throws  Exception 使用这个就可以手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
解决问题:
方案一:

  问题未解决,单独写一个方法,抛出异常并不会回滚;

失败原因

  jdk的动态代理被代理对象必须实现接口,抽取出来的方法没有实现接口,导致事务处理的失败。

修改方案

  采用CGLib动态代理,

方案二:

  完美解决问题;

总结及拓展:
事务

  ACID是原子性(atomicity)一致性(consistency)隔离性 (isolation)和持久性(durability)

    事务的原子性:表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。
    事务的一致性:表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。
    事务的隔离性:表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。
    事务的持久性:表示已提交的数据在事务执行失败时,数据的状态都应该正确。

动态代理

​   我们都知道spring的事务处理用到了AOP的思想,而AOP是基于动态动态去实现的,显然我们所用到的事务(声明式事务)也是基于动态代理实现的

​   我们经常说的动态代理有两种,一种是基于jdk的动态代理,另一种是CGLib的动态代理,那么这两种有啥去别呢?

jdk动态代理

使用JDK动态代理的五大步骤:

  • 通过实现InvocationHandler接口来自定义自己的InvocationHandler;
  • 通过Proxy.getProxyClass获得动态代理类;
  • 通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class);
  • 通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入;

​ 实现自己的InvocationHandler

public class MyInvocationHandler implements InvocationHandler {  
      
    // 目标对象   
    private Object target;  
      
    /** 
     * 构造方法 
     * @param target 目标对象  
     */  
    public MyInvocationHandler(Object target) {  
        this.target = target;  
    }  
  
    /** 
     * 执行目标对象的方法 
     */  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
          
        // 在目标对象的方法执行之前简单的打印一下  
        System.out.println("------------------before------------------");  
          
        // 执行目标对象的方法  
        Object result = method.invoke(target, args);  
          
        // 在目标对象的方法执行之后简单的打印一下  
        System.out.println("-------------------after------------------");  
          
        return result;  
    }  
  
    /** 
     * 获取目标对象的代理对象 
     * @return 代理对象 
     */  
   /* public Object getProxy() {  
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),   
                target.getClass().getInterfaces(), this);  
    }  */
}  

​ 被代理对象的接口以及实现方法

public interface IHello {
    void sayHello();
}

public class HelloImpl implements IHello {
    @Override
    public void sayHello() {
        System.out.println("----------------Hello world!--------------");
    }
}

​ 测试类

/**
 * 使用JDK动态代理的五大步骤:
 * 1.通过实现InvocationHandler接口来自定义自己的InvocationHandler;
 * 2.通过Proxy.getProxyClass获得动态代理类
 * 3.通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)
 * 4.通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入
 * 5.通过代理对象调用目标方法
 */
public class MyProxyTest {
    public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        // =========================第一种==========================
        // 1、生成$Proxy0的class文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 2、获取动态代理类
        Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
        // 3、获得代理类的构造函数,并传入参数类型InvocationHandler.class
        Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
        // 4、通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
        IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
        // 5、通过代理对象调用目标方法
        iHello1.sayHello();
 
        // ==========================第二种=============================
        /**
         * Proxy类中还有个将2~4步骤封装好的简便方法来创建动态代理对象,
         *其方法签名为:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
         */
        IHello  iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加载接口的类加载器
                new Class[]{IHello.class}, // 一组接口
                new MyInvocationHandler(new HelloImpl())); // 自定义的InvocationHandler
        iHello2.sayHello();
    }
}

​ 运行结果

------------------before------------------
----------------Hello world!--------------
-------------------after------------------
CGLib动态代理

​ CGLIB是通过继承实现代理,也就是生成一个类继承被代理类;所以当你的方法不能被继承时,该方法就不能被动态代理**(像final,private,protected这个比较特殊)**

​ 实现一个业务类,注意,这个业务类并没有实现任何接口

public class HelloService {
 
    public HelloService() {
        System.out.println("HelloService构造");
    }
 
    /**
     * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
     */
    final public String sayOthers(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }
 
    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}

​ 自定义MethodInterceptor:

/**
 * 自定义MethodInterceptor
 */
public class MyMethodInterceptor implements MethodInterceptor{
 
    /**
     * sub:cglib生成的代理对象
     * method:被代理对象方法
     * objects:方法入参
     * methodProxy: 代理方法
     */
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}

​ 生成CGLIB代理对象调用目标方法:

public class Client {
    public static void main(String[] args) {
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置enhancer对象的父类
        enhancer.setSuperclass(HelloService.class);
        // 设置enhancer的回调对象
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        HelloService proxy= (HelloService)enhancer.create();
        // 通过代理对象调用目标方法
        proxy.sayHello();
    }
}

​ 运行结果:

HelloService构造
======插入前置通知======
HelloService:sayHello
======插入后者通知======
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Spring Boot 中处理事务异常是很重要的。Spring Boot 提供了一种简单而强大的方式来管理事务,并且可以通过捕获异常并采取适当的措施来处理事务中的异常。 首先,确保你的项目中已经引入了 Spring Boot Starter Data JPA 或者 Spring Boot Starter JDBC,这样你就能够使用 Spring 的事务管理功能了。下面是一个简单的示例: ```java @Service public class MyService { @Autowired private MyRepository repository; @Transactional public void performTransactionalOperation() { // 在这里执行数据库操作 } } ``` 在上面的示例中,`@Transactional` 注解标记了方法 `performTransactionalOperation()`,表示该方法应该在一个事务中执行。如果方法执行过程中发生了任何异常事务将回滚。 当处理事务时,有时候需要捕获特定类型的异常并采取相应的措施。你可以使用 `try-catch` 块来捕获异常并进行处理,或者可以使用 `@ExceptionHandler` 注解来处理特定类型的异常。下面是一个示例: ```java @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(CustomException.class) public ResponseEntity<String> handleCustomException(CustomException ex) { // 处理自定义异常 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage()); } @ExceptionHandler(Exception.class) public ResponseEntity<String> handleException(Exception ex) { // 处理其他异常 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred."); } } ``` 在上面的示例中,`@ControllerAdvice` 注解标记了类 `GlobalExceptionHandler`,表示这个类是一个全局异常处理器。`@ExceptionHandler` 注解用于处理特定类型的异常,你可以根据需要定义多个方法来处理不同类型的异常。 希望以上信息对你有所帮助!如果你有更多问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值