数据库审计框架内部原理解析

​本文介绍Spring Security和Spring Data Jpa集成实现数据库操作审计功能的原理。

本篇中使用的示例同如何对数据库操作进行审计?

Repository

首先Spring Data Jpa中的Repository资源是接口类型的。接口类型的文件在Java中主要用于定义规范。一般的接口中是不包含具体的实现代码和逻辑的。

如果要实现接口中定义的功能,一般需要:

  1. 实现该接口,在具体实现类中增加相关的处理逻辑;

  2. 使用动态代理机制,比如Java Dynamic或CGLIB。动态代理是基于Java规范,在运行时创建相关的Class对象并实例化。真正的处理逻辑都是在代理类中,因此在源代码中是找不到具体的实现逻辑的。

Spring Data Jpa中对Repository的处理使用的就是动态代理技术,底层是Java Dynamic。

下图展示的是一个名为CommentRepository对象在内存中的样子:

从图中可以看出,Spring Data Jpa是基于AOP框架开发的。那为何我们又说Spring Data Jpa是基于动态代理技术实现的框架呢?因为这两个本来就是一回事,AOP底层用的还是动态代理技术……

AOP的整个框架的核心是下图中的这个列表。正常运行时,会依次执行Pointcut判定成功的Interceptor类。Spring Data中的事务也是通过AOP实现的,实现的方式就是图中下标为3的那个Interceptor,名字叫:TransactionInterceptor

在所有AOP切面执行完成后,如果没有被强制返回(比如某一个切面方法抛出异常了),则最终会调用target对象的对应方法。

因此在JdkDynamicAopProxy这个类中,包含了一个名为targetSource的属性,如下图所示:

这个targetSource对一个类型是SimpleJpaRepository的对象进行了封装。别看这个类的名字叫SimpleJpaRepository,其实它里面包含的方法多着呢。Spring Data Jpa中默认提供的大部分数据访问方法的逻辑,都在它里面:

Entity

上面说了在Spring Data Jpa中,所有接口类型的Repository对象,最后都通过AOP/动态代理技术,生成了一个SimpleJpaRepository对象的代理类。那SimpleJpaRepository对象是不是内部保存了JdbcTemplate,并创建sql语句访问数据库呢?

其实不是,SimpleJpaRepository本身不处理和SQL相关的任何操作。在SimpleJpaRepository通过使用EntityManager对象对数据进行持久化。

比如save()方法:

 @Transactional
 @Override
 public <S extends T> S save(S entity) {
  if (entityInformation.isNew(entity)) {
   em.persist(entity);
   return entity;
  } else {
   return em.merge(entity);
  }
 }

这个内部属性em,就是一个类型为EntityManager的实例。

在Spring Data Jpa中,正常使用的EntityManager的实现是SessionImpl,继承关系简化后如下图所示:

EntityManager用于管理所有和数据库交互的操作。结合上面说的Repository内部结构能看出,对Repository的操作是具有事务属性的。

因此,从事务隔离性角度来考虑,EntityManager应该是线程独享的。在Spring Web应用中,用户的每个HTTP请求都会单独创建一个线程,这意味着EntityManager需要为每一个用户单独创建一个实例。

怎么实现呢?听起来好像特别适合使用ThreadLocal来管理了。当在Spring Web应用中使用Spring Data Jpa时,框架会在每次请求到来时,创建EntityManager实例,并保存在ThreadLocal中。后续该线程的所有Repository对数据库的访问,都共用这个EntityManager对象了。

给张图说明下。下面这张图展示的就是线程的所有ThreadLocal对象了。展开的那个元素的referent叫Transactional resources。它是一个HashMap对象,其中有个元素的value是EntityManagerHolder类型的,它的内部保存了一个SessionImpl实例。

SessionImplEntityManger的具体实现类,所以当前这个线程中所有和数据库进行交互的操作,都会使用这个SessionImpl对象。

另外需要注意的是,这个SessionImpl也是一个代理类(多看看就习惯了,Spring Data框架是AOP的天下,切面满天飞……)。

我们将SessionImpl展开,可以看到代理类底层的target对象。这个target对象就是真正的SessionImpl类的实例了:

在上图中,我们圈出了一个名为fastSessionServices的属性,这个属性展开的内容如下:

我们展开下eventListenerGroup_PERSIST,里面的结构如下图所示:

可以看到,其内部属性上保存了一个名为preCreates的HashMap对象。这个对象的key值是我们定义的Entity实体类:

package org.example.entity;
...

@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class Comment {
    @Id @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;
    ...
    @CreatedBy private String creator;
    @CreatedDate private Date createdDate;
    @LastModifiedBy private String modifier;
    @LastModifiedDate private Date ModifiedDate;
}

value是AuditingEntityListenertouchForCreate方法,这个方法就是为了补足创建人、创建时间、修改人、修改时间四个属性的。

 @PrePersist
 public void touchForCreate(Object target) {
  Assert.notNull(target, "Entity must not be null!");
  if (handler != null) {
   AuditingHandler object = handler.getObject();
   if (object != null) {
    object.markCreated(target);
   }
  }
 }

嗯……可以畅想一下:

用户在页面上提交创建评论的请求,请求到达服务器端。在服务器端处理后,最终调用SimpleJpaRepository方法,触发EntityManager(即SessionImpl)的persist方法持久化到数据库中。

SessionImpl内部保存了一个映射表。该映射表要求:如果保存的实体类是Comment类型的,在persist方法执行前,则需要先执行AuditingEntityListenertouchForCreate的方法。

这个touchForCreate方法,完成了将创建人、创建时间、修改人、修改时间四个字段补全的操作。

跑一下试试

首先在浏览器上输入,提交创建一条评论的请求。请求到达服务器端,可以Comment对象和审计相关的四个属性都是空的。

按照我们的分析,直接在SimpleJpaRepositorysave方法上增加断点,如下图所示,四个属性依旧是空的:

现在应该需要进入到SessionImpl中了,我们增加相关位置断点:

成功接住,依旧为空。下面程序应该要进入AuditingEntityListenertouchForCreate方法,开始补充这四个属性:

可以看出,AuditingEntityListener会调用一个名为jpaAuditingHandler的bean对象的markCreated方法。这个jpaAuditingHandler在Spring Data Jpa中就一个实现类AuditingHandler

AuditingHandlermarkCreated方法中增加断点,继续执行程序:

这个AuditingHandler对象,使用IoC容器的自动装配功能,将我们在Application中定义的SpringSecurityAuditorAware对象注入。后面的逻辑就不用跟了,闭着眼睛都能自己补全……

到此,和数据库审计相关的四个属性就补充进来了:

之后就是保存入库,没有啥特别需要说明的。

缺了的部分

本篇初略介绍了应用跑起来时,整个数据流是怎么处理的。还缺了一部分:整个框架在系统启动时是如何构建的,关键对象的属性又是进行初始化的?这个后续再补充吧。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镜悬xhs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值