Skip to content

Bug with unique constraint spanning many columns. #231

@sirf

Description

@sirf

I've encountered a strange issue when a unique constraints is spanning multiple columns.
After violating the constraint once, subsequent attempts to persist entities that does NOT violate the constraint are also failing. Test code to replicate the issue below. I was expecting the entire test to succeed, but the last commit is failing. I suspect a bug.

UniqueTest.java

package org.batoo.jpa.core.test.foobz;

import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
import java.util.Set;

import javax.persistence.Query;
import javax.sql.DataSource;

import junit.framework.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import org.batoo.jpa.core.test.BaseCoreTest;

/**
 * @author sirf
 */
public class UniqueTest extends BaseCoreTest {

    @Test
    public void testUnique() throws Exception {
        DataSource dataSource = this.em().unwrap(DataSource.class);
        Connection c = dataSource.getConnection();
        execute(c, "CREATE SEQUENCE FooBz_pkFooBz_seq");
        execute(c, "CREATE TABLE FooBz (" +
          "pkFooBz BIGINT NOT NULL DEFAULT nextval('FooBz_pkFooBz_seq')," +
          "fkParent BIGINT NOT NULL DEFAULT currval('FooBz_pkFooBz_seq')," +
          "fbname TEXT NOT NULL, PRIMARY KEY (pkFooBz)," +
          "UNIQUE (fkParent, fbname))");
        execute(c, "ALTER TABLE FooBz ADD FOREIGN KEY (fkParent) REFERENCES FooBz (pkFooBz)");

        execute(c, "INSERT INTO FooBz (fbname) VALUES ('000000')");
        execute(c, "INSERT INTO FooBz (fbname) VALUES ('000001')");
        c.close();
        c = null;
        dataSource = null;
        this.close();

        FooBz root0 = this.getFooBz("000000");

        FooBz sub = new FooBz(root0, "sub1");
        this.begin();
        this.persist(sub);
        this.commit();
        Assert.assertNotNull(sub.getId());

        sub = new FooBz(root0, "sub2");
        this.begin();
        this.persist(sub);
        this.commit();
        Assert.assertNotNull(sub.getId());
        Assert.assertEquals(root0, sub.getParent());

        sub = new FooBz(root0, "sub3");
        this.begin();
        this.persist(sub);
        this.commit();
        Assert.assertNotNull(sub.getId());
        Assert.assertEquals(root0.getId(), sub.getParent().getId());


        FooBz root1 = this.getFooBz("000001");

        sub = new FooBz(root1, "sub1");
        this.begin();
        this.persist(sub);
        this.commit();
        Assert.assertNotNull(sub.getId());

        try {
            sub = new FooBz(root1, "sub1");
            this.begin();
            this.persist(sub);
            this.commit();
            Assert.fail("Should not happen, because Unique Constraint");
        } catch (Exception ignore) {
            // expected
            this.rollback();
        }

        sub = new FooBz(root1, "sub3");
        this.begin();
        this.persist(sub);
        this.commit(); // fail here. don't know why
        Assert.assertNotNull(sub.getId());
        Assert.assertEquals("sub3", sub.getFbname());
        this.close();
    }

    private static void execute(Connection c, String query) throws SQLException {
        Statement s = c.createStatement();
        s.execute(query);
        s.close();
    }

    private FooBz getFooBz(String fbname) throws SQLException {
        this.begin();
        Query q = this.cq("SELECT i FROM FooBz i WHERE i.parent = i.id AND i.fbname = :fbname");
        q.setParameter("fbname", fbname);
        FooBz result = (FooBz) q.getSingleResult();
        this.commit();
        return result;
    }
}

FooBz.java

package org.batoo.jpa.core.test.foobz;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Table(name = "FooBz",
       uniqueConstraints = @UniqueConstraint(columnNames = { "fkParent", "fbname" }))
@Entity
@SuppressWarnings("serial")
public class FooBz implements Serializable {

    @Column(name = "pkFooBz", nullable = false, unique = true, updatable = false)
    @Id
    @SequenceGenerator(allocationSize = 1, name = "FooBz_pkFooBz_seq",
                                           sequenceName = "Foo_pkFooBz_seq")
    @GeneratedValue(generator = "FooBz_pkFooBz_seq", strategy = GenerationType.SEQUENCE)
    private Long id = null;

    @JoinColumn(name = "fkParent", nullable = false)
    @ManyToOne
    private FooBz parent = null;

    @Column(name = "fbname", nullable = false)
    private String fbname = null;

    protected FooBz() {
    }

    public FooBz(FooBz parent, String fbname) {
        this.parent = parent;
        this.fbname = fbname;
    }

    public Long getId() {
        return id;
    }

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

    public FooBz getParent() {
        return parent;
    }

    public void setParent(FooBz parent) {
        this.parent = parent;
    }

    public String getFbname() {
        return fbname;
    }

    public void setFbname(String fbname) {
        this.fbname = fbname;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (obj instanceof FooBz) {
            final FooBz rhs = (FooBz) obj;
            return getFbname().equals(rhs.getFbname()) && getParent().equals(rhs.getParent());
        }
        return false;
    }

    @Override
    public int hashCode() {
        return getFbname().hashCode();
    }
}

persistence.xml

<persistence version="2.0"
             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_2_0.xsd">

    <persistence-unit name="default">
        <provider>org.batoo.jpa.core.BatooPersistenceProvider</provider>

        <class>org.batoo.jpa.core.test.foobz.FooBz</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <property name="org.batoo.jpa.ddl" value="NONE" />
        </properties>
    </persistence-unit>
</persistence>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions