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. 

Sunday, 29 July 2012

Tier and Layer

Layer is logical seperation of the system. Typical 3-layered architecture comprises Presentation (UI) layer, Application (business) logic layer and Data Access or persistence layer.

Tier is physical seperation of the system. If the 3 layers are deployed on the same machine, then we have 1 tier and 3 layers. If the 3 layers are deployed on three seperate machines, then we have 3 tiers and 3 layers.

Tuesday, 6 December 2011

Remove all .svn folders in Windows XP

It has been a tedious job to remove all the .svn folders in a project checked out from SVN repository. Thanks to http://www.iamatechie.com/remove-all-svn-folders-in-windows-xp-vista/, we now have an easy way.

Create a blank svnresistry.reg file (can be any name with .reg extension) on your desktop, edit it, and copy the following code snippet as its content.

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN]
@="Delete SVN Folders"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN\command]
@="cmd.exe /c \"TITLE Removing SVN Folders in %1 && COLOR 9A && FOR /r \"%1\" %%f IN (.svn) DO RD /s /q \"%%f\" \""

Double click the reg file to execute it.

Then you can right click on the project folder, you will see a 'Delete SVN Folders' menu.


Just clicking on this menu will remove all the 'svn' folders under this directory recursively.

Saturday, 3 December 2011

JPA one-to-many many-to-one relationship

many-to-one unidirectional mapping

@Entity
@Table(name="T_MANAGER")
public class Manager {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="MANA_ID")
    private int id;
    
    @Column(name="MANA_NAME")
    private String name;
}

@Entity
@Table(name="T_EMPLOYEE")
public class Employee {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="EMPL_ID")
    private int id;
    
    @Column(name="EMPL_NAME")
    private String name;

    @ManyToOne(cascade=CascadeType.PERSIST)
    private Manager manager;
}

Domain model


T_EMPLOYEE.MANAGER_MANA_ID is the foreign key referenced to T_MANAGER.MANA_ID

MANAGER_MANA_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘MANAGER’ + ‘_’+’MANA_ID’)

Test:

@PersistenceContext(unitName="unit")
private EntityManager em;

private void save(){
    Manager manager1 = new Manager("John");
    Employee employee1 = new Employee("Bob");
    Employee employee2 = new Employee("Tim");
    Employee employee3 = new Employee("Mary");
    employee1.setManager(manager1);
    employee2.setManager(manager1);
    employee3.setManager(manager1);
    
    Manager manager2 = new Manager("Robert");
    Employee employee4 = new Employee("Tom");
    Employee employee5 = new Employee("Alex");
    employee4.setManager(manager2);
    employee5.setManager(manager2);
    em.persist(employee1);
    em.persist(employee2);
    em.persist(employee3);
    em.persist(employee4);
    em.persist(employee5);
}
Result:

T_MANAGER


T_EMPLOYEE


Specify foreign key column name

@OneToOne(cascade={CascadeType.PERSIST, 
    CascadeType.MERGE, CascadeType.REMOVE}, 
    optional=false, fetch=FetchType.EAGER)
@JoinColumn(name="F_ADDR_ID", referencedColumnName="ADDR_ID")
private Address address;

Domain model


The name attribute of @JoinColumn (‘F_MANA_ID’) specifies the foreign key column name.

many-to-one bidirectional mapping

@Entity
@Table(name="T_MANAGER")
public class Manager {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="MANA_ID")
    private int id;
    
    @Column(name="MANA_NAME")
    private String name;
    
    @OneToMany(cascade={CascadeType.ALL}, mappedBy="manager")
    private Set<Employee> employees;
}

@Entity
@Table(name="T_EMPLOYEE")
public class Employee {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="EMPL_ID")
    private int id;
    
    @Column(name="EMPL_NAME")
    private String name;

    @ManyToOne(cascade=CascadeType.PERSIST)
    @JoinColumn(name="F_MANA_ID")
    private Manager manager;
}

Domain model

Same as unidirectional mapping


Test:

@PersistenceContext(unitName="unit")
private EntityManager em;

private void save(){
    Manager manager1 = new Manager("John");
    Employee employee1 = new Employee("Bob");
    Employee employee2 = new Employee("Tim");
    Employee employee3 = new Employee("Mary");
    Set<Employee> employees1 = new HashSet<Employee>();
    employee1.setManager(manager1);
    employee2.setManager(manager1);
    employee3.setManager(manager1);
    employees1.add(employee1);
    employees1.add(employee2);
    employees1.add(employee3);
    manager1.setEmployees(employees1);
    
    Manager manager2 = new Manager("Robert");
    Employee employee4 = new Employee("Tom");
    Employee employee5 = new Employee("Alex");
    Set<Employee> employees2 = new HashSet<Employee>();
    employees2.add(employee4);
    employees2.add(employee5);
    employee4.setManager(manager2);
    employee5.setManager(manager2);
    manager2.setEmployees(employees2);
    
    em.persist(manager1);
    em.persist(manager2);
}

Result:

Same as unidirectional mapping

T_MANAGER


T_EMPLOYEE


Retrieving the employees of a manager returns null.

Manager manager = em.find(Manager.class, 1);
System.out.println(manager.getEmployees());

This is because in a one-to-many relationship, the default fetch type at ONE side is LAZY.

@OneToMany(cascade={CascadeType.ALL}, mappedBy="manager", 
           fetch=FetchType.EAGER)
private Set<Employee> employees;

Change the fetch type to EAGER, and we are able to retrieve the employees of a manager.

Add a new employee to a manager

Manager manager = em.find(Manager.class, 1);
Employee emp = new Employee("Adam");
emp.setManager(manager);
manager.getEmployees().add(emp);
em.merge(manager);

Result:


Update an existing employee of a manager

Manager manager = em.find(Manager.class, 1);
Employee employee = manager.getEmployees().iterator().next();
employee.setName("Josh");
em.merge(manager);

Result:



Delete an existing employee of a manager

Manager manager = em.find(Manager.class, 1);
Set<Employee> employees= manager.getEmployees();
for (Employee emp: employees){
    if ("Bob".equalsIgnoreCase(emp.getName())){
        employees.remove(emp);
        break;
    }
}
em.merge(manager);

Result:


Bob is not deleted! We can see that simply removing the object from the set won’t actually remove the row from the database. So in this case, we need to invoke em.remove() to delete the row.

Manager manager = em.find(Manager.class, 1);
Set<Employee> employees= manager.getEmployees();
for (Employee emp: employees){
    if ("Bob".equalsIgnoreCase(emp.getName())){
        employees.remove(emp);
        em.remove(emp);
        break;
    }
}
em.merge(manager);

Result:

Saturday, 26 November 2011

JPA one-to-one relationship

Basic one-to-one unidirectional mapping

@Entity
@Table(name="T_CUSTOMER")
public class Customer {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="CUST_ID")
    private int id;
    
    @Column(name="CUST_NAME")
    private String name;
    
    @OneToOne
    private Address address;
    
}

@Entity
@Table(name="T_ADDRESS")
public class Address {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ADDR_ID")
    private int id;
    
    @Column(name="STREET")
    private String street;
    
    @Column(name="POSTCODE")
    private String postcode;
    
}

Domain model


T_CUSTOMER.ADDRESS_ADDR_ID is the foreign key referenced to T_ADDRESS.ADDR_ID

ADDRESS_ADDR_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘ADDRESS’ + ‘_’+’ADDR_ID’)

Since by default, no operations are cascaded, Address entity needs to be saved before Customer entity does.

@PersistenceContext(unitName="unit")
private EntityManager em;

public void save(){
    Address address = new Address("10 test st", "2912");
    Customer customer = new Customer("Sun", address);
    //Save address to database
    em.save(address);
    //Save customer to database
    em.save(customer);
    System.out.println("Customer saved");
}

The result:

T_CUSTOMER


T_ADDRESS


Add cascade attribute

@OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE, 
        CascadeType.REMOVE})
private Address address;

Now Address entity does not need to be saved before Customer entity does.

public void save(){
    Address address = new Address("10 test st", "2912");
    Customer customer = new Customer("Sun", address);
    //Save customer to database
    em.save(customer);
    System.out.println("Customer saved");
} 

Same for update and delete.

Specify whether association is optional, fetch type

@OneToOne(cascade={CascadeType.PERSIST, 
        CascadeType.MERGE, CascadeType.REMOVE}, 
        optional=false, fetch=FetchType.EAGER)
private Address address;

Default value of optional attribute is true
Default value of fetch attribute is EAGER

Now if trying to save a customer without setting his address, an exception will be thrown.

@PersistenceContext(unitName="unit")
private EntityManager em;

public void save(){
    Address address = new Address("10 test st", "2912");
    Customer customer = new Customer("Sun", address);
    //Save customer to database
    em.save(customer);
    System.out.println("Customer saved");
}

Specify foreign key column name

@OneToOne(cascade={CascadeType.PERSIST, 
        CascadeType.MERGE, CascadeType.REMOVE}, 
        optional=false, fetch=FetchType.EAGER)
@JoinColumn(name="F_ADDR_ID", referencedColumnName="ADDR_ID")
private Address address;

Domain model


The name attribute of @JoinColumn (‘F_ADDR_ID’) specifies the foreign key column name.

The referenceColumnName attribute of @JoinColumn (‘ADDR_ID’) specifies the primary key of the table referenced to. It can be omitted.

One-to-one bidirectional mapping

@Entity
@Table(name="T_CUSTOMER")
public class Customer {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="CUST_ID")
    private int id;
    
    @Column(name="CUST_NAME")
    private String name;
    
    @OneToOne(cascade={CascadeType.ALL}, optional=false)
    @JoinColumn(name="F_ADDR_ID")
    private Address address;
    
}

@Entity
@Table(name="T_ADDRESS")
public class Address {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ADDR_ID")
    private int id;
    
    @Column(name="STREET")
    private String street;
    
    @Column(name="POSTCODE")
    private String postcode;
    
    @OneToOne(mappedBy="address")
    private Customer customer;    
}

Domain model

Same as unidirectional mapping


@PersistenceContext(unitName="unit")
private EntityManager em;

public void save(){
    Address address = new Address("10 test st", "2912");
    Customer customer = new Customer("Sun", address);
    address.setCustomer(customer);
    //Save customer to database
    em.save(customer);
    System.out.println("Customer saved");
}