Dmitry Bedrin commented on Bug HHH-3441

I'm experiencing the same issue with Hibernate 3.3.1.GA (Part of JBoss 5.1.0.GA):

Project.java
package com.tnf.db.ass.mtm;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "PROJECT")
public class Project {

    @Id
    @SequenceGenerator(name = "projectIdSeqGenerator", sequenceName = "SEQ_PROJECT")
    @GeneratedValue(generator = "projectIdSeqGenerator", strategy = GenerationType.SEQUENCE)
    @Column(name = "ID", nullable = false, length = 20, precision = 0)
    private Long id;

    @Column(name = "NAME", length = 255)
    private String name;

    @OneToMany(mappedBy="project", cascade = CascadeType.ALL)
    @org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    private Set<ProjectXPerson> persons;

    public void addPerson(Person person, String role) {

        ProjectXPerson projectXPerson = new ProjectXPerson();
        projectXPerson.setPerson(person);
        projectXPerson.setProject(this);
        projectXPerson.setRole(role);

        if (null == persons) persons = new HashSet<ProjectXPerson>();
        persons.add(projectXPerson);

        if (null == person.getProjects()) person.setProjects(new HashSet<ProjectXPerson>());
        person.getProjects().add(projectXPerson);

    }

    public void clearPersons() {
        if (null == persons) return;
        for (ProjectXPerson projectXPerson : persons) {
            if (null == projectXPerson.getPerson().getProjects()) continue;
            projectXPerson.getPerson().getProjects().remove(projectXPerson);
        }
        persons.clear();
    }
    
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<ProjectXPerson> getPersons() {
        return persons;
    }

    public void setPersons(Set<ProjectXPerson> persons) {
        this.persons = persons;
    }

    @Override
    public String toString() {
        return "Project{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
Person.java
package com.tnf.db.ass.mtm;

import javax.persistence.*;
import java.util.Set;

@Entity
@Table(name = "PERSON")
public class Person {

    @Id
    @SequenceGenerator(name = "personIdSeqGenerator", sequenceName = "SEQ_PERSON")
    @GeneratedValue(generator = "personIdSeqGenerator", strategy = GenerationType.SEQUENCE)
    @Column(name = "ID", nullable = false, length = 20, precision = 0)
    private Long id;

    @Column(name = "NAME", length = 255)
    private String name;

    @OneToMany(mappedBy="person")
    private Set<ProjectXPerson> projects;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<ProjectXPerson> getProjects() {
        return projects;
    }

    public void setProjects(Set<ProjectXPerson> projects) {
        this.projects = projects;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
ProjectXPerson.java
package com.tnf.db.ass.mtm;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name="PROJECT_X_PERSON")
@IdClass(ProjectXPerson.PK.class)
public class ProjectXPerson {

    @Column(name = "ROLE")
    private String role;

    @Id
    private Project project;

    @Id
    private Person person;
    
    public static class PK implements Serializable {

        @ManyToOne(cascade = CascadeType.ALL)
        @JoinColumn(name="PROJECT_ID")
        private Project project;

        @ManyToOne(cascade = CascadeType.ALL)
        @JoinColumn(name="PERSON_ID")
        private Person person;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            PK pk = (PK) o;

            if (person != null ? !person.equals(pk.person) : pk.person != null) return false;
            if (project != null ? !project.equals(pk.project) : pk.project != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = project != null ? project.hashCode() : 0;
            result = 31 * result + (person != null ? person.hashCode() : 0);
            return result;
        }
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public Project getProject() {
        return project;
    }

    public void setProject(Project project) {
        this.project = project;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ProjectXPerson that = (ProjectXPerson) o;

        if (person != null ? !person.equals(that.person) : that.person != null) return false;
        if (project != null ? !project.equals(that.project) : that.project != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = project != null ? project.hashCode() : 0;
        result = 31 * result + (person != null ? person.hashCode() : 0);
        return result;
    }

}

My test case:

MTMTest.java
package com.tnf.db.ass.mtm;

import com.tnf.db.persistence.util.PersistenceUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/beans-test.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class MTMTest {

    @PersistenceContext(unitName = PersistenceUtil.PERSISTENCE_UNIT_NAME)
    protected EntityManager em;
    
    @Test
    public void create() throws Exception {
        
        Person joe = new Person();
        joe.setName("Joe");
        //em.persist(joe);

        Person jane = new Person();
        jane.setName("Jane");
        //em.persist(jane);
        
        Project manhattan = new Project();
        manhattan.setName("Manhattan");
        em.persist(manhattan);

        manhattan.addPerson(joe, "Engineer");
        manhattan.addPerson(jane, "Engineer");

        em.flush();
        em.clear();

    }
    
}

This test case fails with following exception:

java.lang.NullPointerException
	at org.hibernate.type.AbstractType.getHashCode(AbstractType.java:136)
	at org.hibernate.type.AbstractType.getHashCode(AbstractType.java:144)
	at org.hibernate.type.EntityType.getHashCode(EntityType.java:312)
	at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:212)
	at org.hibernate.engine.EntityKey.generateHashCode(EntityKey.java:126)
	at org.hibernate.engine.EntityKey.<init>(EntityKey.java:70)
	at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:184)
	at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:144)
	at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
	at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
	at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
	at org.hibernate.impl.SessionImpl.firePersistOnFlush(SessionImpl.java:671)
	at org.hibernate.impl.SessionImpl.persistOnFlush(SessionImpl.java:663)
	at org.hibernate.engine.CascadingAction$9.cascade(CascadingAction.java:346)
	at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291)
	at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239)
	at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
	at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:319)
	at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:265)
	at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:242)
	at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
	at org.hibernate.engine.Cascade.cascade(Cascade.java:153)
	at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:154)
	at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145)
	at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:88)
	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:49)
	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
	at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:304)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
	at $Proxy63.flush(Unknown Source)
	at com.tnf.db.ass.mtm.MTMTest.create(MTMTest.java:43)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:71)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:202)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

If I remove comments from em.persist(joe); and em.persist(jane); lines it works as expected

This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators.
For more information on JIRA, see: http://www.atlassian.com/software/jira