Hibernate version: 3.2.6
Database: HSQLDB 1.8 (for easy TDD - prod will be MySQL)
I have a situation where I need to store a row where one of the null columns is required for the natural ID. The best example I can think of is something like:
Code:
CREATE TABLE Messages (
messageId BIGINT NOT NULL PRIMARY KEY,
header VARCHAR NOT NULL,
body VARCHAR NOT NULL,
footer VARCHAR,
CONSTRAINT Messages_Unique UNIQUE (header, body, footer));
Basically, I should be able to differentiate between:
("foo", "bar", null)
("foo", "bar", "baz")
("foo", "lorem", null)
("foo", "bar", "loremipsum")
etc.
I tried mapping this with Hibernate using @NaturalId, but this doesn't work since Messages.footer is nullable. Does anyone have any suggestions for how I can update my object model to handle this?
Here's a simple test case demonstrating the problem:
Code:
import org.hibernate.annotations.NaturalId;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.ejb.HibernatePersistence;
import org.hsqldb.jdbcDriver;
import junit.framework.TestCase;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.util.Properties;
public class UniqueNullTest extends TestCase {
EntityManagerFactory emf;
protected void setUp() {
emf = createEntityManagerFactory();
}
protected void tearDown() {
emf.close();
}
public void testMessage() throws Exception {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.merge(new Message("hello", "world", null));
em.getTransaction().commit();
em.close();
em = emf.createEntityManager();
em.getTransaction().begin();
try {
em.merge(new Message("hello", "world", null));
fail("Should not be able to insert a duplicate");
} catch (EntityExistsException expected) {
}
em = emf.createEntityManager();
em.getTransaction().begin();
em.merge(new Message("hello", "world", "goodbye!"));
em.getTransaction().commit();
em.close();
em = emf.createEntityManager();
em.getTransaction().begin();
try {
em.merge(new Message("hello", "world", "goodbye!"));
fail("Should not be able to insert a duplicate");
} catch (EntityExistsException expected) {
}
}
@Entity
public static class Message {
@Id
@GeneratedValue
private Long id;
@NaturalId
@Column(nullable = false, updatable = false)
private String header;
@NaturalId
@Column(nullable = false, updatable = false)
private String body;
@NaturalId
@Column(nullable = true, updatable = false)
private String footer;
public Message() {
}
public Message(String header, String body, String footer) {
this.header = header;
this.body = body;
this.footer = footer;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getFooter() {
return footer;
}
public void setFooter(String footer) {
thi.footer = footer;
}
}
private static EntityManagerFactory createEntityManagerFactory() {
Ejb3Configuration config = new Ejb3Configuration()
.addAnnotatedClass(Message.class);
Properties prop = config.getProperties();
prop.setProperty(Environment.DRIVER, jdbcDriver.class.getName());
prop.setProperty(Environment.DIALECT, HSQLDialect.class.getName());
prop.put(HibernatePersistence.AUTODETECTION, "class");
prop.put(Environment.URL, "jdbc:hsqldb:mem:.;shutdown=true");
prop.put(Environment.USER, "sa");
prop.put(Environment.PASS, "");
prop.put(Environment.HBM2DDL_AUTO, "update");
return config.buildEntityManagerFactory();
}
}