October 14, 2007

Image Handling in Seam Apps Part I: DB Model

This is the start of a tutorial about how to manage images in your Seam based web application. This tutorial consists of several parts:

Part One: The Database Model
Part Two: File Upload
Part Three: Scaling
Part Four: Displaying and Caching
Part Five: Restricting Access

Introducing the example

The example used to demonstrate this tutorial allows a member to upload artworks of various sizes. The member has to login first and be a member of a predefined group. Once uploaded a thumbnail of the original artwork is generated on the fly. The original artwork and the generated image are then stored in the database. Other members can then view both of the thumbnail and the original artwork.

Database Schema

This first part of our tutorial introduces you the database schema and the entity beans representing the tables. To store the artwork and its thumbnail you need an additional table to store information shared between the two. This tables is called artwork_info and it has a one to one relationship with the other two tables. It also has a many to one relationship with the member table.



The following desc commands show the meta-data for them.



The data column in the gs_artwork table is defined as MEDIUMBLOB since a normal BLOB type can hold only up to 65,535 byte which is almost 64KB. You can store almost 16MB using a MEDIUMBLOB.

Programming Model

Each database table maps to its own Java class. There is a One-To-One Unidirectional relationship between the ArtworkInfo class and each of Artwork and Thumbnail. The Member class has a One-To-Many Bidirectional relationship with the ArtworkInfo class.



Let take a look at how we would mark up the ArtworkInfo class to implement the One-To-One relationship:


@Entity(name = "gs_artwork_info")
@Name("artworkInfo")
public class ArtworkInfo implements Serializable {

public static final long serialVersionUID = 596009789005L;

@Id
@GeneratedValue(strategy = AUTO)
@Column(name = "artwork_info_id")
private int id;

@NotNull
@Column(name = "file_name", unique = true)
private String fileName;

@NotNull
@Column(name = "content_type")
private String contentType;

@NotNull
@Column(name = "upload_date")
private Date uploadDate;

@NotNull
private String title;

@NotNull
@Column(name = "hit_count")
private int hitCount;

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "artwork_id")
private Artwork artwork;

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "thumbnail_id")
private Thumbnail thumbnail;

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "uploader")
private Member uploader;

// setters/getters omitted
}


The One-To-One relationship is defined using the @OneToOne annotation and is mapped using the @JoinColumn annotation. The @JoinColumn annotation defines the column in the ArtworkInfo's table that maps to join column, primary key in this case, of the Artwork's table. The @OneToOne annotation specifies the fetch type to be LAZY to avoid eagerly loading the artwork instance from the database. If you don't specify this property, your persistence provider would simply run a second query to retrieve all the columns of the Artwork's table when an instance of ArtworkInfo is read from the database.

To define the One-To-Many relationship between Member and ArtworkInfo you need to have an instance of Member annotated with @ManyToOne in the ArtworkInfo class. To access all the artworks uploaded by a member you need to define a collection-based relation property, Set<ArtworkInfo> in this case, with the @OneToMany annotation. The mappedBy property tells the persistence manager that the information for mapping this relationship to our tables is specified in the ArtworkInfo class, specifically to the uploader property of ArtworkInfo.


@Entity(name = "gs_member")
@Name("member")
public class Member implements Serializable {

public static final long serialVersionUID = 596009789001L;

@Id
@GeneratedValue(strategy = AUTO)
@Column(name = "member_id")
private Integer id;

@NotNull
@Length(max = 30, min = 2)
@Column(name = "member_name", unique = true)
private String memberName;

@OneToMany(mappedBy = "uploader", fetch = FetchType.LAZY)
private Set<ArtworkInfo> artworks;

// setters/getters omitted
}


The Artwork and Thumbnail classes are almost identical with minimal difference between the two. Since the data property stores the actual artwork in binary format you need to have the @Lob annotation. It informs the persistence manager that this property requires a lot of memory and that it needs to handle it internally as Blob. Here even though the data property is specified to be Lazily fetched, the persistence manager simply ignores this property; I have tried to use java.sql.Blob but even in this case the hint of lazily fetching the property was ignored.


@Entity(name = "gs_artwork")
@Name("artwork")
public class Artwork implements Serializable {

public static final long serialVersionUID = 596009789003L;

@Id
@GeneratedValue(strategy = AUTO)
@Column(name = "artwork_id")
private int id;

// the persistence manager ignores the fetch property.
@Lob
@Basic(fetch=FetchType.LAZY)
private byte[] data;

// setters/getters omitted
}

4 comments:

Anonymous said...

could you please publish your sources? or did i just miss the download link?

Jay said...

Unfortunately, blogger does not have a feature to attach the source files. If you believe that something is missing then let me know about it.

Anonymous said...

Great post; is there anything in Seam 2.0+ that would change the way you implemented your image viewer?

Unknown said...

Hi, interesting topic.
May I ask you why you chose to store the images into the database as mediumblobs instead of simply storing them as files on the hard drive?
Doesn't it have performance implications?

Said