Issue
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:
- 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 - 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
- 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!
Solution
Lazy loading attributes
Hibernate can load attribute lazily, but you need to enable byte code enhancements:
First you need to set the property
hibernate.enhancer.enableLazyInitialization
to trueThen 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.
Answered By - Davide D'Alto
Answer Checked By - David Goodson (JavaFixing Volunteer)