Introduction
I wasn't having much luck finding a good simple example application that uses JPA, EJB3, and Maven2, so I figured I'd create a simple skeleton application that hopefully others will find useful. This blog entry http://famvdploeg.com/blog/?p=41 along with the Better Builds With Maven pdf (just google it) got me started. (I'm still new to Maven so I'm not sure I'm doing everything correct, so those with more experience please let me know if there are modifications I should make.)
Setup and Running The Demo On JBoss
The final ear that is generated should run in most application servers. You might have to add a speicfic xxx-web.xml file for your particular server in the web module and if your underlying ORM framework is not hibernate, you'll need to modify the persistence.xml in the ejb module. This tutorial will describe the setup using JBoss5, but it shouldn't take much to have it working in Glassfish, OC4J, etc

  1. Assumes jdk5 or greater is installed and your JAVA_HOME environment variable set.
  2. Download and extract Maven2
  3. Set an environment variable MAVEN_HOME that points to your installation directory. Also add MAVEN_HOME/bin to your path.
  4. Download JBoss5 (at the time of this writing when you click on the Jboss5 download link you'll have an option of downloading a jdk6 version or a standard version. If you are using jdk6 you can use the jdk6 version, otherwise use the standard release. Code for this example was compiled with java5.)
  5. Set an environment variable JBOSS_HOME to your installation directory.
  6. Unzip the learntechnology-maven-jee source code (From now on this location will be refered to as the "project-dir")
  7. In our project-dir/external-resources directory there is a jboss-log4j.xml file. Backup your JBOSS_HOME/server/default/conf/jboss-log4j.xml file and replace it with this one. (Or you can just use the new appender and category that you see in the provided file to your existing jboss-log4j.xml file.)
  8. Set up HSQLDB.
    JBoss comes with its own version of HSQLDB, but I'll explain setting up hsqldb as an external db and we'll start it outside of JBoss as well. (Obviously you can use any other database you want, but you'll want to modify the learntechnology-ds.xml file accordingly and you'll need to take a look at the mydb.script to view the simple table created and the few inserts.)

    • Download HSQLDB and extract to your directory of choice.
    • Put the mysql.script (included in this project's external-resources directory) in the hsqldb/data directory.
    • From the commmand line move to the hsqldb/data directory and run the following command to start the db:
      java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 learntechnology_db
  9. Copy the project-dir/external-resources/learntechnology-ds.xml file to JBOSS_HOME/server/default/deploy
  10. From the command line move to your project-dir and run "mvn clean install" (If you get some errors here make sure you installed maven and set you MAVEN_HOME and added MAVEN_HOME/bin to your environment path.)
  11. The above mvn command will have created several target directories and files in the submodules beneath your project-dir. Copy the project-dir/lt-ear/target/learntechnology-maven-jee=1.0.ear to JBOSS_HOME/server/default/deploy
  12. From the command line move to JBOSS_HOME/bin and run either the run.sh or run.bat file depending on your OS.
  13. Point your browser to http://localhost:8080/lt-web/ and click on the 'view users' link. You should see few users displayed in the resulting jsp.

NOTE ABOUT TESTING: mvn install will run the simple unit test. It also uses a log4j.properties file in test/resources and will output a log file ejb-jar-testing.log inside the lt-ejb module root. The test results will show up in lt-ear/target/surefire-reports. If you want to generate a nicer report, type mvn surefire-report:report from the command line. This will generate an html report called surefire-report.html under lt-ejb/target/site directory. (Running mvn site should generate that file as well, including a larger more complete report. See mvn docs for more info.)
The Project Structure
Here is how the project structure is laid out:
Basic:


Expanded:

The pom.xml files

project parent pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.learntechnology</groupId>
  <artifactId>learntechnology-maven-jee</artifactId> 
  <version>1.0</version>
  <packaging>pom</packaging>
  <name>Sample Maven2 JEE Project</name>
  <modules>
    <module>lt-ejb-jar</module>
    <module>lt-web</module>
    <module>lt-ear</module>
  </modules>
  <repositories>
    <repository>
      <id>maven2-repository.dev.java.net</id>
      <name>Java.net Repository for Maven</name>
      <url>http://download.java.net/maven/2/</url>
      <layout>default</layout>
    </repository> 
  </repositories>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin> 
    </plugins>
  </build> 
  <properties>
    <hibernate.show_sql>false</hibernate.show_sql>
    <logging-location>test.log</logging-location>
  </properties>
  <profiles>
    <profile>
      <id>debug</id>
      <properties>
        <hibernate.show_sql>true</hibernate.show_sql>
      </properties>
    </profile>
  </profiles>
  <dependencies>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.13</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.3</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.1</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>javax.ejb</groupId>
      <artifactId>ejb-api</artifactId>
      <version>3.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>persistence-api</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>5.8</version>
        <scope>test</scope>
        <classifier>jdk15</classifier>
      </dependency> 
  </dependencies>
</project>

ear pom.xml

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>net.learntechnology</groupId>
    <artifactId>learntechnology-maven-jee</artifactId> 
    <version>1.0</version>
  </parent>
  <groupId>net.learntechnology</groupId>
  <artifactId>lt-ear</artifactId>
  <version>1.0</version>
  <packaging>ear</packaging>   
  <name>learntechnology-maven-jee :: lt-ear</name>
  <build>
    <finalName>learntechnology-maven-jee-${pom.version}</finalName>
    <plugins>
      <plugin>
        <artifactId>maven-ear-plugin</artifactId>
        <configuration>
          <generateApplicationXml>true</generateApplicationXml>
          <modules>
            <jarModule>
              <groupId>commons-collections</groupId>
              <artifactId>commons-collections</artifactId>
              <includeInApplicationXml>true</includeInApplicationXml>
            </jarModule>
            <jarModule>
              <groupId>commons-logging</groupId>
              <artifactId>commons-logging</artifactId>
              <includeInApplicationXml>true</includeInApplicationXml>
            </jarModule>
            <jarModule>
              <groupId>commons-lang</groupId>
              <artifactId>commons-lang</artifactId>
              <includeInApplicationXml>true</includeInApplicationXml>
            </jarModule>
            <ejbModule>
              <groupId>net.learntechnology</groupId>
              <artifactId>lt-ejb-jar</artifactId>
            </ejbModule>
            <webModule>
              <groupId>net.learntechnology</groupId>
              <artifactId>lt-web</artifactId>
              <contextRoot>/lt-web</contextRoot>
            </webModule>
          </modules> 
        </configuration>
      </plugin> 
    </plugins>
  </build> 
  <pluginRepositories>
    <pluginRepository>
      <id>codehaus snapshot repository</id>
      <url>http://snapshots.repository.codehaus.org/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
    </pluginRepository>
  </pluginRepositories>
  <dependencies>
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.3</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>net.learntechnology</groupId>
      <artifactId>lt-ejb-jar</artifactId>
      <type>ejb</type>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>net.learntechnology</groupId>
      <artifactId>lt-web</artifactId>
      <type>war</type>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

lt-ejb-jar pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>net.learntechnology</groupId>
    <artifactId>learntechnology-maven-jee</artifactId>
    <version>1.0</version>
  </parent>
  <groupId>net.learntechnology</groupId>
  <artifactId>lt-ejb-jar</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>ejb</packaging>
  <name>net.learntechnology :: lt-ejb-jar</name>
  <build>
    <testResources>
      <testResource>
        <directory>src/test/resources</directory>
        <filtering>true</filtering>
      </testResource>
    </testResources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-ejb-plugin</artifactId>
        <configuration>
          <ejbVersion>3.0</ejbVersion>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.4.2</version>
        <configuration>
          <suiteXmlFiles>
            <suiteXmlFile>src/test/config/testng.xml</suiteXmlFile>
          </suiteXmlFiles>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-report-plugin</artifactId>
        <version>2.4.2</version>
        <configuration>
          <suiteXmlFiles>
            <suiteXmlFile>src/test/config/testng.xml</suiteXmlFile>
          </suiteXmlFiles>
        </configuration>
      </plugin>
    </plugins>
  </reporting>
  <dependencies>
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.3</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>3.2.0.ga</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>hsqldb</groupId>
      <artifactId>hsqldb</artifactId>
      <version>1.8.0.7</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

lt-web pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>net.learntechnology</groupId>
    <artifactId>learntechnology-maven-jee</artifactId> 
    <version>1.0</version>
  </parent>
  <groupId>net.learntechnology</groupId>
  <artifactId>lt-web</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>net.learntechnology :: lt-web war</name>
  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>src/test/resources</directory>
      </testResource>
      <testResource>
        <directory>src/main/webapp</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </testResource>
    </testResources>
  </build>
  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>javax.ejb</groupId>
      <artifactId>ejb-api</artifactId>
      <version>3.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>persistence-api</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>net.learntechnology</groupId>
      <artifactId>lt-ejb-jar</artifactId>
      <type>ejb</type>
      <version>1.0-SNAPSHOT</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>


Relevant Files

Here are the more pertinent files used in the main application:

persistence.xml

<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
  version="1.0">
  <persistence-unit name="OurEntityManager">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:/db/learntechnology</jta-data-source>
    <class>net.learntechnology.persistence.User</class>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
    </properties>
  </persistence-unit>
</persistence>


Base Entity


public abstract class BaseEntity implements Serializable {
  public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
    
}


User Entity


@Entity
//if the table name matches your class name, you don't need the 'name' annotation
@Table(name="lt_user"
public class User extends BaseEntity {
     
    @Id 
    @GeneratedValue(strategy=GenerationType.AUTO
    //we don't need the Column annotation here bc the column name and this field name are the same
    private Long id;
    
    @Column(name="first_name",nullable=false,length=50)
    private String firstName;
    
    @Column(name="last_name",nullable=false,length=50)
    private String lastName;
    
    private Date birthday;


UserService

@Local
public interface UserService {
    public List<User> getUsers();
    public User saveUser(User user);
}


UserServiceBean

@Stateless
public class UserServiceBean implements UserService {
    private static Log log = LogFactory.getLog(UserServiceBean.class);
    
    //we don't really need to use unitName since we only have one EntityManager, 
  //but showing it anyway
    @PersistenceContext(unitName="OurEntityManager")
    private EntityManager em;
      
    public List<User> getUsers() {
        log.debug("getUsers");
    List<User> users = this.em.createQuery("select user from User user order by user.lastName").getResultList();
    return users;
    }
 
  //not using this method in this example app
  public User saveUser(User user) {
        log.debug("saveUser: "+user);
        this.em.persist(user);
        this.em.flush();
        return user;
    }
}


UserAdminServlet

public class UserAdminServlet extends HttpServlet {
    private static Log log = LogFactory.getLog(UserAdminServlet.class);
  
    @EJB
    private UserService userService;
     
    public void init() throws ServletException {
        super.init();
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse respthrows ServletException, IOException {
        doPost(req, resp);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse responsethrows ServletException, IOException {
      log.debug("retrieving users");
    List<User> users = userService.getUsers();
    request.setAttribute("users", users );
    getServletConfig().getServletContext().getRequestDispatcher("/users.jsp").forward(request,response);
    }
}
Relevant Testing Files

testng.xml

<?xml version="1.0"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MySuite" verbose="1">
  <test name="OurTests">
    <groups>
      <run>
        <include name="unit"/>
      </run>
    </groups> 
    <packages>
      <package name="net.learntechnology.persistence"/>
    </packages>
  </test>
</suite>


persistence.xml

<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" 
  version="1.0">
  <persistence-unit name="test-hsqldb">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jar-file>${project.build.directory}/classes</jar-file>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
      <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
      <property name="hibernate.connection.url" value="jdbc:hsqldb:hsql://localhost/learntechnology_db"/>
      <property name="hibernate.connection.username" value="sa"/>
      <property name="hibernate.connection.password" value=""/>
      <!-- <property name="hibernate.hbm2ddl.auto" value="create-drop"/> --> 
      <property name="hibernate.show_sql" value="${hibernate.show_sql}"/>
    </properties>
  </persistence-unit>
</persistence>


BaseTest.java

public class BaseTest {
    private static Logger log = Logger.getLogger(BaseTest.class);
    protected EntityManagerFactory emf;
    public EntityManager em;
    
    public static final String EM_TEST_HSQLDB = "test-hsqldb";
    
    protected void setUp(String emName)   {
        log.info("retrieving EntityManager: "+emName );
        emf = Persistence.createEntityManagerFactory(emName);
        em = emf.createEntityManager()
    }
   
    protected void tearDown()  {
        log.info("closing EntityManagerFactory")
        emf.close();
    }
}

UsersTest.java

@Test(groups={"unit"}) 
public class UsersTest extends BaseTest {

    private static Logger log = Logger.getLogger(UsersTest.class);
    private UserServiceBean userService = new UserServiceBean();
    
    public void getUsers() throws Exception {  
        log.debug("getting users");
        List<User> users = userService.getUsers()
        Assert.assertTrue(users.size() 0);
    }
     
    @BeforeClass    
    public  void runBeforeClass()  {
        log.info("runBeforeClass");
        setUp(BaseTest.EM_TEST_HSQLDB);
        userService.setEmem);
    }
    
    @AfterClass 
    public  void runAfterClass()  {
        log.info("runAfterClass");
        tearDown();
    }
  
}