JPA Transient注解和JSON

45

这是关于JPA Transient注解的后续问题,具体参见以下链接: Why does JPA have a @Transient annotation?

我有一个被@Transient标记的瞬态变量,不想将其持久化。但是,当我从我的rest控制器生成JSON时,这个瞬态变量在输出的JSON中不可用。

POJO PublicationVO很简单,没有花哨的属性,只有一些私有属性(会被持久化)和1个瞬态变量,都有getter和setter方法。

@RequestMapping(value = { "{publicationId}"}, method = RequestMethod.GET, produces = "application/json")
@ResponseBody public PublicationVO getPublicationDetailsJSON(@PathVariable(value = "publicationId") Integer publicationId) {
    LOG.info("Entered getPublicationDetailsJSON - publicationId: " + publicationId);

    //Call method to get the publicationVO based on publicationId
    PublicationVO publicationVO = publicationServices.getPublicationByIdForRestCalls(publicationId);       
    LOG.info("publicationVO:{}", publicationVO);

    LOG.info("Exiting getPublicationDetailsJSON");
    return publicationVO;
}

PublicationVO 如下所示

    package com.trinity.domain.dao;

import java.util.Calendar;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.fasterxml.jackson.annotation.JsonInclude;

@Entity
@Table(name = "publication")
public class PublicationVO {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;    
    @Column(name = "publicationName", unique = false, nullable = false, length = 200)
    private String publicationName;
    @Column(name = "publicationSource", unique = false, nullable = false, length = 45)
    private String publicationSource;

    @Column(name = "dateAdded", unique = false, nullable = false)
    private Calendar dateAdded;

    @Transient
    private float percentageProcessed;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getPublicationName() {
        return publicationName;
    }

    public void setPublicationName(String publicationName) {
        this.publicationName = publicationName;
    }

    public String getPublicationSource() {
        return publicationSource;
    }

    public void setPublicationSource(String publicationSource) {
        this.publicationSource = publicationSource;
    }

    public Calendar getDateAdded() {
        return dateAdded;
    }

    public void setDateAdded(Calendar dateAdded) {
        this.dateAdded = dateAdded;
    }

    public float getPercentageProcessed() {
        return percentageProcessed;
    }

    public void setPercentageProcessed(float percentageProcessed) {
        this.percentageProcessed = percentageProcessed;
    }

    @Override
    public String toString() {
        return "PublicationVO [id=" + id + ", publicationName=" + publicationName + ", publicationSource=" + publicationSource + ", dateAdded=" + dateAdded
                + ", percentageProcessed=" + percentageProcessed + "]";
    }
}

当我在日志中看到publicationVO的调试语句时,短暂变量包含在输出中,但是在我的客户端代码中,短暂变量没有包含在JSON响应中。

非常感谢任何帮助。

谢谢, Damien


你能否编辑你的问题并发布PublicationVO的代码?你的瞬态变量没有被序列化,是否有适当的getter方法? - m4rtin
我看到你的问题中有一些Spring MVC注解以及Spring标签,你能确认JSON序列化是由Jackson库完成的吗? - m4rtin
事实上,在Java中,“transient”确实意味着属性不应该被序列化。JPA只是“扩展”了这个意义,以防止属性持久化。但在您的情况下,该属性仅是JPA注释,并且不是以核心Java方式瞬态的,如果Jackson处理JPA注释,我会感到非常惊讶。此外,Jackson的基本机制建立在getter/setter上,而您的属性具有适当的getter/setter,因此...也许问题与属性的@Transient注释无关? - m4rtin
1
true m4rtin,你说得很有道理。我会尝试手动添加数据库列,删除瞬态注释,看看是否有任何区别,并回复你。 - Damien Gallagher
没错,m4rtin。当属性没有Transient注释并且我为percentageProcessed添加了列时,percentageProcessed属性会在响应中序列化。 - Damien Gallagher
显示剩余7条评论
10个回答

58

3
谢谢,它奏效了。这有助于将应该保留在数据库中的内容和应该放在JSON中的内容分别定义。 - stefitz
2
我不知道它是否有效,但似乎我们最终会在实体中混合跨层信息。 Transient 用于数据库层(最底层),而 JsonSerialize 应该在 UI/客户端层。 - Padmarag
我必须确保导入了正确的包来使用@Json*注解。org.springframework.cloud.cloudfoundry.com.fasterxml.jackson.databind.annotation不起作用,但是com.fasterxml.jackson.databind.annotation.可以。 - N4ppeL

32
与我之前在评论中告诉您的相反,通过Jackson的Hibernate模块,似乎Jackson确实重视JPA注释,用于序列化实体类的实例。
在该模块中,有一个HibernateAnnotationIntrospector,文档将其称为“简单的AnnotationIntrospector,它增加了对使用Transient表示可忽略字段的支持(与Jackson和/或JAXB注释一起)”。
正如您可以看到此处,Jackson的默认行为是检查任何它可以找到的@Transient注释。
所以最终,您的问题可以通过以下3种方式解决:
1.配置Jackson(使用HibernateAnnotationIntrospector的setUseTransient方法)来禁用对@Transient注释的检查(有关实现细节,请参见此答案)。
2.使用除PublicationVO之外的另一个对象作为getPublicationDetailsJSON方法返回的结果。您将不得不在某个时刻将值对象的属性复制到返回的对象中。
  • 移除@Transient注解并持久化该属性(但如果您在第一次将此属性设为JPA-transient时有充分的理由,我也可以理解您不想这样做)。

  • 1
    非常好的回答,m4rtin。我明天会尝试第一步,如果不行,我认为第二步可能是下一个逻辑步骤。 - Damien Gallagher
    非常感谢您提供的信息,m4rtin。对我们很有帮助。 - Mohsin
    3
    杰克逊(Jackson)关注 Hibernate 的注解选择让我感到困惑。这些家伙是不是很high啊?(意指他们的决定太奇怪了) - Gordon Sun

    22

    仅作为m4rtin所提供答案的补充:

    我采取了第一种方法——通过配置Jackson(使用HibernateAnnotationIntrospector的setUseTransient方法)来禁用@Transient注释的检查。

    在我的项目中,为了避免jackson序列化非获取的懒加载对象,我遵循了下面的线程: 避免jackson序列化非获取的懒加载对象

    为了配置我的项目以不忽略瞬态注释,我设置了Hibernate4Module如下:

            Hibernate4Module hm = new Hibernate4Module();
        hm.disable(Feature.USE_TRANSIENT_ANNOTATION);
    

    感谢您在此事上的帮助,m4rtin。


    很高兴你找到了关于第一种方法的实现细节,我会编辑我的答案并添加对你的回答的引用,这肯定对选择解决方案1的人有帮助。 - m4rtin
    终于有东西可以用了!我创建了一个gist,其中包含此设置所需的各种文件。 - Gaʀʀʏ

    15

    我同时使用了@Transient和@JsonProperty,它能正常工作!

    @Transient
    @JsonProperty
    private String mapImageSrc;
    

    1
    +1 是的,这也可以工作。JsonSerialize 和 JsonProperty 似乎都可以工作。我想知道它们之间是否有微妙的区别,以及是否有一个更受欢迎。 - HopeKing
    1
    你甚至可以使用 @JsonProperty(access = Access.WRITE_ONLY) 或 @JsonProperty(access = Access.READ_ONLY) 来细化访问。 - Selindek
    这是唯一对我有效的方法,当我为我的字段使用@Transient时。 - hashcoder

    6

    对我有用的方法:

    @Transient
    @JsonProperty
    

    在 GETTER 中(而不是私有字段定义中)
    并且
    @JsonAutoDetect(fieldVisibility = Visibility.ANY) 
    

    注释类

    我可以问一下为什么要点踩吗? - Vic
    这对我有用,必须在类级别上添加@JsonAutoDetect,谢谢! - Kevin Orriss

    4

    由于这里没有其他解决方案适用于我,所以让我发布我的解决方案,它是如何为我解决问题的:

    @Transient
    @JsonSerialize
    private String mapImageSrc;
    

    对我来说,这似乎是最好的解决方案,因为 @JsonSerialize 注释是为该用例而设计的。


    在我的情况下,我只有getter方法,例如int getCounter(),没有setter或字段。我所要做的就是添加@JsonSerialize即可,无需使用transient或其他内容。 - Harun

    2

    我曾经也遇到过同样的问题。以下解决方案对我有用:

    @Bean
    public Module hibernate5Module() {
        Hibernate5Module hnetModule = new Hibernate5Module();
        hnetModule.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
        return hnetModule;
    }
    

    感谢m4rtin的贡献。

    1
    尝试在添加 @Transient 后使用 @JsonView

    对于琐碎的答案和建议,请使用注释。 - Rathan Naik

    0

    在我的情况下,仅适用于这个:

    @Transient
    @JsonGetter(value = "transientProperty")
    public String getSomething() {
        return something;
    }
    

    希望这对某人有用。 谢谢。


    -1

    在 com.fasterxml.jackson.annotation 中使用 @JsonIgnore, 对于日期变量,还有 @JsonFormat。 对我来说很有效。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接