使用Spring Data Repositories的审计信息的自定义JSON序列化

时间:2017-06-15 08:49:21

标签: java json jackson spring-data-jpa spring-data-rest

我使用带有Spring Data JPA和Data Rest的Spring boot 1.5.2实现公司内部REST服务。

问题

在使用Spring Data Rest-Repositories暴露某些域模型时,我正在寻找一种将对象序列化为字符串的有效方法。

上下文

我的域模型都来自BaseEntity,如下所示:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
  @Version
  private Long version;
}

有了这个,每个域模型都有公开的属性createdBycreateDatelastModifiedBylastModifiedDate,如下所示:

public class TestEntity extends BaseEntity { private String name; } 相应的JSON输出如下所示:

{
    "createdBy":
    {
        "name": "testEM",
        "contactInfo":
        {
            "title": null,
            "givenName": "GivenName",
            "surName": "Surname",
            "mail": "test@test.mail.de"
        },
        "function": "EMPLOYEE",
        "department":
        {
            "name": "mydep"
        }
    },
    "createdDate": "2017-06-12T11:49:17.013Z",
    "lastModifiedBy":
    {
        <same representation as "createdBy">
    },
    "lastModifiedDate": "2017-06-14T11:27:32.370Z",
    "name": "Hello,Name!",
    "new": false,
    "_links":
    {
        "self":
        {
            "href": "http://localhost:8080/testres/1"
        },
        "testEntity":
        {
            "href": "http://localhost:8080/testres/1{?projection}",
            "templated": true
        }
    }
}

我想要什么

现在,我希望缩短createdBylastModfifiedBy的表示形式,以便这些条目不包含User对象。相反,只应显示名称(来自User.getName()):

{
    "createdBy": "testEM",
    "createdDate": "2017-06-12T11:49:17.013Z",
    "lastModifiedBy": "testEM",
    "lastModifiedDate": "2017-06-12T11:49:17.013Z",
    ... // other properties
}

实现这一目标的最佳方法是什么?

我试过了:

  • 在用户实体上使用@JsonIdentityInfo - 这个没有任何影响
  • 通过@Bean Jackson2ObjectMapperBuilderCustomizer customizer() {...}为用户实体注册自定义(反)序列化程序 - 呈现{ "createdBy": { "content": "testEM"}}
  • 使用public User getCreatedBy()在我的BaseEntity类中注释被覆盖的方法@JsonSerialize(using= UserJsonSerializer.class) - 这会引发异常

    {
        "timestamp": 1497515751192,
        "status": 500,
        "error": "Internal Server Error",
        "exception": "org.springframework.http.converter.HttpMessageNotWritableException",
        "message": "Could not write content: Can not override serializer; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not override serializer",
        "path": "/testres/1"
    }
    
  • 我也读过有关使用@JsonView的内容,但是,对于我来说,如何为特定用例启用这些内容并不清楚

更新

我已经创建了一些投影,这是减少输出的开箱即用的支持方式。有关我编写的代码,请参阅此Gist。 有了这些,并将投影设置为摘录,条目列表显示正常。但是,当您请求localhost:8080/testRepo/1之类的特定资源时,您将获得未投影的输出。我知道Spring不会默认将投影应用于特定实体。因此,我们必须将请求参数?=projection=testProjection应用于每个请求。 由于这是可行的(因为应用程序不会公开)它可能没问题,但对其他人来说可能没有。所以问题仍然存在,我们如何以有效的方式为每种资源改变审计信息?

更新2

我再次阅读了Spring Data REST Documentation并偶然发现了这一段:

There is another route. If the Address domain object does not have it’s own repository definition, Spring Data REST will inline the data fields right inside the Person resource.

因此,当审核员的类型为UserRepository时,您必须公开User。 巧合的是,这是我在创建MWE时遇到的确切行为(最小的工作示例,无法上传到github,因为我在代理后面:()。 因此,在公开@RepositoryRestResource UserRepository extends JpaRepository<User, Long>的情况下,Spring会生成此JSON:

{
    "createdDate": "2017-06-12T11:49:17.013Z",
    "lastModifiedDate": "2017-06-14T11:27:32.370Z",
    "name": "Hello,EM!",
    "new": false,
    "_links":
    {
        "self":
        {
            "href": "http://localhost:8080/testRepo/1"
        },
        "testEntity":
        {
            "href": "http://localhost:8080/testRepo/1{?projection}",
            "templated": true
        },
        "lastModifiedBy":
        {
            "href": "http://localhost:8080/testRepo/1/lastModifiedBy"
        },
        "createdBy":
        {
            "href": "http://localhost:8080/testRepo/1/createdBy"
        }
    }
}

这种行为对我来说是可以接受的,所以请考虑解决这个问题。 如果有人有额外的输入,请随时发布!

非常感谢任何帮助!

1 个答案:

答案 0 :(得分:0)

对于我提出的问题,这不是一个解决方案,但对我和公司而言,这是一个可以接受的妥协。

快速解决方案: 当您在API中公开RestRepository<User>且审核员类型为User时,Spring会生成指向createdBylastModifiedBy的HAL链接。两个审计日期都将内联,因为它们是简单的字符串(由于JodaTime转换)。

示例代码:

// resolves auditor from SecurityContext
public class AuditorAwareImpl implements AuditorAware<User> {

  @Override
  public User getCurrentAuditor() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication != null && authentication.getPrincipal() instanceof WrappedUser) {
      WrappedUser principal = (WrappedUser)authentication.getPrincipal();
      return principal.getUser();
    }
    throw new IllegalStateException("No current auditor available!");
  }
}

公开UserRepository:

//exported is true by default
@RepositoryRestResource(exported = true)
public interface UserRepository extends JpaRepository<User, Long> {
  Optional<User> findByName(String loginName);
}

创建所有其他域对象继承的AuditEntity:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
  @javax.persistence.Version
  private Long version;
}

公开您的域名模型:

@Entity
public class Project extends BaseEntity {
  private String project_name;
  // other properties
}

@RepositoryRestResource
public interface ProjectRepo extends JpaRepository<User, Long> {}

这将为/projects/{id}生成以下JSON:

{
    "createdDate": "2017-06-12T11:49:17.013Z",
    "lastModifiedDate": "2017-06-14T11:27:32.370Z",
    "project_name": "MyExampleProjectName",
    "new": false,
    "_links":
    {
        "self":
        {
            "href": "http://localhost:8080/projects/1"
        },
        "project":
        {
            "href": "http://localhost:8080/projects/1{?projection}",
            "templated": true
        },
        "lastModifiedBy":
        {
            "href": "http://localhost:8080/projects/1/lastModifiedBy"
        },
        "createdBy":
        {
            "href": "http://localhost:8080/projects/1/createdBy"
        }
    }
}