Ejb 3 Tutorial 2 - Printable

Session Bean and JPA


This tutorial revisits the entities from JPA Tutorial 1 - Getting Started and introduces a DAO, which gets its entity manager auto-wired.

Setting Up Your Project

The instructions for setting up your project mirror those from the first tutorial: EJB3 Tutorial 1 - Create and Configure.

For the remainder of this tutorial, when you see <project>, replace it with Ejb3Tutorial2.

Create the Project

  1. Pull down the File menu and select new:project
  2. Select Java Project
  3. Click Next
  4. Enter <project>
  5. Under Project Layout select create separate source and output folders
  6. Click Finish
  7. Select <project>, right-click and select new:Source Folder
  8. Enter conf for the name
  9. Click on Finish
  10. Select <project>, right-click and select new:Source Folder
  11. Enter test for the name
  12. Click on Finish

Edit your project properties

Now that we have created a user library, we can add that user library to our project:
  1. Select <project>, and press alt-enter or right-click and select properties.
  2. Select Java Build Path
  3. Select the Libraries tab
  4. Click on Add Library
  5. Select User Library and click Next
  6. Click on the box next to EJB3_EMBEDDABLE and click Finish
  7. Click Add Library
  8. Select JUnit and click Next
  9. In the pull-down list, select JUnit 4 and click Finish
  10. Click on OK to make the change to your project's classpath

Setup the configuration files

The JBoss Embeddable container looks for several files in the classpath. To keep all of these in one place, we'll add another source directory to our project and then import several files into that directory.
  1. Select the conf folder under <project>
  2. Pull down the File menu and select Import
  3. Expand General
  4. Select File System and click on Next
  5. Click on Browse and go to the following directory: C:/libs/jboss-EJB-3.0_Embeddable_ALPHA_9/conf
  6. Click on OK
  7. You'll see conf in the left pane, select it
  8. Verify that the Into folder: lists <project>/conf (if not, just enter it or browse to it)
  9. Click Finish
  10. Expand the conf directory and verify that the files are now there

Add Resource Adapter Archive(RAR)

The Java Connector system defines Resource Adapter Archive files (RAR files). We need to add a few RAR files into the class path. We will import two more files into the conf directory:
  1. Select the conf folder
  2. Pull down the File menu and select Import
  3. Expand General
  4. Select File System and click on Next
  5. Click on Browse and go to the following directory: C:/libs/jboss-EJB-3.0_Embeddable_ALPHA_9/lib
  6. Select jcainflow.rar and jms-ra.rar
  7. Click Finish

Create a jndi.properties file

Note, depending on the version of the embeddable container you download, you might already have a file called jndi.properties. If you do, skip to the next section.
  1. Select the conf directory, right-click and select new then select File
  2. Enter the name jndi.properties and click finish
  3. Enter the following 2 lines then save and close the file:
java.naming.factory.initial=org.jnp.interfaces.LocalOnlyContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces

Create a persistence.xml

This example presents a utility class we'll be using later. The container needs a persistence.xml file to operate. This file must be found under a META-INF directory somewhere in the classpath or the embeddable container will not start. The file's name is persistence.xml with a lower-case 'p'. On a Unix system, this will make a difference. On a PC, this won't make a difference and it is one of those things that might work on your machine but not on the linux build box.

  1. Select your src directory
  2. Right-click, select New:Folder
  3. Enter META-INF
  4. Click OK
  5. Select META-INF
  6. Right-lick, select New:File
  7. Enter persistence.xml
  8. Click Finish
  9. Copy the following example into your new file then save it by pressing ctrl-s

persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
   <persistence-unit name="custdb">
 
    <!-- This persistence unit uses the default data source that JBoss    -->
    <!-- defines called DefaultDS. If we wanted to use our own data       -->
    <!-- source, we'd need to define a custom data source somewhere.      -->
    <!-- That somewhere is vendor specific.                               -->
 
    <!-- In the case of JBoss, since we're using the embedded container,  -->
    <!-- we need to add two beans in a file called                        -->
    <!-- embedded-jboss-beans.xml. We name the first                      -->
    <!-- HypersonicLocalServerDSBootstrap and we name the second          -->
    <!-- HypersonicLocalServerDS. This two step process defines a data    -->
    <!-- source.                                                          -->
 
    <!-- In the first bean definition, we additionally bind it in Jndi    -->
    <!-- under some name. If we used the name                             -->
    <!-- java:/HypersonicLocalServerDS then we would use the following    -->
    <!-- entry to use that data source instead of the default one:        -->
    <!-- <jta-data-source>java:/HypersonicLocalServerDS</jta-data-source> -->
 
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>


The Entity Model

Creating the Entity Model

We need to create our entity model. There are three classes in it:
  • Address
  • Company
  • Person

To create the basic classes:
  1. Select the src folder under <project>
  2. Right-click and select New::Class
  3. For the package, enter entity
  4. For the name, enter Address, Company, and Person respectively.

Type or enter the source provided below for each of the three classes.

Changes

The entity model is changed slightly from the first JPA tutorial. There is one change in Company.java. Review that class' comments to understand that change. The quick summary is that we've added eager fetching to a relationship.

Address.java
package entity;
 
import javax.persistence.Embeddable;
 
/**
 * There are no changes to this entity. Its embedded in the Person and Company
 * and as such, it is automatically fetched.
 */
@Embeddable
public class Address {
    private String streetAddress1;
    private String streetAddress2;
    private String city;
    private String state;
    private String zip;
 
    public Address() {
    }
 
    public Address(final String sa1, final String sa2, final String city,
            final String state, final String zip) {
        setStreetAddress1(sa1);
        setStreetAddress2(sa2);
        setCity(city);
        setState(state);
        setZip(zip);
    }
 
    public String getCity() {
        return city;
    }
 
    public void setCity(final String city) {
        this.city = city;
    }
 
    public String getState() {
        return state;
    }
 
    public void setState(final String state) {
        this.state = state;
    }
 
    public String getStreetAddress1() {
        return streetAddress1;
    }
 
    public void setStreetAddress1(final String streetAddress1) {
        this.streetAddress1 = streetAddress1;
    }
 
    public String getStreetAddress2() {
        return streetAddress2;
    }
 
    public void setStreetAddress2(final String streetAddress2) {
        this.streetAddress2 = streetAddress2;
    }
 
    public String getZip() {
        return zip;
    }
 
    public void setZip(final String zip) {
        this.zip = zip;
    }
}

Company.java
package entity;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
 
import javax.persistence.CascadeType;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
 
/**
 * There is one significant change to this Entity from the one we used in the
 * first JPA tutorial. Review the employees attribute for details on what and
 * why.
 */
@Entity
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    private String name;
 
    @Embedded
    private Address address;
 
    /**
     * This attribute has one important (and significant change) from the
     * original Company.java. We've added eager fetching to this relationship.
     * By default, one to many relationships are lazily loaded. This means the
     * collection will not actually be brought back from the database unless it
     * is specifically used. So long as the Company instance is managed, this is
     * not a problem. Once the object is no longer managed, however, lazily
     * loaded references that have not been touched are not available (according
     * to the specification, what happens is undefined).
     * 
     * In the previous example, we did everything in a single transaction. But
     * now we have a transaction starting and stopping on each method in the
     * session bean, so as soon as we return from one of the session bean
     * methods, the transaction is closed. Once the transaction is closed, all
     * managed objects are flushed from the entity manager (and therefore no
     * longer managed).
     */
    @OneToMany(mappedBy = "job", cascade = { CascadeType.PERSIST, CascadeType.MERGE,
            CascadeType.REFRESH }, fetch = FetchType.EAGER)
    private Collection<Person> employees = new ArrayList<Person>();
 
    public Company() {
    }
 
    public Company(final String name, final Address address,
            final Person... persons) {
        setName(name);
        setAddress(address);
        for (Person p : persons) {
            getEmployees().add(p);
        }
    }
 
    public Address getAddress() {
        return address;
    }
 
    public void setAddress(final Address address) {
        this.address = address;
    }
 
    public Collection<Person> getEmployees() {
        return employees;
    }
 
    public void setEmployees(final List<Person> newStaff) {
        // fire everybody
        final List<Person> clone = new ArrayList<Person>(employees);
 
        for (Person p : clone) {
            fire(p);
        }
 
        for (Person p : newStaff) {
            hire(p);
        }
    }
 
    public Long getId() {
        return id;
    }
 
    public void setId(final Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(final String name) {
        this.name = name;
    }
 
    public void hire(final Person p) {
        employees.add(p);
        p.setJob(this);
    }
 
    public void fire(final Person p) {
        employees.remove(p);
        p.setJob(null);
    }
 
    @Override
    public boolean equals(final Object rhs) {
        if (rhs instanceof Company) {
            return ((Company) rhs).getId() == getId();
        }
 
        return false;
    }
 
    @Override
    public int hashCode() {
        return 101 * getId().hashCode();
    }
}

Person.java
package entity;
 
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
 
/**
 * Give the tests and assertions so far, there's no need to fix this problem.
 */
@Entity
public class Person {
    @Id
    @GeneratedValue
    int id;
    private String firstName;
    private char middleInitial;
    private String lastName;
 
    @Embedded
    private Address address;
 
    @ManyToOne
    private Company job;
 
    public Person() {
    }
 
    public Person(final String fn, final char mi, final String ln,
            final Address address) {
        setFirstName(fn);
        setMiddleInitial(mi);
        setLastName(ln);
        setAddress(address);
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(final String firstName) {
        this.firstName = firstName;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(final int id) {
        this.id = id;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(final String lastName) {
        this.lastName = lastName;
    }
 
    public char getMiddleInitial() {
        return middleInitial;
    }
 
    public void setMiddleInitial(final char middleInitial) {
        this.middleInitial = middleInitial;
    }
 
    public Address getAddress() {
        return address;
    }
 
    public void setAddress(final Address address) {
        this.address = address;
    }
 
    public Company getJob() {
        return job;
    }
 
    public void setJob(Company job) {
        this.job = job;
    }
 
    @Override
    public boolean equals(final Object rhs) {
        if (rhs instanceof Person) {
            return ((Person) rhs).getId() == getId();
        }
 
        return false;
    }
 
    @Override
    public int hashCode() {
        return 101 * getId();
    }
}


The Dao and its Interface

We will create a session bean that wraps access to the company behind a session bean interface. This is another example of the Data Access Object (dao) pattern. This is one of the typical ways we use a session bean.

This dao looks pretty much the same as the session bean from our first EJB example. As with the first example, we've placed the interface in one package and the implementation (the session bean) in a second interface.

CompanyDao.java
package dao;
 
import entity.Company;
 
/**
 * I do not define whether I am remote or local so we won't know how to lookup
 * implementations of me until we get to the concrete implementation that
 * implements me.
 */
public interface CompanyDao {
    void createCompany(Company c);
 
    Company update(Company c);
 
    Company find(Long id);
}

To create this interface:
  1. Expand Ejb3Tutorial2 and select the src directory
  2. Right-click, select New::Interface
  3. Enter dao for the package and CompanyDao for the name
  4. Click Finish
  5. Type the code into the file and save it

CompanyDaoImpl.java
package dao.impl;
 
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
 
import dao.CompanyDao;
import entity.Company;
 
/**
 * Note we've used the optional name parameter to give this bean a name in Jndi.
 * Since this class only implements one interface and since there are no uses of
 * -at- remote here or in the CompanyDao interface, this is a local bean. Thus,
 * the name to lookup will be "CompanyDao/local".
 */
@Stateless(name = "CompanyDao")
public class CompanyDaoImpl implements CompanyDao {
 
    /**
     * We have defined a persistence unit called custdb. To do so, we must
     * update META-INF/persistence.xml. We optionally need to define a new
     * data source (for our examples, we're using the default data source
     * used by JBoss, which is an in-memory HSQL database instance).
     * 
     * Review comments in persistence.xml regarding the details of defining
     * a different data source.
     */
    @PersistenceContext(unitName = "custdb")
    private EntityManager em;
 
    /**
     * This is now a method running under a transaction manager. Since we've
     * defined no transaction attribute, this method's transaction property is
     * equal to REQUIRED. If one is not started, then it will be started. So
     * when we enter this method, assuming no other transaction has started, the
     * container will start a transaction and then when we exit this method, the
     * transaction will be committed (recording any changes we've made).
     */
    public void createCompany(final Company c) {
        em.persist(c);
    }
 
    public Company update(final Company c) {
        return em.merge(c);
    }
 
    /**
     * This find method makes no changes so it can run without a transaction.
     */
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public Company find(final Long id) {
        return em.find(Company.class, id);
    }
}

To create this class:
  1. Expand Ejb3Tutorial2 and select the src directory
  2. Right-click, select New::Class
  3. Enter dao.impl for the package and CompanyDaoImpl for the name
  4. Click Finish
  5. Type the code into the file and save it


Starting JBoss EJB 3 Embeddable Container

As with our first tutorial, we need to be able to start the container. Here's the JBossUtil class again. Below is the file again. However, to get this file into your new tutorial:
  1. Expand your first project (Ejb3Tutorial1)
  2. Expand the src directory, select the util package
  3. Copy it (ctrl-c or right-click and select copy)
  4. Expand your second project (Ejb3Tutorial2)
  5. Select the src directory
  6. Paste it (ctrl-v or right-click and select paste)

JBossUtil.java
package util;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.logging.Logger;
 
import javax.naming.InitialContext;
import javax.naming.NamingException;
 
import org.jboss.ejb3.embedded.EJB3StandaloneBootstrap;
 
/**
 * This class was originally necessary when using the ALPHA 5 version of the
 * embeddable container. With the alpha 9 release, initialization is quite
 * simple, you need just 2 lines to initialize your JBoss Embeddable EJB3
 * Container Environment. Unfortunately, the one that is available for download
 * uses System.out.println() in a few places, so this simple utility hides that
 * output and also provides a simple lookup mechanism.
 */
public class JBossUtil {
    private static PrintStream originalOut;
    private static PrintStream originalErr;
    private static OutputStream testOutputStream;
    private static PrintStream testOuputPrintStream;
 
    static boolean initialized;
    static InitialContext ctx;
 
    private JBossUtil() {
        // I'm a utility class, do not instantiate me
    }
 
    /**
     * JBoss EJB3 Embeddable Container uses System.out. Redirect that output to
     * keep the console output clean.
     */
    private static void redirectStreams() {
        // configure the console to get rid of hard-coded System.out's in
        // the JBoss libraries
        testOutputStream = new ByteArrayOutputStream(2048);
        testOuputPrintStream = new PrintStream(testOutputStream);
 
        originalOut = System.out;
        originalErr = System.err;
 
        System.setOut(testOuputPrintStream);
        System.setErr(testOuputPrintStream);
    }
 
    /**
     * Restore the System.out and System.err streams to their original state.
     * Close the temporary stream created for the purpose of redirecting I/O
     * while the initializing is going on.
     */
    private static void restoreStreams() {
        System.setOut(originalOut);
        System.setErr(originalErr);
        testOuputPrintStream.close();
        try {
            testOutputStream.close();
        } catch (IOException e) {
            Logger.getLogger(JBossUtil.class.getName()).info(
                    "Unable to close testoutstream");
        }
    }
 
    /**
     * This method starts and initializes the embeddable container. We do not
     * offer a method to properly clean up the container since this is really
     * meant for testing only.
     * 
     * This method may freely be called as often as you'd like since it lazily
     * initializes the container only once.
     */
    public static void startDeployer() {
        if (!initialized) {
            redirectStreams();
 
            EJB3StandaloneBootstrap.boot(null);
            EJB3StandaloneBootstrap.scanClasspath();
 
            initialized = true;
 
            restoreStreams();
        }
    }
 
    /**
     * This is for symmetry. Given how we are using this class, there's little
     * need to actually shutdown the container since we run a quick application
     * and then stop the JVM.
     */
    public static void shutdownDeployer() {
        EJB3StandaloneBootstrap.shutdown();
    }
 
    private static InitialContext getContext() {
        /**
         * We only keep one context around, so lazily initialize it
         */
        if (ctx == null) {
            try {
                ctx = new InitialContext();
            } catch (NamingException e) {
                throw new RuntimeException("Unable to get initial context", e);
            }
        }
 
        return ctx;
    }
 
    /**
     * The lookup method on InitialContext returns Object. This simple wrapper
     * asks first for the expected type and the for the name to find. It gets
     * the name out of JNDI and performs a simple type-check. It then casts to
     * the type provided as the first parameter.
     * 
     * This isn't strictly correct since the cast uses the expression (T), where
     * T is the generic parameter and the type is erased at run-time. However,
     * since we first perform a type check, we know this cast is safe. The -at-
     * SuppressWarnings lets the Java Compiler know that we think we know what
     * we are doing.
     * 
     * @param <T>
     *            Type type provided as the first parameter
     * @param clazz
     *            The type to cast to upon return
     * @param name
     *            The name to find in Jndi, e.g. XxxDao/local or, XxxDao/Remote
     * @return Something out of Jndi cast to the type provided as the first
     *         parameter.
     */
    @SuppressWarnings("unchecked")
    public static <T> T lookup(Class<T> clazz, String name) {
        final InitialContext ctx = getContext();
        /**
         * Perform the lookup, verify that it is type-compatible with clazz and
         * cast the return type (using the erased type because that's all we
         * have) so the client does not need to perform the cast.
         */
        try {
            final Object object = ctx.lookup(name);
            if (clazz.isAssignableFrom(object.getClass())) {
                return (T) object;
            } else {
                throw new RuntimeException(String.format(
                        "Class found: %s cannot be assigned to type: %s",
                        object.getClass(), clazz));
            }
 
        } catch (NamingException e) {
            throw new RuntimeException(String.format(
                    "Unable to find ejb for %s", clazz.getName()), e);
        }
    }
}

A unit test or main program can use the following code to initialize the JBoss EJB 3 Container:
    JBossUtil.startDeployer();


Smoke Test Suite

Here is a quick test suite to verify that things basically work. To create this test:
  1. Expand your project (Ejb3Tutorial2)
  2. Select the test source folder
  3. Right-click and select New:Class
  4. Enter dao.impl for the Package
  5. Enter CompanyDaoImplTest for the Class name
  6. Click Finish
  7. Enter the following class
  8. Once the class is saved and compiles, execute it (Right-click in the file, select Run As::JUnit Test

CompanyDaoImplTest.java
package dao.impl;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
 
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
 
import util.JBossUtil;
import dao.CompanyDao;
import entity.Address;
import entity.Company;
import entity.Person;
 
public class CompanyDaoImplTest {
    private CompanyDao dao;
 
    @BeforeClass
    public static void setupJboss() throws Exception {
        JBossUtil.startDeployer();
    }
 
    @Before
    public void getCompanyDao() throws Exception {
        dao = JBossUtil.lookup(CompanyDao.class, "CompanyDao/local");
    }
 
    private Company createCompanyImpl() {
        final Address a = new Address("5080 Spectrum Drive", "Suite 700 West",
                "Addison", "TX", "75001");
        final Company c = new Company("Valtech", a);
        dao.createCompany(c);
        return c;
    }
 
    @Test
    public void createCompany() {
        final Company c = createCompanyImpl();
        assertEquals("Valtech", c.getName());
        assertNotNull(c.getId());
    }
 
    @Test
    public void hirePerson() {
        final Address a = new Address("5080 Spectrum Drive", "Suite 700 West",
                "Addison", "TX", "75001");
        final Person p = new Person("Brett", 'L', "Schuchert", a);
        final Company c = createCompanyImpl();
        c.hire(p);
        dao.update(c);
        final Company found = dao.find(c.getId());
        assertEquals(1, found.getEmployees().size());
    }
}


Optional: Data Source Configuration

The persistence.xml file mentions the possibility of using your own data source rather than the default data source to hit a different database.

When you use the embedded container, it looks for a file called embedded-jboss-beans.xml for its datasources (and several other things). In the one that ships with the ALPHA 9 release, you'll see the following two entries near the bottom:
   <bean name="DefaultDSBootstrap"
         class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
      <property name="driverClass">org.hsqldb.jdbcDriver</property>
      <property name="connectionURL">jdbc:hsqldb:.</property>
      <property name="userName">sa</property>
      <property name="jndiName">java:/DefaultDS</property>
      <property name="minSize">0</property>
      <property name="maxSize">10</property>
      <property name="blockingTimeout">1000</property>
      <property name="idleTimeout">100000</property>
      <property name="transactionManager">
          <inject bean="TransactionManager"/>
      </property>
      <property name="cachedConnectionManager">
          <inject bean="CachedConnectionManager"/>
      </property>
      <property name="initialContextProperties">
          <inject bean="InitialContextProperties"/>
      </property>
   </bean>
 
   <bean name="DefaultDS" class="java.lang.Object">
      <constructor factoryMethod="getDatasource">
         <factory bean="DefaultDSBootstrap"/>
      </constructor>
   </bean>

To create your own data source, you basically copy both of these and update the values accordingly. Here is one example that uses HSQL with a local server:
   <bean name="HypersonicLocalServerDSBootstrap" 
         class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
      <property name="driverClass">org.hsqldb.jdbcDriver</property>
      <property name="connectionURL">jdbc:hsqldb:hsql://localhost/xdb</property>
      <property name="userName">sa</property>
      <property name="jndiName">java:/HypersonicLocalServerDS</property>
      <property name="minSize">0</property>
      <property name="maxSize">10</property>
      <property name="blockingTimeout">1000</property>
      <property name="idleTimeout">100000</property>
      <property name="transactionManager">
          <inject bean="TransactionManager"/>
      </property>
      <property name="cachedConnectionManager">
          <inject bean="CachedConnectionManager"/>
      </property>
      <property name="initialContextProperties">
          <inject bean="InitialContextProperties"/>
      </property>
   </bean>
 
   <bean name="HypersonicLocalServerDS" class="java.lang.Object">
      <constructor factoryMethod="getDatasource">
         <factory bean="HypersonicLocalServerDSBootstrap"/>
      </constructor>
   </bean>

To use this, you need to do two things. Update persistence.xml and start a HSQL server.

Update persistence.xml
Replace the following line:
<jta-data-source>java:/DefaultDS</jta-data-source>

With this one:
<jta-data-source>java:/HypersonicLocalServerDS</jta-data-source>

To start an HSQL server, you can use the following steps:
  1. Under the place where you extracted HSQL (C:\libs\hsqldb if you used the same directories as the tutorial), create a new directory called "databases".
  2. Change to that directory
  3. Use the following command to start the HSQL server:
java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 xdb


Optional: QuantumDb Setup and Configuration

QuantumDb Configuration


QuantumDb is an Eclipse plugin that lets you view a database. This is a quick start guide.

Download and Install

  1. Download GEF (a required package) from here
  2. Download QuantumDb from here
  3. Open the GEF zip file and extract just the plugins and features directories directly into you eclipse installation (c:\eclipse)
  4. Open the QuantumDb zip file and extract just the plugins and features directories directly into your eclipse installation (c:\eclipse)
  5. Restart Eclipse


Start your Database

This example assumes hypersonic is running with the following startup script:
org.hsqldb.Server -database.0 file:mydb -dbname.0 xdb

In our examples, we created a folder called database under the installation directory of hypersonic, so the full folder name is:
C:\libs\hsqldb\database

Assuming java is in your classpath, the following command will start hypersonic:
java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 xdb


JPA in JSE Settings

persistence.xml
<persistence>
    <persistence-unit name="examplePersistenceUnit" 
                      transaction-type="RESOURCE_LOCAL">
        <properties>
            <property name="hibernate.show_sql" value="false" />
            <property name="hibernate.format_sql" value="false" />
 
            <property name="hibernate.connection.driver_class" 
                      value="org.hsqldb.jdbcDriver" />
            <property name="hibernate.connection.url" 
                      value="jdbc:hsqldb:hsql://localhost/xdb" />
            <property name="hibernate.connection.username" value="sa" />
 
            <property name="hibernate.dialect" 
                      value="org.hibernate.dialect.HSQLDialect" />
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

JPA in JEE Settings

This assumes you are using the JBoss Embedded container.

<?xml version="1.0" encoding="UTF-8"?>
<persistence>
   <persistence-unit name="custdb">
      <jta-data-source>java:/HypersonicLocalServerDS</jta-data-source>
      <properties>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>

And the additions to embedded-jboss-bean.xml:
   <bean name="HypersonicLocalServerDSBootstrap" 
         class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
      <property name="driverClass">org.hsqldb.jdbcDriver</property>
      <property name="connectionURL">jdbc:hsqldb:hsql://localhost/xdb</property>
      <property name="userName">sa</property>
      <property name="jndiName">java:/HypersonicLocalServerDS</property>
      <property name="minSize">0</property>
      <property name="maxSize">10</property>
      <property name="blockingTimeout">1000</property>
      <property name="idleTimeout">100000</property>
      <property name="transactionManager">
          <inject bean="TransactionManager"/>
      </property>
      <property name="cachedConnectionManager">
          <inject bean="CachedConnectionManager"/>
      </property>
      <property name="initialContextProperties">
          <inject bean="InitialContextProperties"/>
      </property>
   </bean>
 
   <bean name="HypersonicLocalServerDS" class="java.lang.Object">
      <constructor factoryMethod="getDatasource">
         <factory bean="HypersonicLocalServerDSBootstrap"/>
      </constructor>
   </bean>

Using the Perspective

Now that everything is setup, you'll need to open the perspective and form a connection to the database.

  1. Click the Open Perspective button and select Other
  2. Select Quantum DB and click OK
  3. Right-click in Database Bookmarks pane and select New Bookmark...
  4. Click on the Add Driver button
  5. Click on Add External Jar...
  6. Find hsqldb.jar (c:\libs\hsqldb\lib\hsqldb.jar) and click Open
  7. Click on Browse...
  8. Select org.hsqldb.jdbcDriver and click ok
  9. Click on Finish
  10. Select the driver you just added (it is probably the first in the list but look in the JDBC Driver Name column for org.hsqldb.jdbcDriver
  11. Click Next
  12. In the Userid field, enter sa
  13. Leave the Password field blank
  14. Leave Prompt for Password unselected
  15. In the JDBC URL filed, enter jdbc:hsqldb:hsql://localhost/xdb
  16. Name your bookmark HypersonicLocalServer and click Finish
  17. Double-click on you new HypersonicLocalServer Bookmark
  18. Expand PUBLIC
  19. Expand Tables

At this point you can experiment with the plugin.

Exercises

Fetching and Lazy Initialization

The Company entity has one attribute where we've specified the fetch type. Review the employees attribute in Company.java. Remove the fetch = FetchType.EAGER and execute the tests.

There's another way to make this "work" by manually reading the contents of the collection. Experiment with that and see if you can get it to work using this technique. What is the impact of @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) on a lazy fetch? (Hint: you might have some issues with this combination.)

Review
  • Which approach do you prefer?
  • What are the advantages/disadvantages?
  • Give one example where you'd use each technique.
  • Suggest a way to rewrite this or the test or both to remove the need to perform eager fetching.

Hint: You can compare the SQL from each approach by adding the following line to the persistence.xml:

         <property name="hibernate.show_sql" value="true" />

Fetch and Lazy Initialization Revisited

Add a unit test where you:
  1. Create a person
  2. Create a company
  3. Hire the person
  4. Retrieve the person (you can add a new DAO or simply add a method to the Company Dao)
  5. Verify that they have one job and that it is for the expected company.
Review
  • Did this test work? If not, why? If so, what can you say about @OneToMany versus @ManyToOne?
  • If it did not work, fix it.

Add Tests

Review the driver from the first JPA tutorial. Use that as example source from which you can derive tests.

In addition, add the following tests:
Attempt to Hire Person Already Hired
Write a test that creates a Person and 2 companies. Hire the person at the first company. Attempt to hire the person for the second company.

The results should be one of two things (you choose):
  • The test expects some kind of exception like "Person Has a Job" to be thrown.
  • You change the relationship between Person and Company to be bi-direction and many to many so that a Person can work for multiple companies.

Hire Person with Same Name/Address
Create two people and hire them both. Make sure this works.

Question, do you think it should work? If not, then update the equals and hashCode method and make this test be one that only works if the attempt fails.

Test Isolation

Use the installed QuantumDB Perspective to discover if our tests are leaving around objects after they have completed. Assuming they have, write code to have each test clean up after itself.