 |
Rails and JPA (Instead of ActiveRecord)
Posted by bleonard on September 26, 2007 at 08:30 AM | Comments (10)
Out of sheer morbid curiosity, I wondered what it would take to replace ActiveRecord with JPA in the classic blog demo. Jeroen Zwartepoorte, a developer I met last week at RailsConf, convinced me to go ahead and publish my results.
In almost all cases, I tried to keep the view and controller code similar to what you would expect from generated scaffolding, so all of the JPA code is isolated in the model class. I also refrained from making any changes to the Java entity classes, but certainly that's also a route worth exploring.
Getting Started
- Download and install NetBeans 6.0 Beta 1. Grab the Full distribution so you can get the Java IDE, Ruby and GlassFish.
Creating the Java Entity Classes
For consistency, I'm going to create the same blog application that's know to every Rails developer. However, instead of generating a model and running migrations, we'll create an Entity class and a persistence unit.
- Create a new Java Application named Post. There's no need for a Main Class.
- Create a new Persistence Unit. Select jdbc:derby://localhost:1527/sample [app on App] as the database connection and click Finish. Switch the the XML view and set the toplink.jdbc.password property to app.
- Create a new Entity Class named Posts. Set the package name to entity.
- Add fields and accessors for title and body. Your completed class should look as follows:
/*
* Posts.java
*
* Created on Sep 26, 2007, 10:15:53 AM
*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package entity;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
*
* @author bleonard
*/
@Entity
public class Posts implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String title;
private String body;
public void setId(Long id) {
this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
Test the Entity Classes
- Create a new JUnit Test named PostsTest in the entity package. You can uncheck the Test Initializer and Finalizer, we will not be using them.
- Add code to initialize the Entity Manager and test the Post entity. My complete PostTest class looks as follows:
/*
* PostsTest.java
* JUnit 4.x based test
*
* Created on September 24, 2007, 7:42 PM
*/
package entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author bleonard
*/
public class PostsTest {
private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("PostPU");
private static EntityManager em = emf.createEntityManager();
private static EntityTransaction trans = em.getTransaction();
public PostTest() {
}
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testPersist() {
// Persist some data...
trans.begin();
Posts post = new Posts();
post.setId(new Long(100));
post.setTitle("George");
post.setBody("Jetson");
em.persist(post);
trans.commit();
// Retrieve the data...
Posts result = em.find(Posts.class, new Long(100));
assertEquals(post, result);
//Clean up for the next test...
trans.begin();
em.remove(post);
trans.commit();
}
}
- Add the Java DB Driver Library to the project's Test Libraries.
- Start the Database (Tools > Java DB Database > Start Server)
- Press Shift+F6 to run the test:
Create the Rails Application
Now that we appear to have a functioning entity class, let's create the Rails application that will use that class.
- Create a new Ruby on Rails application named jpa_blog.
- Generate a controller named blog with views list, new, show and edit. We'll develop each of these views in turn.
- In the Models folder, create a new Ruby class named Post (note, we are not generating a Post model).
Step 1: Listing the Entries
Code the View
- Replace the contents of list.rhtml with the following:
<h1>The Ruby Blog</h1>
<% @posts.reverse.each do |post| %>
<h2><%= post.title %></h2>
<p><%= post.body %></p>
<small> <%= link_to 'Permalink', :action => 'show', :id => post %></small>
<hr>
<% end %><br />
<%= link_to 'New post', :action => 'new' %>
Code the Controller
- Add the following to blog_controller.rb:
def index
list
render :action => 'list'
end
def list
# Declare a paginator for the posts table
@post_pages, @posts = paginate :posts, :per_page => 10
end
Code the Model
- Add the following to the top of post.rb:
require 'java'
include_class 'javax.persistence.Persistence'
include_class 'javax.persistence.EntityManager'
include_class 'javax.persistence.EntityManagerFactory'
include_class 'java.util.List'
include_class 'java.lang.Long'
include_class 'entity.Posts'
- Add the accessors:
attr_accessor :id, :title, :body
- Define private methods for getting the entity manager and finding all rows in the table:
private
def self.getEntityManager
emf = Persistence.createEntityManagerFactory("PostPU")
return emf.createEntityManager()
end
def self.find_all
query = getEntityManager.createQuery("SELECT p FROM Posts p")
list = query.getResultList()
posts = [] #Create a new array to return
list.each {|post|
temp = Post.new
temp.id = post.getId
temp.title = post.getTitle
temp.body = post.getBody
posts << temp
}
return posts
end
- Define the methods that will be called by the paginate method used in the controller (these are public):
# Called by `paginate'
def self.count(*args)
query = getEntityManager.createQuery("SELECT p FROM Posts p")
count = query.getResultList().size();
puts count
end
def self.find(*args)
case args.first
when :all then find_all
else find_from_id(args.first)
end
end
Configure JRuby
- Open the Options dialog to find the location of your JRuby interpreter.
- Copy the following jars to your JRuby lib directory:
- Post.jar (build your project if you don't have a Post.jar)
- derbyclient.jar (Tools > Java DB Database > Settings will give you the location of derbyclient.jar)
- toplink-essentials.jar and toplink-essentials-agena.jar (check the Library Manager for the location of TopLink Essentials)
Test the List
This will not be very exciting because the list is empty (you can tweak the unit test to add some entries). But we will verify the application runs without exceptions. With your cursor in the index action, press F6 to start WEBrick and launch the browser. Browse to http://localhost:3000/blog.
Step 2: Creating a New Entry
Code the View
- Create a new partial, _form.rhtml, to be used by both the new and edit pages:
<p>Title<br/>
<%= text_field 'post', 'title' %></p>
<p>Body<br/>
<%= text_area 'post', 'body' %></p>
- Replace the contents of new.rhtml with the following:
<h1>New post</h1>
<% form_tag :action => 'create' do %>
<%= render :partial => 'form' %>
<%= submit_tag "Create" %>
<% end %>
<%= link_to 'Back', :action => 'list' %>
Code the Controller
- Add the following to blog_controller.rb:
def create
@post = Post.new(params[:post])
if @post.save
flash[:notice] = 'Post was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end
Code the Model
- Define the initialize and save methods in post.rb:
def initialize(attributes = nil)
if !attributes.nil?
@title = attributes[:title]
@body = attributes[:body]
end
end
def save
p = Posts.new
p.title = title
p.body = body
em = Post.getEntityManager
em.getTransaction().begin()
em.persist(p);
em.getTransaction().commit()
return true
end
Test Adding a New Entry
- Return to the browser and click the new link. Add a new post.


Step 3: Viewing an Entry
Code View
- Replace the contents of show.rhtml with the following:
<p>
<b>Title:</b> <%= h @post.title %>
</p>
<p>
<b>Body:</b> <%= h @post.body %>
</p>
<%= link_to 'Edit', :action => 'edit', :id => @post %>
<%= link_to 'Back', :action => 'list' %>
Code the Controller
- Add the following to blog_controller.rb:
def show
@post = Post.find(params[:id])
end
Code the Model
- Add the following to_param convenience method which I copied from base.rb:
# Copied from base.rb. Allows us to specify post rather than post.id in list.rhtml.
# Enables Active Record objects to be used as URL parameters in Action Pack automatically.
def to_param
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
end
- Add the following private method:
def self.find_from_id(id)
result = getEntityManager.find(Posts.java_class, Long.new(id))
post = Post.new
post.id = result.get_id
post.title = result.get_title
post.body = result.get_body
return post
end
Test Viewing and Entry
- Return to your browser, refresh the page (so the Permalink is generated correctly) and click the Permalink:

Step 4: Editing an Entry
Code the View
- Replace the contents of edit.rhtml with the following:
<h1>Editing post</h1>
<% form_tag :action => 'update', :id => @post do %>
<%= render :partial => 'form' %>
<%= submit_tag 'Edit' %>
<% end %>
<%= link_to 'Show', :action => 'show', :id => @post %>
<%= link_to 'Back', :action => 'list' %>
Code the Controller
- An edit and update methods to blog_controller.rb:
def edit
@post = Post.find(params[:id])
end
def update
@post = Post.find(params[:id])
if @post.update_attributes(params[:post])
flash[:notice] = 'Post was successfully updated.'
redirect_to :action => 'show', :id => @post
else
render :action => 'edit'
end
end
Code the Model
- Add the update_attributes to post.rb:
def update_attributes(attributes)
p = Posts.new
p.id = id
p.title = attributes[:title]
p.body = attributes[:body]
em = Post.getEntityManager
em.getTransaction().begin()
em.merge(p);
em.getTransaction().commit()
return true
end
Test Editing an Entry
- Return to your browser and click the Edit link:


Resources
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Hi
when I tried to run the zip files, I received the following error.
oracle.toplink.essentials.exceptions.DatabaseException:
Internal Exception: java.sql.SQLException: java.net.ConnectException : Error connecting to server localhost on port 1527 with message Connection refused: connect.
Error Code: -4499
Please explain and help.
Thank you
Posted by: guitardj on September 30, 2007 at 09:20 PM
-
Hi. I believe you just need to start the Database server (Tools > Java DB Database > Start Server). Hope the helps, Brian.
Posted by: bleonard on October 01, 2007 at 06:28 AM
-
Hi Brian,
Thanks for the tip, I changed to use mySQL instead. Now everything is working properly.
As I was going through the tutorial, I was wonder how/why you know that paginate invokes the count and find methods. Did you look at the API or any other resources? Please let me know.
Thanks for your time
Jason
Posted by: guitardj on October 01, 2007 at 10:25 PM
-
Hi Jason, good question. I used a couple of techniques. First would be the exception trace that indicated the method was missing. For example:
NoMethodError in BlogController#index
undefined method `count' for Post:Class
RAILS_ROOT: script/../config/..
Application Trace |
Framework Trace |
Full Trace
/Applications/NetBeans/NetBeans 6.0 Beta 1.app/Contents/Resources/NetBeans/ruby1/jruby-1.0.1/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/pagination.rb:173:in `count_collection_for_pagination'
/Applications/NetBeans/NetBeans 6.0 Beta 1.app/Contents/Resources/NetBeans/ruby1/jruby-1.0.1/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/pagination.rb:197:in `paginator_and_collection_for'
/Applications/NetBeans/NetBeans 6.0 Beta 1.app/Contents/Resources/NetBeans/ruby1/jruby-1.0.1/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/pagination.rb:130:in `paginate'
app/controllers/blog_controller.rb:10:in `list'
app/controllers/blog_controller.rb:4:in `index'
Second, I would use the Ruby debugger against an ActiveRecord version of the application running on the CRuby interpreter to find out about where the method was located and how it was called. Brian
Posted by: bleonard on October 02, 2007 at 07:31 AM
-
hi brain,
thanks for the information. I will definitely take a shot once I get home. This might sound stupid, but what benefits do you think by using JPA instead of ActiveRecord have? It looks to me that we need to re-implement a lot features ActiveRecord already provides, and also we need to track down all the methods and implement them again. Please let me know what you think. Thanks!
Posted by: guitardj on October 02, 2007 at 10:11 AM
-
Hi Brian,
Thanks again for this wonderful blog. Quick question. If I decide to have another table called "User", does it mean that I will need to create another Entity class called "Users" and another Persistence Unit for the Entity "Users"? If so, then aren't we going to deal with many Persistence Unit and Entity files?
do you think it's possible to create a generic parent Entity class and one generic Persistence Unit xml file?
Thanks
Jason
Posted by: guitardj on October 02, 2007 at 11:01 PM
-
I first starting thinking about Rails and JPA after hearing about Twitter's performance problems at last May's RailsConf in Portland. That being said, however, I have no idea if the the JPA configuration would perform any better than ActiveRecord. A more likely reason would be that you already have an investment in Java entity classes and would like to leverage them from a Rails application. /Brian
Posted by: bleonard on October 03, 2007 at 05:27 AM
-
Jason, in Java, persistence.xml (the persistence unit) is equivalent to Rails' database.yml. No matter how many tables you had, you would only need 1 persistence unit (unless for some reason the tables were in another database).
Entity classes are equivalent to Rails' model classes, so you would also have an additional model class in Rails if you added a User table. The difference is the content of the 2 files. The Rails framework is smart enough to dynamically inspect the schema and provide accessors to the database. In Java, you need to define the database columns in the entity class. You will note with JPA, however, that we never actually created the table - it was created for us by the persistence provider based on the definition in the entity class. The Rails camp will argue that this is what migrations are for :-).
/Brian
Posted by: bleonard on October 03, 2007 at 08:56 AM
-
Hi Brian,
Thank you so much. I appreciate your time. Hope you will have more blogs about using JPA with Rails
Posted by: guitardj on October 03, 2007 at 09:31 AM
-
Jason, sure thing. If you have additional ideas, just let me know. I've been considering a blog on debugging into the Rails source, showing, for example, exactly how I knew paginate invokes count. /Brian
Posted by: bleonard on October 04, 2007 at 01:01 AM
|