Home > Mobile >  How do I make JPA entity field truly write-only
How do I make JPA entity field truly write-only

Time:06-18

I have a case where I'm persisting a large jsonb field into a PostGres table, but do not want to read it when I fetch the entity; if I do fetch it, my service goes OOM. A better design might be to separate this into a 1 to 1 table, but I can't do that at this time.

To plead that this is not a duplicate question, here's some of my research:

  1. I'm not able to mark the column LAZY since I have a simple column not a join` JPA/Hibernate write only field with no read
  2. I tried the empty setter in this suggestion, which makes sense - but it still appears to read the column and I OOM: https://www.zizka.ch/pages/programming/java/hibernate/hibernate-write-only.html
  3. I also tried omitting the setter altogether in my @Data class: Omitting one Setter/Getter in Lombok

So, I can not see the field, but I can't seem to keep it from being read into memory in the background. It seems like there must be some simple setting in JPA or Hibernate to exclude a column from read. Before I go try to make a complex repository hierarchy just to see if it works, I thought I would ask here in case I get lucky.

Thanks in advance!

CodePudding user response:

Lazy loading attributes

Hibernate can load attribute lazily, but you need to enable byte code enhancements:

  1. First you need to set the property hibernate.enhancer.enableLazyInitialization to true

  2. Then you can annotate the field with @Basic( fetch = FetchType.LAZY ). Here's the example from the documentation:

    @Entity
    public class Customer {
    
      @Id
      private Integer id;
    
      private String name;
    
      @Basic( fetch = FetchType.LAZY )
      private UUID accountsPayableXrefId;
    
      @Lob
      @Basic( fetch = FetchType.LAZY )
      @LazyGroup( "lobs" )
      private Blob image;
    
      //Getters and setters are omitted for brevity
    }
    

You can also enable this feature via the Hibernate ORM gradle plugin

Named Native queries

You could also decide to not map it and save/read it with a named native query. It seems a good trade off for a single attribute - it will just require an additional query to save the json.

Example:

@Entity
@Table(name = "MyEntity_table")
@NamedNativeQuery(
    name = "write_json",
    query = "update MyEntity_table set json_column = :json where id = :id")
@NamedNativeQuery(
    name = "read_json",
    query = "select json_column from MyEntity_table where id = :id")
class MyEntity {
....
}

Long id = ...
String jsonString = ...
session.createNamedQuery( "write_json" )
    .setParameter( "id", id )
    .setParameter( "json", jsonString )
    .executeUpdate();

jsonString = (String)session.createNamedQuery( "read_json" )
    .setParameter( "id", id )
    .getSingleResult();

In this case, schema generation is not going to create the column, so you will need to add it manually (not a big deal, considering that there are better tools to update the schema in production).

MappedSuperclass

You can also have two entities extending the same superclass (this way you don't have to copy the attributes). They have to update the same table:

@MappedSuperclass
class MyEntity {

   @Id
   Long id;
   String name
   ...
}

@Entity
@Table(name = "MyEntity_table")
class MyEntityWriter extends MyEntity {

   String json
}

@Entity
@Table(name = "MyEntity_table")
class MyEntityReader extends MyEntity {
   // No field is necessary here
}

Now you can use MyEntityWriter for saving all the values and MyEntityReader for loading only the values you need.

I think you will have some problems with schema generation if you try to create the tables because only one of the two will be created:

  • If MyEntityWriter is the first table created, then no problem
  • If MyEntityWriter is the second table created, the query will fail because the table already exist and the additional column won't be created.

I haven't tested this solution though, there might be something I haven't thought about.

  • Related