Wednesday, 29 February 2012

JDK7 EJB3.1 and Netbeans Project (Part II) - Hibernate and Transactions

Part I - Introduction, Part II - Hibernate and Transactions, Part III - Testing

Hibernate LazyInitializationException


In a Model-View-Controller pattern, the part that deals primarily with Transactions and Hibernate is the Model. This means the View, that needs the data to render the result to the user, is outside the transaction and in Hibernate this often causes LazyInitializationExceptions. Especially when traversing to proxies of collections inside the entities. In order to prevent this there are several solutions described in Open Session In View(1) article.

They are summarized below.
  1. use an interceptor, when the server is hit automatically start a transaction, when the result is transmitted back, automatically close/commit the transaction
  2. just make sure the Model provides all the data to the View, so the view does not run into the LazyInitializationException.
  3. have the view open a new transaction to retrieve the data, after the model is finished (which is a really really bad idea)
  4. have the framework deal with it
I prefer the last option, have the framework deal with it. At work, for example, this is done by using JBoss Seam and I must say, I've never had to deal with LazyInitializationExceptions.

Enterprise Java Beans - The Old Way


The good part of Enterprise Java Beans is that they provide the transaction support on the container level, so you, as a developer, do not need to be concerned with it. The bad part is that to access a Enterprise Java Bean requires either another Enterprise Java Bean or a call to the InitialContext. Like in the code below.

/**
 * Retrieve my gamebean.
 */

private GameBeanLocal lookupGameBeanLocal()
{
    GameBeanLocal gbl = null;
    try
    {
        javax.naming.Context c = new InitialContext();
        gbl = (GameBeanLocal) c.lookup("java:global/game/game-ejb/GameBean!mmud.beans.GameBeanLocal");
    } catch (NamingException ne)
    {
        itsLog.throwing(this.getClass().getName(), "lookupGameBeanLocal", ne);
        throw new RuntimeException(ne);
    }
    itsLog.exiting(this.getClass().getName(), "lookupGameBeanLocal");
    if (gbl == null)
    {
        throw new NullPointerException("unable to retrieve GameBean");
    }
    return gbl;
}
This is the code usually used in the WAR file of your EAR file to contact your Enterprise Java Beans. Any Hibernate entities the EJBs return suffer from the LazyInitializationException.

Enterprise Java Beans 3.1


But now, there's Enterprise Java Beans 3.1 which solves this problem, by the following new items:
  • EJBs can be contained inside your WAR
  • Context and Dependency Injection works in most (more) cases

For example the following Enterprise Java Bean was put inside the WAR, and annotated with REST Annotations and uses Hibernate Entities.

/**
 * Comment Enterprise Bean, maps to a Comment Hibernate Entity.
 * @author mr. Bear
 */

@Stateless
@Path("/comments")
public class CommentBean
{
    @PersistenceContext(unitName = "myDataSource")
    private EntityManager em;

    @EJB
    JobBean jobBean;

    protected EntityManager getEntityManager()
    {
        return em;
    }

    public CommentBean()
    {
    }

    @POST
    @Override
    @Consumes(
    {
        "application/xml", "application/json"
    })
    public void create(Comment entity)
    {
        getEntityManager().persist(entity);
    }

    @PUT
    @Override
    @Consumes(
    {
        "application/xml", "application/json"
    })
    public void edit(Comment entity)
    {
        getEntityManager().merge(entity);
    }

    @DELETE
    @Path("{id}")
    public void remove(@PathParam("id") Long id)
    {
        getEntityManager().remove(find(id));
    }

    @GET
    @Path("{id}")
    @Produces(
    {
        "application/xml", "application/json"
    })
    public Comment find(@PathParam("id") Long id)
    {
        return getEntityManager().find(Comment.class, id);
    }
}

The Entity has appropriate annotations to indicate it can be converted to JSON and/or XML.
/**
 * Comment Entity mapped to the Comment table in the database.
 * @author mr. bear
 */

@Entity
@Table(name = "Comment")
@XmlRootElement
public class Comment implements Serializable
{
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Long id;
    @Size(max = 255)
    @Column(name = "author")
    private String author;
    @Basic(optional = false)
    @NotNull
    @Column(name = "submitted")
    @Temporal(TemporalType.TIMESTAMP)
    private Date submitted;
    @Lob
    @Size(max = 65535)
    @Column(name = "comment")
    private String comment;
    @JoinColumn(name = "galleryphotograph_id", referencedColumnName = "id")
    @ManyToOne(optional = false)
    private GalleryPhotograph galleryphotographId;

    public Comment()
    {
    }

    public Comment(Long id)
    {
        this.id = id;
    }

    public Comment(Long id, Date submitted)
    {
        this.id = id;
        this.submitted = submitted;
    }

    public Long getId()
    {
        return id;
    }

    public void setId(Long id)
    {
        this.id = id;
    }

    public String getAuthor()
    {
        return author;
    }

    public void setAuthor(String author)
    {
        this.author = author;
    }

    public Date getSubmitted()
    {
        return submitted;
    }

    public void setSubmitted(Date submitted)
    {
        this.submitted = submitted;
    }

    public String getComment()
    {
        return comment;
    }

    public void setComment(String comment)
    {
        this.comment = comment;
    }

    @JsonIgnore
    @XmlTransient
    public GalleryPhotograph getGalleryphotographId()
    {
        return galleryphotographId;
    }

    public void setGalleryphotographId(GalleryPhotograph galleryphotographId)
    {
        this.galleryphotographId = galleryphotographId;
    }
}
And, voilĂ , no more LazyInitializationExceptions, no more retrieving EJBs through the InitialContext, no more EARs containing WARs and EJB JARs.

Infinite Recursion


One of the problems that occur, when you do NOT have any LazyInitializationExceptions, is Infinite Recursion. This happens when your Hibernate entities refer to each other, and in a REST service, Jersey tries to flatten the structure into JSON or XML for transmission.

This could be the case, in the example above, if there was a collection of comments in galleryphotograph, and a reference to the respective galleryphotograph in the comments.

In order to solve this, make sure to use XmlTransient and JsonIgnore at appropriate places.

Conclusion


The last paragraph "Can't this be done easier" in the Open Session In View is awesome. It provides the answer that the framework should handle all the transaction management, instead of yourself having to provide it.

And now this time has come! The new EJB 3.1 version allows you to put EJBs right there in your WAR! Either as a separate JAR file, or as class files. The same classloader will pick them up and you can use them in your classes via Dependency Injection as much as you like!

It does mean there is no modularization, but in my experience modularization is only a requirement for the exceptionally high-end big projects.

References

Open Session In View
https://community.jboss.org/wiki/OpenSessionInView
Data Transfer Objects
http://martinfowler.com/eaaCatalog/dataTransferObject.html
Wikipedia : Data Transfer Object
http://en.wikipedia.org/wiki/Data_transfer_object
Java Persistence With Hibernate
Christian Bauer, Gavin King
Is Java EE 6 War The New EAR? The Pragmatic Modularization And Packaging
http://www.adam-bien.com/roller/abien/entry/is_java_ee_6_war

Sunday, 19 February 2012

JDK7 EJB3.1 and Netbeans Project (Part I) - Introduction

Part I - Introduction, Part II - Hibernate and Transactions, Part III - Testing

Introduction


I've tasked myself with learning the new things available to JDK 7 and EJB 3.1 and how they integrate with Netbeans. In order to so do, in my experience, it is most gratifying to pick up a new project using these new technologies.

In this case, as at the time, I was wondering what to do with my old Photographs, I've decided to start up a project called YourPersonalPhotographOrganiser, which is nothing more than a simple Photo Gallery.

You can find the netbeans project on github at https://github.com/maartenl/YourPersonalPhotographOrganiser. Just check it out into your ~/NetBeansProjects/YourPersonalPhotographOrganiser directory, and see how far you get.

It's a work in progress, but it's at the stage where there's something more or less workable. Let me remind you that this software is for use at your own risk. Use it locally, as there is NO security (neither authentication nor authorization) implemented at the moment.

Requirements


  1. simple database, easy to make changes directly, if so required
  2. used for home use
  3. no authentication or authorization required
  4. helps me to understand the jdk 7, glassfish and jee 3.1, by using all the new stuff in there.
  5. absolutely NO changing of the photographs, all changes are done in java, in memory, in glassfish.*
  6. flexible in where these photographs are located (no need to keep them in the webdir, for example)

*) I've had too many instances where:
  1. changing files from webinterface is a security risk, and requires proper access rights.
  2. changing files causes the extra data present in the jpegs put there by photocameras to be discarded
  3. changing files potentially causes deterioration of the quality of the jpegs
  4. changing files has sometimes caused the file to be damaged in some way
  5. changing files makes it impossible to determine if the photo is already present in your collection

Technical

Some of the (new) stuff that is being used.
  1. JDK7 (Look for "JDK7" in the sourcecode)
    1. multiple catch
    2. try-with-resources
    3. new switch statement
    4. diamond-notation
    5. filevisitor interface
    6. Path class usage
  2. EJB 3.1
    1. no local interfaces on beans
    2. EJBs inside the WAR, no longer is an EAR required
    3. Improved Context and Dependency Injection
  3. Netbeans IDE 7.0.1.
  4. GlassFish Server Open Source Edition 3.1.1 (build 12).
  5. JPA (Hibernate)
  6. REST (Jersey)
  7. MySQL
  8. JQuery
  9. HTML, CSS, JavaScript and AJAX
  10. JSON

Database Schema

The database schema below shows the used Hibernate Entities. They have the same name as the tables. The database script below should run without errors on your average MySQL database.
drop table if exists Log;
drop table if exists Tag;
drop table if exists Comment;
drop table if exists GalleryPhotograph;
drop table if exists Gallery;   
drop table if exists Photograph;
drop table if exists Location;

create table Location (
 id bigint not null auto_increment primary key,
 filepath varchar(512)
);

create table Photograph (
 id bigint not null auto_increment primary key,
 location_id bigint not null,
 filename varchar(255),
 relativepath varchar(1024),
 taken timestamp,
 hashstring varchar(1024),
 filesize bigint,
 angle int,
 foreign key (location_id) references Location (id)
);

create table Gallery (
 id bigint not null auto_increment primary key,
 name varchar(80),
 description text,
 creation_date timestamp not null default current_timestamp,
 parent_id bigint,
 highlight bigint,
 sortorder int not null,
 foreign key (parent_id) references Gallery (id),
 foreign key (highlight) references Photograph (id)
);

create table GalleryPhotograph (
 id bigint not null auto_increment primary key,
 gallery_id bigint not null,
 photograph_id bigint not null,
 name varchar(255),
 description text,
 sortorder bigint,
 foreign key (gallery_id) references Gallery (id),
 foreign key (photograph_id) references Photograph (id)
);

create table Comment (
 id bigint not null auto_increment primary key,
 galleryphotograph_id bigint not null,
 author varchar(255),
 submitted timestamp,
 comment text,
 foreign key (galleryphotograph_id) references GalleryPhotograph (id)
);

create table Tag (
 tagname varchar(80) not null,
 photograph_id bigint not null,
 primary key (tagname, photograph_id),
 foreign key (photograph_id) references Photograph (id)
);

create table Log (
 id bigint not null auto_increment primary key,
 jobdate timestamp not null default current_timestamp,
 joblog blob not null
);

-- only allows a photograph to appear once in a gallery
create unique index unique_per_photograph_per_gallery
on GalleryPhotograph (gallery_id, photograph_id);

Update 1: Moved angle field from GalleryPhotograph over to Photograph
Update 2: It's nice to have a script for creating the database, but an ORM can automatically generate the proper tables for you if you like.