Monday, January 29, 2007

EJB 3.0 Outside the Container and Inside the JVM - Part 1: The POJO Model

NOTE: This is a more truncated version of an entry I started previously. The aim is to keep it simpler and to actually finish this it...

ANOTHER NOTE: I use Netbeans. It's not popular but I like it. If you don't have Netbeans you'll still be able to follow this series but won't be able to drag and drop a Swing UI in part 4 using Matisse. My heart bleeds...

EJB 3.0 and JPA 1.0 have made it easy for people like me (RDBMS-phobics) to persist our O-O domain models. What's more, Derby (aka JavaDB) has made it easy to have within-JVM RDBMS persistence that is transparent to the user of your thick client application. What follows is a simple example application I wrote to learn some of the basic concepts (and also track my expenses claims)

Step 0: Setting up my project.

I like Maven; Maven 1.0.x to be precise. Before anything else I set up a basic maven project called "expenses-tracker-model" in the standard fashion:


You'll also need to manually get hold of the JPA and Derby Jars (toplink-essentials.jar, derby-10.1.jar and javaee-9.jar) and place them in your repository wherever you see fit.

Step 1: Coding the Domain Model

I like to model in OO land. My model is very simple - a single, serializable POJO class called "Claim" - which will eventually map to a single Derby table called "CLAIM". I create my java class and add attributes as follows:
private Long id;
private String refNumber;
private String projectCode;
private String description;
private Date dateFrom;
private Date dateTo;
private String status;
private Date submitDate;
private Date paidDate;
private Long totalClaimed;
private Long totalFromCurrentAccount;
I now need to add the annotations which will allow JPA to work its magic. First I tell the class that it is an entity (i.e. will be represented in the RDBMS) with the following annotation:
import javax.persistence.Entity;
...
@Entity
public class Claim implements Serializable {
...
The compiler is now clever enough to know that, unless told otherwise, the attributes on this class will be represented as columns in our table. That's how EJB 3.0 works. It assumes the default unless you tell it different. Very handy.

The observant amongst you will realise that we most likely need a primary key. You're right. We need to declare which will be our primary key field and how we will work with it. This is done with the @Id annotation. The @GeneratedValue annotation tells JPA that we'dlike the RDBMS to auto generate our PKs for us (which makes things even easier at our O-O end):
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
...
/** Unique, datastore generated id */
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
Finally, because we have java.util.Date fields, we need to provide some extra information about these for JPA also:
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
...
@Temporal(TemporalType.TIMESTAMP)
private Date dateFrom;
We're nearly there. Another quick IDE aided step to encapsulate our attributes with some getters and setters and we're ready to go. You should now have something like this:
package com.andrewharmellaw.exptracker.model;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Claim implements Serializable {

/** Unique, datastore generated id */
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String refNumber;

private String projectCode;

private String description;

@Temporal(TemporalType.TIMESTAMP)
private Date dateFrom;

@Temporal(TemporalType.TIMESTAMP)
private Date dateTo;

private String status;

@Temporal(TemporalType.TIMESTAMP)
private Date submitDate;

@Temporal(TemporalType.TIMESTAMP)
private Date paidDate;

private Long totalClaimed;

private Long totalFromCurrentAccount;

/** Creates a new instance of Claim */
public Claim() {

}

public Long getId() {
return id;
}

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

public String getRefNumber() {
return refNumber;
}

public void setRefNumber(String refNumber) {
this.refNumber = refNumber;
}

public String getProjectCode() {
return projectCode;
}

public void setProjectCode(String projectCode) {
this.projectCode = projectCode;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Date getDateFrom() {
return dateFrom;
}

public void setDateFrom(Date dateFrom) {
this.dateFrom = dateFrom;
}

public Date getDateTo() {
return dateTo;
}

public void setDateTo(Date dateTo) {
this.dateTo = dateTo;
}

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}

public Date getSubmitDate() {
return submitDate;
}

public void setSubmitDate(Date submitDate) {
this.submitDate = submitDate;
}

public Date getPaidDate() {
return paidDate;
}

public void setPaidDate(Date paidDate) {
this.paidDate = paidDate;
}

public Long getTotalClaimed() {
return totalClaimed;
}

public void setTotalClaimed(Long totalClaimed) {
this.totalClaimed = totalClaimed;
}

public Long getTotalFromCurrentAccount() {
return totalFromCurrentAccount;
}

public void setTotalFromCurrentAccount(Long totalFromCurrentAccount) {
this.totalFromCurrentAccount = totalFromCurrentAccount;
}
}

Right, that's the dull part done. Now we can get to the fun part - using this POJO to auto-create our database. That's in the next post in this series...

Saturday, January 20, 2007

Kill Your Computer

I mean it. Kill it. It's probably on its last legs anyway. Then get a new one and see how much you've lost. Didn't I tell you to backup before you wielded the sledgehammer? Sorry.

My laptop died yesterday. What with a previous entry about how great life was with all your stuff online now was the time to test it out. Job 1: Dowload Firefox. Job 2: Get Google Browser Synch up and running. Job 3: Get my favourite extensions and I'm up and running.

Its interesting to me to see what else I felt I had to install to be productive. For me this was the following:
I was up and going in 2 hours (Slight aside: If I had Eclipse rather than Netbeans how long would this have taken? A lot longer). Now at th emoment I think that's pretty cool. What would really make my day would be if Google Browser Synch remembered which of my bookmarks were on my toolbar and what firefox extensions I used. Oh, and a web based version of Thinking Rock would be great too. I can but hope...

NOTE: If you want to kind of feel like what its like to lose everything just run Windows Cleanup - I lost loads of old files which MSFT deemed no longer important. :-( That'll teach me.

Tuesday, January 02, 2007

Running my Java 5 App on MacOS X

I've written a simple app for my wife in Java (using Netbeans Matisse). I've got an XP laptop which I use to develop on. Running it was simple within the IDE and I created a simple zip file containing my jar, the required libraries and the derby database files and a .bat file to make running it a double click affair. How hard could it be to move it to her Mac?

Being quite simplistic about it and having worked at Sun in the past (Unix is Unix right?) I thought I could create a simple .sh equivalent of the .bat file to protect my beloved from the intricacies of the "java" command. Before this I thought I'd just try out the command at the terminal to check it worked. Best practice you see...

Wrong. I kept getting errors about my jar files (all of them) not being excutable ("cannot execute binary file"). My first thought was that it was a Windows / Mac newline thing... nope. Corrupted JAR files?.. Nope. Then I found a blog entry about how my problem was common and you needed to wrap things in a shell script. I did, and then started getting all manner of even wierder errors (most likely due to my terrible korn shell skills rather than anything else). I was stuck but had the distinct feeling that there was a very simple solution.

There was. I had my classpath seperated with ";"'s. Macs don't like that (I'd forgetten that other unixes don't either - so much for my Solaris skills) and need ":"'s instead. Dammit. Now she runs like a dream (and looks sweet too with the default MacOS X liquid look and feel...