Enterprise Java Development@TOPIC@

Chapter 29. Mapping Large Objects

29.1. Mapping CLOBS
29.2. Mapping BLOBS
29.3. Summary

This chapter will take you through mapping large objects (Clobs and Blobs) to your database.

The normal database types like varchar can be indexed and searched for but usually have a maximum length somewhere between 2,000 and 5,000 characters. If you need so store larger documents or images databases provide two additional types:

The actual database type(s) are not necessarily called Clob and Blob, so JPA provides a layer of abstraction between what the application needs and how it is declared in the database.

JPA has built-in rules to map string/character data to a varchar and we must define a @javax.persistence.Lob metadata property to have it mapped differently. Lets start this exercise by using the default mapping and then add overrides.

  1. Create the following class in your src/main tree.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENTITYEX_HORSE")
    public class Horse {
        @Id @GeneratedValue
        private int id;
        private String name;
        private String description;
        private char[] history;
        private byte[] photo;
        
        public int getId() { return id; }
        public void setId(int id) {
            this.id = id;
        }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        
        public String getDescription() { return description; }
        public void setDescription(String description) {
            this.description = description;
        }
        
        public char[] getHistory() { return history; }
        public void setHistory(char[] history) {
            this.history = history;
        }
    }
  2. Add the new class to the persistence unit.

    
    
            <class>myorg.entityex.annotated.Horse</class>
  3. Build the module and observe the database schema that is created. Note that all of our string and char[] properties are being mapped to a varchar.

       create table ENTITYEX_HORSE (
            id integer generated by default as identity,
            description varchar(255),
            history varchar(255),
            name varchar(255),
            primary key (id)
        );
    
  4. Add the following test method to the existing JUnit test case.

    
    
        @Test 
        public void testLob() {
            log.info("testLob");
            //create our host object with Lob objects
            Horse horse = new Horse();
            horse.setName("Mr. Ed");
            horse.setDescription("There once was a horse of course and his name was Mr. Ed...");
            horse.setHistory("Mister Ed is a fictional talking horse residing in Mount Kisco, New York,...".toCharArray());
            em.persist(horse);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(horse);
            Horse horse2 = em.find(Horse.class, horse.getId());
            assertEquals("unexpected description", horse.getDescription(), horse2.getDescription());
            assertTrue("unexpected history", Arrays.equals(horse.getHistory(), horse2.getHistory()));
        }
  5. Using the database server profile, run the tests and observe the data left in the database tables.

    $ mvn clean test -Ph2srv -P\!h2db
    ...
    
    SELECT * FROM ENTITYEX_HORSE;
    ID      DESCRIPTION     HISTORY     NAME  
    1   There once was a horse of course and his name was Mr. Ed... Mister Ed is a fictional talking horse residing in Mount Kisco, New York,...    Mr. Ed
    
  6. Update the hosting class and supply @Lob for description and history properties

    
    
        @Lob
        private String description;
        @Lob
        private char[] history;
  7. Rebuild the module and observe the change in database types. The mapping has been changed to a Clob type and the database column for the H2 database looks like it is also "clob".

       create table ENTITYEX_HORSE (
            id integer generated by default as identity,
            description clob,
            history clob,
            name varchar(255),
            primary key (id)
        );
    

At this point in time you now have string fields that can be used to store large amounts of data. As a side-exercise, try storing 100K character strings stored as Clobs and then switch them back to varchar to see the difference in size constraints. Try setting the @Column.length field to a high value to accomodate the string mapped as a varchar. Where is the maximum? That maximum value is not the same on all databases.

The above section worked with character/string data. We also may need tp store binary information. JPA will

  1. Add the following byte[] property to the host class. Annotate it as a @Lob type to assure we bet the right storage type.

    
    
        @Lob
        private byte[] photo;
        
        public byte[] getPhoto() { return photo; }
        public void setPhoto(byte[] photo) {
            this.photo = photo;
        }
  2. Rebuild the module and observe the database schema created. We now have a "blob" type for photo.

       create table ENTITYEX_HORSE (
            id integer generated by default as identity,
            description clob,
            history clob,
            name varchar(255),
            photo blob,
            primary key (id)
        );
    
  3. Add a Java Serializable type to the Horse class. JPA can store this type of object in a blob as well.

    
    
    public class Horse {
        public static class Jockey implements Serializable {
            private static final long serialVersionUID = 1L;
            private String name;
            public String getName() { return name; }
            public void setName(String name) { this.name = name; }
        }
        @Lob
        private Jockey jockey;
        
        public Jockey getJockey() { return jockey; }
        public void setJockey(Jockey jockey) {
            this.jockey = jockey;
        }    
  4. Update the test method with the following to exercise the Blob fields.

    
    
    ...
                byte[] picture = new byte[10*1000];
                new Random().nextBytes(picture);
            horse.setPhoto(picture);
                Horse.Jockey jockey = new Horse.Jockey();
                jockey.setName("Wilbur Post");
            horse.setJockey(jockey);
            em.persist(horse);
    ...
            Horse horse2 = em.find(Horse.class, horse.getId());
    ...        
            assertTrue("unexpected photo", Arrays.equals(horse.getPhoto(), horse2.getPhoto()));
            assertEquals("unexpected jockey", horse.getJockey().getName(), horse2.getJockey().getName());
  5. Rebuild/test the module to generate schema and verify functionality. Notice we now have photo (the byte[] type) and jockey (the Serializable type) mapped to a database blob.

        create table ENTITYEX_HORSE (
            id integer generated by default as identity,
            description clob,
            history clob,
            jockey blob,
            name varchar(255),
            photo blob,
            primary key (id)
        );
    

If you took a look at the results in the H2 DB browser UI using a default query, you should notice that binary information is now stored in the two additional fields. Note that the jockey name is not stored as a simple String. It is stored as a serialized string within a serialized Jockey class within a Blob field of the database. We can only get and set the Jockey and not search for their name using this mapping mechanism.

In this chapter we mapped large string/character types to Clobs and binary content to Blobs. One thing we still need to point out is that Clobs and Blobs can come at a performance cost. You likely will want to model Clob and Blob data in lazily loaded child tables (using JPA relationships) to allow quick and efficient access to the traditional column data and then optionally provide the large payloads on demand.