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

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

@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对象时将其插入数据库.这些是我的要求:

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

由于事务管理是在服务层中处理的,因此创建日志并将其保存在控制器中会违反原子性要求.

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

我可以将事务注释移至控制器,在我读过的很多地方都没有建议这样做.

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

我感谢任何指导或示例代码都能满足这种情况下的要求.

解决方法:

有多个步骤可以解决您的问题:

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

1.传递日志对象

您可以使用ThreadLocal设置日志实例.

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.拦截器的顺序(事务和日志)

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

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

上一篇:设计规则


下一篇:spring-事务服务中的ConstraintViolationException不回滚