Monday 24 September 2012

JPA Hibernate one-to-many bidirectional mapping on List

This article illustrates how to use JPA and Hibernate annotation to do one-to-many bidirectional mapping for ordered entities.

The One side is the Teacher entity

@Entity
public class Teacher {
 
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
 
    @Column
    private String name;
 
    @OneToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER)
    @JoinColumn(name="STU_ID")
    @IndexColumn(name="IDX")
    private List<Student> students = new ArrayList<Student>();
  
    public Teacher(String name) {
        super();
        this.name = name;
    }
 
    public Teacher() {
        super();
    }

    public void addStudent(Student s){
        students.add(s);
        s.setTeacher(this);
    }

    //Setters and getters
}

Note the Teacher class has a List of Students, not a Set of Students. The @IndexColumn annotation is used to specify which column in Student table to store the index.

The Many side is the Student entity

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
 
    @Column
    private String name;
 
    @ManyToOne
    @JoinColumn(name="STU_ID", 
            insertable=false, updatable=false)
    private Teacher teacher;

    public Student(String name) {
        Super();
        this.name = name;
    }
 
    public Student() {
        super();
    }

    //Setters and getters 
}

The name attribute of @JoinColumn for Teacher and Student should match one another (in this case, it is "STU_ID").

Write a JUnit test case


public void testTeacherDao(){
    TeacherDao dao = (TeacherDao)applicationContext.getBean("teacherDao");
    Teacher teacher = new Teacher("t1");
    teacher.addStudent(new Student("s1"));
    teacher.addStudent(new Student("s2"));
    dao.save(teacher);
}            


Output:

Teacher table





Student table






This example works in Hibernate 4.1.7.

Wednesday 19 September 2012

Spring applicationContext.xml cheat sheet for creating transaction manager for resource local persistence unit

Spring 3.0.5 + Hibernate 3.4.0

Approach 1

Specify Connection properties in persistence.xml
<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="hibernate-resourceLocal" transaction-type="RESOURCE_LOCAL">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
         <properties>
             <!-- Connection properties -->
             <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.ClientDriver" />
             <property name="hibernate.connection.url" value="jdbc:derby://localhost:1527/c:\derbydb\mydb" />
             <property name="hibernate.connection.username" value="APP" />
             <property name="hibernate.connection.password" value="APP" />

             <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
             <!-- create-drop, create -->
             <property name="hibernate.hbm2ddl.auto" value="update" />
             <property name="hibernate.show_sql" value="true" />
             <property name="hibernate.format_sql" value="true" />
             <property name="hibernate.connection.autocommit" value="true" />
         </properties>
    </persistence-unit>
</persistence>

applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
        xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/jee 
            http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx  
            http://www.springframework.org/schema/tx/spring-tx.xsd">

     <context:component-scan base-package="com.demo.dao" />

     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
         <property name="persistenceUnitName" value="hibernate-resourceLocal"/>
     </bean>

     <tx:annotation-driven />
 
     <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
         <property name="entityManagerFactory" ref="entityManagerFactory" />
     </bean> 
 
</beans>

Approach 2

Specify Connection properties in applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
        xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/jee 
            http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx  
            http://www.springframework.org/schema/tx/spring-tx.xsd">

     <context:component-scan base-package="com.demo.dao" />

     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
         <property name="dataSource" ref="dataSource"/>
         <property name="persistenceUnitName" value="default"/>
         <property name="jpaVendorAdapter">
             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                 <property name="showSql" value="true"/>
                 <property name="generateDdl" value="true"/>
                 <property name="databasePlatform" value="org.hibernate.dialect.DerbyDialect"/>
             </bean>
         </property>
     </bean>
 
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
         <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"/>
         <property name="url" value="jdbc:derby://localhost:1527/c:\derbydb\mydb"/>
         <property name="username" value="APP"/>
         <property name="password" value="APP"/>
     </bean>
 
     <tx:annotation-driven/>
 
     <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
         <property name="entityManagerFactory" ref="entityManagerFactory" />
     </bean>
</persistence-unit>



The persistence.xml is all but empty.
<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="default"/>
</persistence>

Glassfish 3.1 + Spring 3.0 + JNDI

I am writing a web application deployed in Glassfish application server 3.1. The service bean is exposed as a Spring managed bean instead of an EJB. The service bean is injected with an EntityManager, the data source of which is obtained from JNDI.

Wiring Spring with JNDI in Glassfish is more difficult than I thought.

I come across some common exceptions on which there are tons of discussions online. However, I fail to find helpful answers.

Therefore I feel necessary to write this blog to provide a simple solution.

persistence.xml
<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="jta" transaction-type="JTA">
         <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
         <jta-data-source>jdbc/myderby</jta-data-source>
         <properties>
             <property name="eclipselink.ddl-generation" value="create-tables" />
             <property name="eclipselink.ddl-generation.output-mode" value="database" />
             <property name="eclipselink.logging.parameters" value="true" />
             <property name="eclipselink.logging.level" value="FINE" />
         </properties>
     </persistence-unit>
</persistence>


applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
     xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/jee 
        http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx  
        http://www.springframework.org/schema/tx/spring-tx.xsd">

     <context:component-scan base-package="com.demo.controller" />
     <context:component-scan base-package="com.demo.dao" />

     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
         <property name="persistenceUnitName" value="jta"/>
     </bean>

     <tx:annotation-driven />
 
     <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
         <property name="entityManagerFactory" ref="entityManagerFactory" />
     </bean>
  
</beans>

UserDao.java
@Repository("userDao")
public class UserDaoImpl implements UserDao {

     @PersistenceContext 
     protected EntityManager entityManager;
 
     @Transactional
     public void save(User user) {
         entityManager.persist(user);
     }
}

Deployment is fine. When dao is accessed at runtime, an exception is thrown:


java.lang.IllegalStateException:
Exception Description: Cannot use an EntityTransaction while using JTA.
     at org.eclipse.persistence.internal.jpa.transaction.JTATransactionWrapper.getTransaction(JTATransactionWrapper.java:65)
     at org.eclipse.persistence.internal.jpa.EntityManagerImpl.getTransaction(EntityManagerImpl.java:1153)
     at org.springframework.orm.jpa.DefaultJpaDialect.beginTransaction(DefaultJpaDialect.java:70)
     at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:332)
I change the transaction-type from "JTA" to "RESOURCE_LOCAL" in persistence.xml.

The result is even worse. Deployment is complaining:

SEVERE: Exception while preparing the app : The persistence-context-ref-name [com.demo.dao.UserDaoImpl/entityManager] in module [spring-mvc-jpa-jndi] resolves to a persistence unit called [jta] which is of type RESOURCE_LOCAL. Only persistence units with transaction type JTA can be used as a container managed entity manager. Please verify your application.

I am not surprised to see this exception though. Since this is a Java EE environment, the transaction type should only be JTA. So I change the transaction type back to JTA.

I then try a lot of approaches in vain until I see this article:

http://java.dzone.com/tips/how-use-glassfish-managed-jpa

The core value of the article is this single line:

<tx:jta-transaction-manager/>


I replace my transactionManager bean definition with this line and it works perfectly.

After some investigation, it turns out this line creates the transactionMananger instance from this class: org.springframework.transaction.jta.JtaTransactionManager.

So it also works if I replace this line with:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>


Now it is easy to explain why the previous transactionManager causes an exception.  Clearly the org.springframework.orm.jpa.JpaTransactionManager is the transaction manager for RESOURCE_LOCAL persistence unit while org.springframework.transaction.jta.JtaTransactionManager is the transaction manager for JTA persistence unit. The latter probably delegates all the hassle stuff such as "beginning transaction","committing" and "closing transaction" to the Java EE container while the former needs to do all this chore.

With only one persistence unit defined, I can further strip the applicationContext.xml down to the following final version.

<context:component-scan base-package="com.demo.controller" />
<context:component-scan base-package="com.demo.dao" />

<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"/>

<tx:annotation-driven />
 
<tx:jta-transaction-manager/>
I am using Spring 3.0.5.

Monday 17 September 2012

A bug in EclipseLink

I recently found a bug in EclipseLink JPA provider.

I posted the question and answer at Stackoverflow.

Let me summarize the problem here.

First, retrieve an entity from the database, call entityManager.merge() on this entity, change the entity's Id, call entityManager.merge() on this entity again. An exception will be thrown.

Note the above operations are not executed in the same transaction context. Rather, each merge() is executed within its own transaction.

Here is the pseudocode to illustrate the idea.


User user = userManager.find(1); 
userManager.merge(user); 
System.out.println("User is managed? "+userManager.contains(user);
user.setId(2);
userManager.merge(user);


The console outputs:

User is managed? false

Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.1.3.v20110304-r9073): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [demo.model.User] is mapped to a primary key column in the database. Updates are not allowed.


userManager.contains() methods invokes the entityManager.contains() method, the fact that it returns false indicates the entity is not being managed. When switching the JPA provider from EclipseLink to Hibernate, it works fine. And according to the JPA specification, it shouldn't throw any exception in this scenario. I believe this bug has manifested itself in other forms. I have found several articles discussing this exception. If you want to reproduce this error, this blog presents one of the simplest approaches.

Thursday 13 September 2012

Persistence.xml cheat sheet for Hibernate, EclipseLink and OpenJPA

<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="jta" transaction-type="JTA">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <jta-data-source>jdbc/myderby</jta-data-source>
 </persistence-unit>

 <persistence-unit name="hibernate-resourceLocal" transaction-type="RESOURCE_LOCAL">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <properties>
   <!-- Connection properties -->
   <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.ClientDriver" />
   <property name="hibernate.connection.url" value="jdbc:derby://localhost:1527/c:\derbydb\mydb" />
   <property name="hibernate.connection.username" value="APP" />
   <property name="hibernate.connection.password" value="APP" />

   <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
   <!-- create-drop, create -->
   <property name="hibernate.hbm2ddl.auto" value="update" />
   <property name="hibernate.show_sql" value="true" />
   <property name="hibernate.format_sql" value="true" />
   <property name="hibernate.connection.autocommit" value="true" />
  </properties>
 </persistence-unit>

 <persistence-unit name="eclipseLink-resourceLocal" transaction-type="RESOURCE_LOCAL">
  <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  <class>com.test.entity.User</class>
  <properties>
   <!-- Connection properties -->
   <property name="eclipselink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver" />
   <property name="eclipselink.jdbc.url" value="jdbc:derby://localhost:1527/c:\derbydb\mydb" />
   <property name="eclipselink.jdbc.user" value="APP" />
   <property name="eclipselink.jdbc.password" value="APP" />

   <property name="eclipselink.target-database" value="Derby" />
   <!-- drop-and-create-tables -->
   <property name="eclipselink.ddl-generation" value="create-tables" />
   <property name="eclipselink.ddl-generation.output-mode"
    value="database" />
   <property name="eclipselink.logging.parameters" value="true" />
   <property name="eclipselink.logging.level" value="FINE" />
  </properties>
 </persistence-unit>

 <persistence-unit name="openjpa-resourceLocal" transaction-type="RESOURCE_LOCAL">
  <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
  <class>com.test.entity.User</class>
  <properties>
   <!-- Connection properties -->
   <property name="openjpa.ConnectionDriverName" value="org.apache.derby.jdbc.ClientDriver" />
   <property name="openjpa.ConnectionURL" value="jdbc:derby://localhost:1527/c:\derbydb\mydb" />
   <property name="openjpa.ConnectionUserName" value="APP" />
   <property name="openjpa.ConnectionPassword" value="APP" />

   <property name="openjpa.RuntimeUnenhancedClasses" value="supported" />
   <property name="openjpa.jdbc.DBDictionary" value="derby" />
   <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
   <property name="openjpa.Log" value="DefaultLevel=WARN, Runtime=INFO, Tool=INFO, SQL=TRACE" />
  </properties>
 </persistence-unit>
</persistence>

Use Hibernate as JPA provider in Glassfish

Glassfish's default JPA provider is EclipseLink. For some reason, I need to replace EclipseLink with Hibernate as the JPA provider.

This article describes the steps I take, the difficulties I come across and how I overcome them.

My development environment:

Eclipse 3.6.2 (oepe-helios-all-in-one)  downloaded from here.
Glassfish 3.1.2
Apache Derby Server

I begin by creating a ordinary Java project in eclipse, named 'jpa'

Create the persistence.xml file under META-INF folder, which is under src.








Since I am not quite sure whether I have configured the JNDI data source correctly (which is out of the scope of this article), I still use EclipseLink as the JPA provider for now. So here is the contents of persistence.xml.
















Then I create a simple entity bean (User) and a simple stateless session bean (UserManagerBean).

The final project structure should look like this:














I want to test the persistence configuration so eagerly that I immediately create an Enterprise Application Project called jpa-ear, add the jpa project into it, and try to deploy the ear into Glassfish server.

I get the following error unexpectedly. I would expect the console to tell me that the UserManagerBean was successfully deployed and ready to be looked up by JNDI, as such.

I remember having deployed a stateless session bean into JBoss successfully. At this stage, I am not sure whether there is something wrong with my data source configuration or the Glassfish requires a web project to be included in the EAR.



















Then I create a web project, called jpa-web,  and add it to the jpa-ear project. Interestingly, the EAR is deployed successfully even I haven't linked the web project to the ejb project.

The console says UserManagerBean has a JNDI name.






I write a servlet class to make sure the User can be persisted into the database. Now it is time I moved on to the central part of this task ----- replacing the EclipseLink with Hibernete.

I update the persistence.xml to the following:
















As soon as the file is saved, auto deployment kicks off, and an error message pops up.

















Fair enough. That is because Glassfish can't find the hibernate libraries.

I could copy the hibernate libraries into Glassfish's lib folder but I don't want to do that. Because I just want the hibernate persistence provider to be confined in this application scope and don't like the other applications to be affected at all.

So I download the latest hibernate release and add the necessary jar files to jpa project's classpath, only disappointed to see the error is still there.

I copy all the jar files to the lib folder in jpa-web project.  Buy it's still not working.

I then realize that it is the EAR that gets deployed to the server, not the WAR. So if any project needs these jar files, it should be the EAR project rather than the WEB project!

However, there is no lib folder under jpa-ear project. I right click the project, and select Deployment Assembly. There seem to have something I can try.



























I go Add -> Archives from File System -> Add, select all the necessary jar files





























This time it is complaining about another missing class.

















Obviously, the server has found the org.hibernate.ejb.HibernatePersistence class it was complaining about before. Now the org.hibernate.proxy.EntityNotFoundDelegate is missing.

With the help of jarfinder, I come to know that the hibernate-core jar file contains the EntityNotFoundDelegate class. But haven't I just added this class to the EAR project?

Checking out the Deployment Assembly window again, I notice something weird --- The hibernate-core jar file, along with two other jar files, does not have lib in its deploy path.

















Under jpa-web/EAR Libraries, those three jar files without lib in their path are not there!



















I don't know why those 3 jar files are not included in the EAR libraries. Is it a bug of Eclipse?

It looks like I need to change some configuration file to add lib to those 3 jar files' path. Luckily it doesn't take long to find the configuration file. It is named org.eclipse.wst.common.component under jpa-ear/.settings.

For hibernate-commons-annotations.jarjavassist.jarhibernate-core.jar, add lib in their deploy-path attribute. Here is the screenshot for hibernate-core.jar





Refresh the jpa-ear project, redeploy the ear to Glassfish. And it works fine.


Tuesday 11 September 2012

Change Glassfish default server port number once domain has been created

Today I encounter a problem when trying to start the Glassfish server in the Eclipse.

Here is the error:



















It turns out the port 8080 is occupied on my machine while Glassfish's default server port number happens to be 8080 as well.

I used command netstat -o to find out which process is using port 8080.
















The result doesn't make much sense to me. What does PID being 0 mean? I have no idea. Since this is a company computer, someone may have installed some weird software before. Killing that process and freeing port 8080 doesn't appear to be the best solution. So the question now comes to how to change the Glassfish's default server port number to something else.

When I first try to install Glassfish 3.1, I download an exe version. The exe installer does allow me to specify my own server port number. However, Eclipse doesn't like it. Eclipse only accepts the zip version.

In the process of creating a new Glassfish server instance in Eclipse, Eclipse doesn't provide much setting to configure.  Other than setting the path for the domain, everything else seems to be set as the default value.

I open the config folder under the domain just created. Suppose your root folder of Glassfish is c:\glassfish3, then the config folder is likely to be at c:\glassfish3\glassfish\domains\domain1.

I open the file named domain.xml because it looks likely to have the domain information, including the port number.

I search 8080, and there is one match.










I change 8080 to 9090 and restart the server. It still pops up the same error message.

I then remove the server instance from Eclipse and create a new server instance again.

Then it works like a charm.