Sunday, April 10, 2011

JPA and unmodifiable collections

Patrycja Węgrzynowicz (see Yon Labs / Yon Consulting) had very interesting talk on 33rd Degree Conference this year called "Patterns and Anti-Patterns in Hibernate". Inspired by this talk, I decided to verify JPA providers behavior regarding unmodifiable Collections.

I will use the same Employer - Employee - Benefit model as in my previous JPA posts (ex. JPA Demystified (episode 1) - @OneToMany and @ManyToOne mappings).

Let's take a look at the Employer entity:
@Entity
@Table(name = "EMPLOYERS")
public class Employer implements Serializable {
    ...
    private List<Employee> employees = new ArrayList<Employee>();
    ...

    public void addEmployee(Employee employee) {
        employee.setEmployer(this);
        employees.add(employee);
    }
    ...
    @OneToMany(mappedBy = "employer", cascade = CascadeType.PERSIST)
    public List<Employee> getEmployees() {
        return Collections.unmodifiableList(employees);
    }
    ...
    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }
    ...
}
As you see addEmployee and getEmployees method usage should protect the employees list from the modifying outside the Employer.

Let's use following test:
    @Test
    @Transactional(readOnly = false)
    public void test03() throws Exception {
        Employer employer = employerDAO.get(1L);
        assertEquals(3, employer.getEmployees().size());
        employerDAO.merge(employer);
    }
Nothing special, we fetch the Employer from database, doesn't change anything, and try to merge the state into database. Simple, isn't it? Well, when you try to use Hibernate as JPA provider you'll encounter:
java.lang.UnsupportedOperationException
 at java.util.Collections$UnmodifiableCollection.clear(Collections.java:1037)
 at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:501)
 at org.hibernate.type.CollectionType.replace(CollectionType.java:574)
 at org.hibernate.type.TypeFactory.replace(TypeFactory.java:505)
 at org.hibernate.event.def.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:392)
 at org.hibernate.event.def.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:200)
 at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:173)
 at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:81)
 at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:704)
 at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:688)
 at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:692)
 at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:235)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
 at $Proxy29.merge(Unknown Source)
 at [...].dao.jpa.DefaultEmployerDAO.merge(DefaultEmployerDAO.java:22)
 at [...].dao.EmployerDAOTest.test03(EmployerDAOTest.java:76) 
Good Lord, why it is trying to clear the collection!? Patrycja mentioned in her talk, that when you return the unmodifiable view of collection, Hibernate will treat the property as "dirty" (because both collections are different as objects) and try to persist the changes - which in this case means deleting all collection elements and persist same collection elements again!! ...

OK, maybe OpenJPA will behave better ...
java.lang.IllegalAccessError: class org.apache.openjpa.util.java$util$Collections$UnmodifiableRandomAccessList$0$proxy cannot access its superclass java.util.Collections$UnmodifiableRandomAccessList
 at java.lang.ClassLoader.defineClass1(Native Method)
 at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:466)
 at serp.bytecode.BCClassLoader.findClass(BCClassLoader.java:50)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
 at java.lang.Class.forName0(Native Method)
 at java.lang.Class.forName(Class.java:247)
 at org.apache.openjpa.util.GeneratedClasses.loadBCClass(GeneratedClasses.java:67)
 at org.apache.openjpa.util.ProxyManagerImpl.getFactoryProxyCollection(ProxyManagerImpl.java:363)
 at org.apache.openjpa.util.ProxyManagerImpl.newCollectionProxy(ProxyManagerImpl.java:189)
 at org.apache.openjpa.kernel.StateManagerImpl.newFieldProxy(StateManagerImpl.java:1824)
 at org.apache.openjpa.kernel.StateManagerImpl.newProxy(StateManagerImpl.java:1790)
 at org.apache.openjpa.jdbc.meta.strats.StoreCollectionFieldStrategy.load(StoreCollectionFieldStrategy.java:543)
 at org.apache.openjpa.jdbc.meta.FieldMapping.load(FieldMapping.java:934)
 at org.apache.openjpa.jdbc.kernel.JDBCStoreManager.load(JDBCStoreManager.java:691)
 at org.apache.openjpa.kernel.DelegatingStoreManager.load(DelegatingStoreManager.java:117)
 at org.apache.openjpa.kernel.ROPStoreManager.load(ROPStoreManager.java:78)
 at org.apache.openjpa.kernel.StateManagerImpl.loadFields(StateManagerImpl.java:3047)
 at org.apache.openjpa.kernel.StateManagerImpl.loadField(StateManagerImpl.java:3121)
 at org.apache.openjpa.kernel.StateManagerImpl.beforeAccessField(StateManagerImpl.java:1606)
 at org.apache.openjpa.kernel.StateManagerImpl.accessingField(StateManagerImpl.java:1591)
 at org.apache.openjpa.enhance.RedefinitionHelper.accessingField(RedefinitionHelper.java:59)
 at org.apache.openjpa.enhance.[...]$entities$domain$Employer$pcsubclass.getEmployees(Unknown Source)
 at [...].dao.EmployerDAOTest.test03(EmployerDAOTest.java:75)
Nope :(. OpenJPA fails much sooner - it is unable to fetch the Employees list!

Our last hope - EclipseLink ... Works :) it performs appropriate selects, to fetch the entity, then on merge verifies that there were no changes in it, and simply does nothing.

Of course all those 3 providers work perfectly when you simply return the collection, not the unmodifiable view.

Saturday, April 2, 2011

NSObject - description method

I believe that all Java Developers, using debugger from time to time, are appreciating toString method in Object class :) - probably the same feelings share all Objective-C Developers who know NSObject's description method.

In short, this method returns a string that represents the content of the receiving class, by default it is something like this:
<Card: 0x4b64c90>
Sounds mysterious for now, but when you override this method in your class (Card in this example), and make it look like this (face and value are properties of Card class):
- (NSString *) description {
    return [NSString stringWithFormat: @"face: %@, value: %d", self.face, self.value];
}
And then, in debugger, select Print / Description on the Card object:


You will see something like that:


The same method is used while you send the messages to the system log, using NSLog function:
NSLog(@"Card: %@", card);
Similar to the debugger Print / Description command, you'll see your object description in console when the NSLog will be reached in code.