java - 使用 Spring 框架以原子方式维护服务层事务和数据库日志记录

标签 java spring spring-mvc transactions transactional

我有一个使用 Spring 和 Hibernate 实现的 Web 应用程序。应用程序中的典型 Controller 方法如下所示:

@RequestMapping(method = RequestMethod.POST)
public @ResponseBody
Foo saveFoo(@RequestBody Foo foo, HttpServletRequest request) throws Exception {
    // authorize
    User user = getAuthorizationService().authorizeUserFromRequest(request);
    // service call
    return fooService.saveFoo(foo);
}

典型的服务类如下所示:

@Service
@Transactional
public class FooService implements IFooService {

    @Autowired
    private IFooDao fooDao;

    @Override
    public Foo saveFoo(Foo foo) {
        // ...
    }
}

现在,我想创建一个 Log 对象,并在每次保存 Foo 对象时将其插入到数据库中。这些是我的要求:

  • Log 对象应包含来自授权User 对象的userId
  • Log 对象应该包含来自 HttpServletRequest 对象的一些属性。
  • 保存操作和日志创建操作应该是原子的。 IE。如果一个 foo 对象保存在对象中,我们应该在数据库中有一个相应的日志,指示用户和操作的其他属性。

由于事务管理是在服务层处理的,创建日志并将其保存在 Controller 中违反了原子性要求。

我可以将 Log 对象传递给 FooService 但这似乎违反了关注点分离原则,因为日志记录是一个横切关注点。

我可以将事务注释移动到 Controller ,这在我读过的许多地方都没有建议。

我还阅读了有关使用 spring AOP 和拦截器完成工作的信息,对此我经验很少。但他们使用的信息已经存在于服务类中,我无法弄清楚如何将信息从 HttpServletRequest 或授权的 User 传递给该拦截器。

我很感激任何指导或示例代码来满足这种情况下的要求。

最佳答案

要解决您的问题需要执行多个步骤:

  1. 以非侵入式方式将 Log 对象传递给服务类。
  2. 创建基于 AOP 的拦截器以开始将日志实例插入数据库。
  3. 维护 AOP 拦截器(事务拦截器和日志拦截器)的顺序,以便首先调用事务拦截器。这将确保用户插入和日志插入在单个事务中发生。

<强>1。传递日志对象

您可以使用 ThreadLocal 来设置 Log 实例。

public class LogThreadLocal{
    private static ThreadLocal<Log> t = new ThreadLocal();

    public static void set(Log log){}
    public static Log get(){}
    public static void clear(){}
}

Controller:saveFoo(){
    try{
        Log l = //create log from user and http request.
        LogThreadLocal.set(l);
        fooService.saveFoo(foo);
    } finally {
        LogThreadLocal.clear();
    }
}

<强>2。日志拦截器 查看 spring AOP 的工作原理 ( http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop-api.html )

a) 创建一个注释(作为切入点),@Log 用于方法级别。此注释将放在要进行日志记录的服务方法上。

@Log
public Foo saveFoo(Foo foo) {}

b) 创建 org.aopalliance.intercept.MethodInterceptor 的实现,LogInteceptor(作为建议)。

public class LogInterceptor implements MethodInterceptor, Ordered{

    @Transactional
    public final Object invoke(MethodInvocation invocation) throws Throwable {
        Object r = invocation.proceed();
        Log l = LogThreadLocal.get();
        logService.save(l);
        return r;
    }
}

c) 连接切入点和顾问。

<bean id="logAdvice" class="com.LogInterceptor" />

<bean id="logAnnotation"    class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut">
    <constructor-arg type="java.lang.Class" value="" />
    <constructor-arg type="java.lang.Class" value="com.Log" />
</bean>

<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="advice" ref="logAdvice" />
    <property name="pointcut" ref="logAnnotation" />
</bean>

<强>3。拦截器的排序(事务和日志)

确保您实现了 org.springframework.core.Ordered 接口(interface)到 LogInterceptor 并从 getOrder() 方法返回 Integer.MAX_VALUE。在您的 spring 配置中,确保您的事务拦截器具有较低的订单值(value)。

因此,首先调用您的事务拦截器并创建一个事务。然后,您的 LogInterceptor 被调用。此拦截器首先进行调用(保存 foo),然后保存日志(从线程本地提取)。

关于java - 使用 Spring 框架以原子方式维护服务层事务和数据库日志记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33720432/

相关文章:

java - 使用简单的 jdbc 调用将数组作为输入参数传递给 Oracle 存储过程

java - Spring 3.0.5 Webapp 在 tomcat 中运行良好...不会在 JBoss EAP 5.1 中启动

java - 多个 Spring 项目,共享身份验证

javascript - 在 react 18 中使用 makeStyles Material UI 没有结果

java - IE11和Safari没有将授权 header 传递给后端

java - 工厂方法中的依赖注入(inject)导致 NullPointerException

java - 使用 Spring 数据调用自定义 mongoDB 查询失败

java - 使用 maven/eclipse 的 AspectJ 编译为 org/aspectj/bridge/IMessageHolder 抛出 NoClassDefFoundError

java - JToolTip 是否可以保留

java - 如何将 Switch 与字符串一起使用