【问题标题】:What is the easiest way to ignore a JPA field during persistence?在持久化期间忽略 JPA 字段的最简单方法是什么?
【发布时间】:2010-11-19 21:32:43
【问题描述】:

我本质上是在寻找一个“@Ignore”类型的注释,我可以用它来阻止一个特定的字段被持久化。如何实现?

【问题讨论】:

    标签: java database hibernate jpa


    【解决方案1】:

    显然,使用 Hibernate5Module,如果使用 ObjectMapper,@Transient 将不会被序列化。删除将使其工作。

    import javax.persistence.Transient;
    
    import org.junit.Test;
    
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.Setter;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class TransientFieldTest {
    
        @Test
        public void Print_Json() throws JsonProcessingException {
    
            ObjectMapper objectEntityMapper = new ObjectMapper();
            //objectEntityMapper.registerModule(new Hibernate5Module());
            objectEntityMapper.setSerializationInclusion(Include.NON_NULL);
    
            log.info("object: {}", objectEntityMapper.writeValueAsString( //
                    SampleTransient.builder()
                                   .id("id")
                                   .transientField("transientField")
                                   .build()));
    
        }
    
        @Getter
        @Setter
        @Builder
        private static class SampleTransient {
    
            private String id;
    
            @Transient
            private String transientField;
    
            private String nullField;
    
        }
    }
    

    【讨论】:

      【解决方案2】:

      根据实体属性类型有多种解决方案。

      基本属性

      假设您有以下account 表:

      account 表映射到Account 实体,如下所示:

      @Entity(name = "Account")
      public class Account {
      
          @Id
          private Long id;
      
          @ManyToOne
          private User owner;
      
          private String iban;
      
          private long cents;
      
          private double interestRate;
      
          private Timestamp createdOn;
      
          @Transient
          private double dollars;
      
          @Transient
          private long interestCents;
      
          @Transient
          private double interestDollars;
      
          @PostLoad
          private void postLoad() {
              this.dollars = cents / 100D;
      
              long months = createdOn.toLocalDateTime()
                  .until(LocalDateTime.now(), ChronoUnit.MONTHS);
              double interestUnrounded = ( ( interestRate / 100D ) * cents * months ) / 12;
              this.interestCents = BigDecimal.valueOf(interestUnrounded)
                  .setScale(0, BigDecimal.ROUND_HALF_EVEN).longValue();
      
              this.interestDollars = interestCents / 100D;
          }
      
          //Getters and setters omitted for brevity
      }
      

      基本实体属性映射到表列,因此id、iban、cents 等属性是基本属性。

      但 dollars、interestCents 和 interestDollars 是计算属性,因此您使用 @Transient 注释它们以将它们从 SELECT、INSERT、UPDATE 和 DELETE SQL 语句中排除。

      因此,对于基本属性,您需要使用@Transient 来排除给定属性的持久化。

      关联

      假设您有以下post 和post_comment 表:

      您希望将Post 实体中的latestComment 关联映射到最新添加的PostComment 实体。

      为此,您可以使用@JoinFormula 注释:

      @Entity(name = "Post")
      @Table(name = "post")
      public class Post {
       
          @Id
          private Long id;
       
          private String title;
       
          @ManyToOne(fetch = FetchType.LAZY)
          @JoinFormula("(" +
              "SELECT pc.id " +
              "FROM post_comment pc " +
              "WHERE pc.post_id = id " +
              "ORDER BY pc.created_on DESC " +
              "LIMIT 1" +
          ")")
          private PostComment latestComment;
       
          //Getters and setters omitted for brevity
      }
      

      在获取Post实体的时候,可以看到latestComment被获取了,但是如果你想修改它,这个改动会被忽略。

      因此,对于关联,您可以使用@JoinFormula 忽略写入操作,同时仍允许读取关联。

      @MapsId

      另一种忽略已由实体标识符映射的关联的方法是使用@MapsId。

      例如,考虑以下一对一的表关系:

      PostDetails 实体的映射如下:

      @Entity(name = "PostDetails")
      @Table(name = "post_details")
      public class PostDetails {
       
          @Id
          private Long id;
       
          @Column(name = "created_on")
          private Date createdOn;
       
          @Column(name = "created_by")
          private String createdBy;
       
          @OneToOne(fetch = FetchType.LAZY)
          @MapsId
          private Post post;
       
          public PostDetails() {}
       
          public PostDetails(String createdBy) {
              createdOn = new Date();
              this.createdBy = createdBy;
          }
       
          //Getters and setters omitted for brevity
      }
      

      请注意id 属性和post 关联都映射同一个数据库列,即post_details 主键列。

      要排除 id 属性,@MapsId 注释将告诉 Hibernate post 关联处理表主键列值。

      因此,当实体标识符和关联共享同一列时,您可以使用@MapsId忽略实体标识符属性并使用关联。

      使用insertable = false, updatable = false

      另一种选择是使用insertable = false, updatable = false 作为您希望被 Hibernate 忽略的关联。

      例如,我们可以像这样映射之前的一对一关联:

      @Entity(name = "PostDetails")
      @Table(name = "post_details")
      public class PostDetails {
      
          @Id
          @Column(name = "post_id")
          private Long id;
      
          @Column(name = "created_on")
          private Date createdOn;
      
          @Column(name = "created_by")
          private String createdBy;
      
          @OneToOne
          @JoinColumn(name = "post_id", insertable = false, updatable = false)
          private Post post;
      
          //Getters and setters omitted for brevity
      
          public void setPost(Post post) {
              this.post = post;
              if (post != null) {
                  this.id = post.getId();
              }
          }
      }
      

      @JoinColumn 注解的insertable 和updatable 属性将指示Hibernate 忽略post 关联,因为实体标识符负责post_id 主键列。

      【讨论】:

      • 注意 id 属性和 post 关联都映射同一个数据库列,即 post_comment 主键列。 这在 post 和 @ 的上下文中是否正确987654368@表为例?
      • 感谢您发现它。我修好了。
      • 我在 DB2 中尝试了所有这些方法,而我唯一可以使用 DB2 数据库的方法是 @JoinColumn。 PrimaryKeyColumn 也不起作用。大多是生成的生成 SQL 不正确。
      【解决方案3】:

      使用@Transient 使JPA 忽略该字段。

      但是! Jackson 也不会序列化该字段。解决只需添加@JsonProperty

      一个例子

      @Transient
      @JsonProperty
      private boolean locked;
      

      【讨论】:

      • 这对我有用。
      【解决方案4】:

      有时你想:

      1. 序列化列
      2. 忽略列被持久化:

      使用@Column(name = "columnName", insertable = false, updatable = false)

      一个很好的场景是使用其他列值自动计算某一列时

      【讨论】:

      • 是的,这个解决方案适合场景
      • 这对我来说是最好的解决方案:我想在 EntityManager 操作期间忽略该列,但仍然使用它来自动创建架构?
      【解决方案5】:

      要忽略一个字段,用@Transient注释它,这样它就不会被hibernate映射。

      但是当转换为 JSON 时,jackson 不会序列化该字段。

      如果您需要将 JPA 与 JSON 混合使用(JPA 省略但仍包含在 Jackson 中)请使用 @JsonInclude:

      @JsonInclude()
      @Transient
      private String token;
      

      提示:

      token == null时也可以使用JsonInclude.Include.NON_NULL,在反序列化时隐藏JSON中的字段:

      @JsonInclude(JsonInclude.Include.NON_NULL)
      @Transient
      private String token;
      

      【讨论】:

      • 我正在运行 JAX-RS 2.0.1 / Jersey 2.25.1 / Jackson 2.8.7 并且使用该堆栈 @JsonInclude 不是必需的:@Transient 字段仍包含在 JSON 中。 (你仍然得到我的投票:该技术本身在其他情况下可能非常有用)。
      • 我是在 Spring Boot 上做的
      • 嗨,我正在尝试使用它,但它没有被@Transient 字段序列化
      • 在 Spring Boot 2.1.7 中尝试过,但没有成功。但你仍然得到我的投票,因为它可能适用于其他场景。
      • @Transient 字段仅在 jackson-datatype-hibernate 模块位于类路径上并使用 ObjectMapper 注册时才被排除,这可能是带有 Jackson 和 Hibernate 的 Spring Boot 应用程序的默认设置,以及为什么设置@JsonInclude 对其他人也是可选的。
      【解决方案6】:

      以上答案都不适用于我使用 Hibernate 5.2.10、Jersey 2.25.1 和 Jackson 2.8.9。我终于找到了答案(有点,他们引用 hibernate4module 但它也适用于 5)here。所有 Json 注释都不能与 @Transient 一起使用。显然 Jackson2 足够“聪明”,可以忽略标有@Transient 的东西,除非你明确告诉它不要这样做。关键是添加 hibernate5 模块(我用来处理其他 Hibernate 注释)并在我的 Jersey 应用程序中禁用 USE_TRANSIENT_ANNOTATION 功能:

      ObjectMapper jacksonObjectMapper = new ObjectMapper();
      Hibernate5Module jacksonHibernateModule = new Hibernate5Module();
      jacksonHibernateModule.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
      jacksonObjectMapper.registerModule(jacksonHibernateModule);  
      

      这里是 Hibernate5Module 的依赖:

      <dependency>
          <groupId>com.fasterxml.jackson.datatype</groupId>
          <artifactId>jackson-datatype-hibernate5</artifactId>
          <version>2.8.9</version>
      </dependency>
      

      【讨论】:

      • 在尝试了上面提到的所有其他方法和其他@JsonProperty@JsonInclude@JsonSerialize + @JsonDeserializemapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, false));之后,这个解决方案终于奏效了。谢谢!
      【解决方案7】:

      要忽略一个字段,用@Transient 注释它,这样它就不会被hibernate 映射。
      来源:Hibernate Annotations。

      【讨论】:

        【解决方案8】:

        为了完成上述答案,我使用了一个 XML 映射文件,其中 @Transient 和 transient 都不起作用... 我不得不将瞬态信息放在 xml 文件中:

        <attributes>
        (...)
            <transient name="field" />
        </attributes>
        

        【讨论】:

          【解决方案9】:

          这个答案来得有点晚,但它完成了响应。

          为了避免实体中的字段被持久化到数据库中,可以使用以下两种机制之一:

          @Transient - 将字段标记为不可持久化的 JPA 注释

          transient java 中的关键字。当心 - 使用这个关键字,将阻止该字段与来自 java 的任何序列化机制一起使用。因此,如果必须对字段进行序列化,您最好只使用 @Transient 注释。

          【讨论】:

          • 我只想忽略 get 方法的持久性怎么样?例如: myjparepository.save() 会像往常一样保存模型,而 myjparepository.find(id) 会忽略我想要的字段?
          • @xtiger 您可以创建自定义查询来归档该功能。这是一个例子link
          【解决方案10】:

          @Transient 符合您的需求。

          【讨论】:

          • 但是然后jackson在转成JSON的时候不会序列化字段...怎么解决?
          • 这取决于您的应用程序设计。如果您注释您的实体类 - 它适用于任何地方;但是,如果您注释使用实体的 dao - 这是另一回事。简而言之:当你有多个存储时使用 DAO
          • 不适用于列表字段。对于 Collection 类型的字段,hibernate 实际上会创建一个新的中间表。有什么解决方法吗?
          • @MobileMon 可以解决的,看我的回答stackoverflow.com/a/41850392/3871754
          • @MobileMon 最好不要将同一个实体用于数据库和 API 目的(单一职责)。
          猜你喜欢
          • 2010-12-29
          • 2017-08-21
          • 1970-01-01
          • 1970-01-01
          • 2010-09-28
          • 1970-01-01
          • 1970-01-01
          • 2010-10-11
          • 1970-01-01
          相关资源
          最近更新 更多