Lazily loading a clob in hibernate
Asked Answered
U

4

16

There's a lot one can find about this googling a bit but I haven't quite found a workable solution to this problem.

Basically what I have is a big CLOB on a particular class that I want to have loaded on demand. The naive way to do this would be:

class MyType {

  // ...

  @Basic(fetch=FetchType.LAZY)
  @Lob
  public String getBlob() {
    return blob;
  }
}

That doesn't work though, apparently due to the fact I'm using oracle drivers, i.e. Lob objects aren't treated as simple handles but are always loaded. Or so I've been led to believe from my forays. There is one solution that uses special instrumentation for lazy property loading, but as the Hibernate docs seem to suggest they're less than interested in making that work correctly, so I'd rather not go that route. Especially with having to run an extra compile pass and all.

So the next solution I had envisioned was separating out this object to another type and defining an association. Unfortunately, while the docs give conflicting information, it's apparent to me that lazy loading doesn't work on OneToOne associations with shared primary key. I'd set one side of the association as ManyToOne, but I'm not quite sure how to do this when there's a shared primary key.

So can anybody suggest the best way to go about this?

Unashamed answered 17/7, 2009 at 10:29 Comment(5)
Could you clarify as to why it doesn't work with Oracle?Puccini
I fleshed that bit out a bit, I'm not entirely sure what the exact problem is (bit vague on the details). If the problem is with the mapping could you give one that should lazy load the clob properly?Unashamed
If you tried it with Oracle, and it failed, how did it fail?Puccini
It didn't fail as such, hibernate is simply generating queries where the full clob is always fetched (it's in the select), regardless of whether it's marked as lazy or not.Unashamed
This happens with sqlserver and jtds drivers as well. There is simply no lazy loading.Billhead
L
8

According to this only PostgreSQL implements Blob as really lazy. So the best solution is to move the blob to another table. Do you have to use a shared primary key? Why don't you do something like this:

public class MyBlobWrapper {
    @Id
    public Long getId() {
       return id;
    }
    @Lob
    public String getBlob() {
        return blob;
    }
    @OneToOne(fetch=FetchType.LAZY,optional=false) 
    public MyClass getParent() {
        return parent;
    }
}
Lindquist answered 17/7, 2009 at 11:23 Comment(6)
The blob is just a field in the same table so yes, have to use a shared primary key. Your approach works as long as optional="false" is set on the owning side. I'm assuming that'll break horribly when the object is null?Unashamed
D'oh. Of course it'll never be null as the primary key exists. It'll just be the clob field that's null. Bit of a thinko there, thanks. :-)Unashamed
This is the necessary mapping on the parent side btw (MyType). you might want to include it in your answer: @OneToOne(fetch=FetchType.LAZY,optional=false)Unashamed
Done, I added suggested parameters.Lindquist
Okay but it needs to be on the parent side, MyClass in your example.Unashamed
I know this is quite old post but I would like to add some words of comment to this most popular answer. Lazy loading of (C/B)lob works in i.e. Oracle 11g also but it requires byte code instrumentation as suggested below by @Edwin Dalorzo - checked with Hibernate 5 (It might have not been working in 2009)Dolhenty
U
5

Instead of doing equilibristics with hibernate annotations, one may just try converting the field from String into Clob (or Blob):

@Lob  
@Basic(fetch=FetchType.LAZY)  
@Column(name = "FIELD_COLUMN")  
public Clob getFieldClob() {  
  return fieldClob;  
}  

public void setFieldClob(Clob fieldClob) {  
  this.fieldClob = fieldClob;  
}  

@Transient  
public String getField()  
{  
  if (this.getFieldClob()==null){  
    return null;  
  }  
  try {  
    return MyOwnUtils.readStream(this.getFieldClob().getCharacterStream());  
  } catch (Exception e) {  
    e.printStackTrace();  
  }  

  return null;  
}  

public void setField(String field)  
{  
  this.fieldClob = Hibernate.createClob(field);  
} 

Worked for me (the field started to load lazily, on Oracle).

Undersexed answered 13/11, 2012 at 9:12 Comment(2)
How does this cause the field to be loaded lazily ? From what I can tell this just adds convenience getter / setter to access the lob as a string ?Favored
@BenGeorge Note that the Lob annotation is being put on a Clob getter in my answer, as opposed to a String getter in the question. And I don't use the Clob field directly, but only through a transient getter. Warning: the version of Hibernate on which I tried this, was, if I remember correctly, 3.2. To verify that the loading was indeed lazy, I used Wireshark to inspect the traffic to/from the database.Undersexed
C
4

Since you appear to be using Hibernate I wonder if your problem is related to the following Hibernate feature:

Using Lazy Properties Fetching

Hibernate3 supports the lazy fetching of individual properties. This optimization technique is also known as fetch groups. Please note that this is mostly a marketing feature; optimizing row reads is much more important than optimization of column reads. However, only loading some properties of a class could be useful in extreme cases. For example, when legacy tables have hundreds of columns and the data model cannot be improved.

Lazy property loading requires buildtime bytecode instrumentation. If your persistent classes are not enhanced, Hibernate will ignore lazy property settings and return to immediate fetching.

See Bytecode Instrumentation for Hibernate Using Maven.

Conjugal answered 4/2, 2014 at 11:55 Comment(0)
U
1

Old post, but only one that helped me, thanks to @TadeuszKopec answer.

Looks like it is hard to do lazy loading of blob with JPA. I tried @OneToOne association, but it complicates more than help. I just moved the bytes to another class, with no association with MyClass (parent. Same table, same id):

@Entity
@Table(name="MyTable")
public class MyBlobWrapper{

    @Id
    @Column(name = "id") // id of MyTable, same as MyClass
    private Long id;

    @Lob
    private byte[] bytes;   
}

@Entity
@Table(name="MyTable")
public class MyClass{

    @Id
    @Column(name = "id")
    private Long id;
    // other fields  .....
}

Just remember to flush parent, before saving the blob:

 em.persist(parent);
 em.flush();
 em.merge(new MyBlobWrapper(parent_id,new byte[1000]));

Now I can load the pdf alone:

String query1 = " select PDF from MyBlobWrapper PDF where PDF.id = :id";

I am just beginner with JPA, hope that helps.

Unoccupied answered 7/3, 2018 at 18:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.