OPENJPA-1235 Added support for named unique constraints

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@802864 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jeremy Bauer 2009-08-10 17:03:42 +00:00
parent 538f1fd084
commit 9af553bc8d
14 changed files with 541 additions and 3 deletions

View File

@ -92,6 +92,7 @@ public class TableJDBCSeq
private String _seqColumnName = "SEQUENCE_VALUE";
private String _pkColumnName = "ID";
private String[] _uniqueColumnNames;
private String _uniqueConstraintName;
private Column _seqColumn = null;
private Column _pkColumn = null;
@ -397,7 +398,10 @@ public class TableJDBCSeq
_seqColumn.setJavaType(JavaTypes.LONG);
if (_uniqueColumnNames != null) {
String uniqueName = dict.getValidUniqueName("UNQ", table);
String uniqueName = _uniqueConstraintName;
if (StringUtils.isEmpty(uniqueName)) {
uniqueName = dict.getValidUniqueName("UNQ", table);
}
Unique u = table.addUnique(uniqueName);
for (String columnName : _uniqueColumnNames) {
if (!table.containsColumn(columnName))
@ -805,6 +809,14 @@ public class TableJDBCSeq
return dict.getLong(rs, 1);
}
public void setUniqueConstraintName(String _uniqueConstraintName) {
this._uniqueConstraintName = _uniqueConstraintName;
}
public String getUniqueConstraintName() {
return _uniqueConstraintName;
}
/**
* AllocateSequenceRunnable is a runnable wrapper that will inserts the
* initial sequence value into the database.

View File

@ -62,6 +62,7 @@ public class SequenceMapping
private static final String PROP_PK_COL = "PrimaryKeyColumn";
private static final String PROP_PK_VALUE = "PrimaryKeyValue";
private static final String PROP_UNIQUE = "UniqueColumns";
private static final String PROP_UNIQUE_CONSTRAINT = "UniqueConstraintName";
private File _mapFile = null;
private String _table = null;
@ -69,6 +70,7 @@ public class SequenceMapping
private String _primaryKeyColumn = null;
private String _primaryKeyValue = null;
private String[] _uniqueColumns = null;
private String _uniqueConstraintName = null;
public SequenceMapping(String name, MappingRepository repos) {
super(name, repos);
@ -175,6 +177,12 @@ public class SequenceMapping
// Array of unique column names are passed to configuration
// as a single string "x|y|z". The configurable (TableJDBCSeq) must
// parse it back.
if (_uniqueConstraintName != null &&
_uniqueConstraintName.length() > 0) {
appendProperty(props, PROP_UNIQUE_CONSTRAINT,
addQuotes(_uniqueConstraintName));
}
if (_uniqueColumns != null && _uniqueColumns.length > 0)
appendProperty(props, PROP_UNIQUE,
StringUtils.join(_uniqueColumns,'|'));
@ -186,4 +194,14 @@ public class SequenceMapping
}
return name;
}
public void setUniqueConstraintName(String name) {
_uniqueConstraintName = name;
}
public String getUniqueConstraintName() {
return _uniqueConstraintName;
}
}

View File

@ -277,6 +277,7 @@ public class AnnotationPersistenceMappingParser
break; // nothing to do
case 1:
meta.setUniqueColumns(gen.uniqueConstraints()[0].columnNames());
meta.setUniqueConstraintName(gen.uniqueConstraints()[0].name());
break;
default:
log.warn(_loc.get("unique-many-on-seq-unsupported", el, name));
@ -558,6 +559,10 @@ public class AnnotationPersistenceMappingParser
column.setName(columnNames[i]);
uniqueConstraint.addColumn(column);
}
String name = anno.name();
if (!StringUtils.isEmpty(name)) {
uniqueConstraint.setName(name);
}
return uniqueConstraint;
}

View File

@ -601,6 +601,9 @@ public class AnnotationPersistenceMappingSerializer
if (columns.length > 1)
sb.insert(0, "{").append("}");
ab.add("columnNames", sb.toString());
if (StringUtils.isNotEmpty(unique.getName())) {
ab.add("name", unique.getName());
}
}
@Override

View File

@ -96,6 +96,7 @@ enum MappingTag {
MAP_KEY_TEMPORAL,
MAPPING_OVERRIDE,
MAPPING_OVERRIDES,
NAME,
NONPOLY,
ORDER_COL,
STRAT,

View File

@ -99,6 +99,7 @@ public class XMLPersistenceMappingParser
_elems.put("map-key-column", MAP_KEY_COL);
_elems.put("map-key-join-column", MAP_KEY_JOIN_COL);
_elems.put("map-key-temporal", MAP_KEY_TEMPORAL);
_elems.put("name", NAME);
_elems.put("order-column", ORDER_COLUMN);
_elems.put("primary-key-join-column", PK_JOIN_COL);
_elems.put("secondary-table", SECONDARY_TABLE);
@ -249,6 +250,9 @@ public class XMLPersistenceMappingParser
case UNIQUE:
ret = startUniqueConstraint(attrs);
break;
case NAME:
ret = true;
break;
case TEMPORAL:
case ENUMERATED:
case MAP_KEY_ENUMERATED:
@ -285,6 +289,19 @@ public class XMLPersistenceMappingParser
return (ret) ? tag : null;
}
private boolean endName() {
String name = this.currentText();
if (StringUtils.isNotEmpty(name)) {
Object current = currentElement();
if (current instanceof Unique) {
Unique unq = (Unique)current;
unq.setName(name);
}
}
return true;
}
@Override
protected void endClassMappingElement(String name)
throws SAXException {
@ -333,6 +350,9 @@ public class XMLPersistenceMappingParser
case TABLE_GEN:
endTableGenerator();
break;
case NAME:
endName();
break;
}
}
@ -1130,6 +1150,14 @@ public class XMLPersistenceMappingParser
private boolean startUniqueConstraint(Attributes attrs)
throws SAXException {
Unique unique = new Unique();
// TODO JRB: If the spec is corrected, get the unique constraint name
// via attribute
/*
String name = attrs.getValue("name");
if (StringUtils.isNotEmpty(name)) {
unique.setName(name);
}
*/
pushElement(unique);
return true;
}
@ -1161,6 +1189,9 @@ public class XMLPersistenceMappingParser
for (Column uniqueColumn : uniqueColumns)
columnNames[i++] = uniqueColumn.getName();
seq.setUniqueColumns(columnNames);
if (StringUtils.isNotEmpty(unique.getName())) {
seq.setUniqueConstraintName(unique.getName());
}
} else {
throw new InternalException();
}

View File

@ -566,6 +566,14 @@ public class XMLPersistenceMappingSerializer
}
private void serializeUniqueConstraint(Unique unique) throws SAXException {
if (StringUtils.isNotEmpty(unique.getName())) {
//TODO JRB: If the spec is modified to use a name attribute
// remove the element def and uncomment the attribute def
startElement("name");
addText(unique.getName());
endElement("name");
// addAttribute("name", unique.getName());
}
startElement("unique-constraint");
Column[] columns = unique.getColumns();
for (Column column:columns) {

View File

@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.jdbc.unique;
import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
* Data structures for testing unique constraint settings on ORM Annotations.
* @UniqueConstraint annotation is declared at class-level with
* @Table,
* @SecondaryTable annotations
* and at field-level with
* @JoinTable annotation.
* also with
* @Column(unique=true) on single column.
*
* The columns included in unique constraint must be non-nullable. This is
* recommended that the non-nullability of the column is explictly set by the
* user, though the implementation forces a column to non-nullable as a column
* is included in a unique constraint.
*
* The name of the constraint is generated by the implementation as JPA ORM
* specification has not allowed to specify a name for the constraint via the
* annotation or XML descriptor. Some databases allow two constraints having the
* same name but applied to different tables, while some other databases do not.
*
*
* @author Pinaki Poddar
*
*/
@Entity
@Table(name="N_UNIQUE_A",
uniqueConstraints={@UniqueConstraint(name="uca_f1_f2", columnNames={"f1","f2"}),
@UniqueConstraint(name="uca_f3_f4", columnNames={"f3","f4"})})
@SecondaryTable(name="N_UNIQUE_SECONDARY",
uniqueConstraints=@UniqueConstraint(name="uca_sf1", columnNames={"sf1"}))
public class NamedUniqueA {
@Id
private int aid;
// Same named field in UniqueB also is defined as unique
@Column(unique=true, nullable=false)
private int f1;
@Column(nullable=false)
private int f2;
@Column(nullable=false)
private int f3;
@Column(nullable=false)
private int f4;
private int f5;
private int f6;
@Column(table="N_UNIQUE_SECONDARY", nullable=false)
private short sf1;
@Column(table="N_UNIQUE_SECONDARY")
private short sf2;
@ManyToMany
@JoinTable(name="N_UNIQUE_JOINTABLE",
joinColumns={@JoinColumn(name="FK_A", nullable=false,
referencedColumnName="aid")},
inverseJoinColumns={@JoinColumn(name="FK_B", nullable=false,
referencedColumnName="bid")},
uniqueConstraints=@UniqueConstraint(name="uca_fka_fkb", columnNames={"FK_A","FK_B"}))
private Collection<NamedUniqueB> bs;
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.jdbc.unique;
import java.util.Set;
import javax.persistence.*;
@Entity
@Table(name="N_UNIQUE_B",
uniqueConstraints={@UniqueConstraint(name="ucb_f1_f2", columnNames={"f1","f2"})})
public class NamedUniqueB {
@Id
@GeneratedValue(strategy=GenerationType.TABLE, generator="namedTestGenerator")
@TableGenerator(name="namedTestGenerator", table="N_UNIQUE_GENERATOR",
pkColumnName="GEN1", valueColumnName="GEN2",
uniqueConstraints={@UniqueConstraint(name="ucb_gen1_gen2", columnNames={"GEN1","GEN2"})})
private int bid;
// Same named field in UniqueA also is defined as unique
@Column(unique=true, nullable=false)
private int f1;
@Column(nullable=false)
private int f2;
@CollectionTable(name="N_U_COLL_TBL", uniqueConstraints=
{@UniqueConstraint(name="ucb_f3", columnNames="f3")})
@ElementCollection
@Column(name="f3", nullable=false)
private Set<String> f3;
}

View File

@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.jdbc.unique;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
import org.apache.openjpa.persistence.jdbc.SQLSniffer;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
public class TestNamedUniqueConstraint extends SQLListenerTestCase {
@Override
public void setUp(Object... props) {
super.setUp(DROP_TABLES, NamedUniqueA.class, NamedUniqueB.class);
}
public void testMapping() {
// If the database does not support unique constraints, exit
if (!supportsUniqueConstraints())
return;
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.getTransaction().commit();
em.close();
// The above should trigger schema definition
List<String> sqls = super.sql;
assertSQLFragnments(sqls, "CREATE TABLE N_UNIQUE_A",
"uca_f1_f2 UNIQUE .*\\(f1, f2\\)",
"uca_f3_f4 UNIQUE .*\\(f3, f4\\).*");
assertSQLFragnments(sqls, "CREATE TABLE N_UNIQUE_B",
"ucb_f1_f2 UNIQUE .*\\(f1, f2\\).*");
assertSQLFragnments(sqls, "CREATE TABLE N_UNIQUE_SECONDARY",
"uca_sf1 UNIQUE .*\\(sf1\\)");
assertSQLFragnments(sqls, "CREATE TABLE N_UNIQUE_GENERATOR",
"ucb_gen1_gen2 UNIQUE .*\\(GEN1, GEN2\\)");
assertSQLFragnments(sqls, "CREATE TABLE N_UNIQUE_JOINTABLE",
"uca_fka_fkb UNIQUE .*\\(FK_A, FK_B\\)");
assertSQLFragnments(sqls, "CREATE TABLE N_U_COLL_TBL",
"ucb_f3 UNIQUE .*\\(f3\\).*");
}
private boolean supportsUniqueConstraints() {
OpenJPAEntityManagerFactorySPI emfs = (OpenJPAEntityManagerFactorySPI)emf;
JDBCConfiguration jdbccfg = (JDBCConfiguration)emfs.getConfiguration();
return jdbccfg.getDBDictionaryInstance().supportsUniqueConstraints;
}
void assertSQLFragnments(List<String> list, String... keys) {
if (SQLSniffer.matches(list, keys))
return;
fail("None of the following " + sql.size() + " SQL \r\n" +
toString(sql) + "\r\n contains all keys \r\n"
+ toString(Arrays.asList(keys)));
}
}

View File

@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.jdbc.unique;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
import org.apache.openjpa.persistence.jdbc.SQLSniffer;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
public class TestNamedUniqueConstraintWithXMLDescriptor extends SQLListenerTestCase {
@Override
public void setUp(Object... props) {
super.setUp(DROP_TABLES, NamedUniqueA.class, NamedUniqueB.class);
}
protected String getPersistenceUnitName() {
return "NamedUniqueConstraintTest";
}
public void testMapping() {
// If the database does not support unique constraints, exit
if (!supportsUniqueConstraints())
return;
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.getTransaction().commit();
em.close();
// The above should trigger schema definition
List<String> sqls = super.sql;
assertSQLFragnments(sqls, "CREATE TABLE NX_UNIQUE_A",
"ucxa_f1_f2 UNIQUE .*\\(f1x, f2x\\)",
"ucxa_f3_f4 UNIQUE .*\\(f3x, f4x\\).*");
assertSQLFragnments(sqls, "CREATE TABLE NX_UNIQUE_B",
"ucxb_f1_f2 UNIQUE .*\\(f1x, f2x\\).*");
assertSQLFragnments(sqls, "CREATE TABLE NX_UNIQUE_SECONDARY",
"ucxa_sf1 UNIQUE .*\\(sf1x\\)");
assertSQLFragnments(sqls, "CREATE TABLE NX_UNIQUE_GENERATOR",
"ucxb_gen1_gen2 UNIQUE .*\\(GEN1_XML, GEN2_XML\\)");
assertSQLFragnments(sqls, "CREATE TABLE NX_UNIQUE_JOINTABLE",
"ucxa_fka_fkb UNIQUE .*\\(FK_A_XML, FK_B_XML\\)");
assertSQLFragnments(sqls, "CREATE TABLE NX_U_COLL_TBL",
"ucxb_f3 UNIQUE .*\\(f3x\\).*");
}
private boolean supportsUniqueConstraints() {
OpenJPAEntityManagerFactorySPI emfs = (OpenJPAEntityManagerFactorySPI)emf;
JDBCConfiguration jdbccfg = (JDBCConfiguration)emfs.getConfiguration();
return jdbccfg.getDBDictionaryInstance().supportsUniqueConstraints;
}
void assertSQLFragnments(List<String> list, String... keys) {
if (SQLSniffer.matches(list, keys))
return;
fail("None of the following " + sql.size() + " SQL \r\n" +
toString(sql) + "\r\n contains all keys \r\n"
+ toString(Arrays.asList(keys)));
}
}

View File

@ -220,5 +220,16 @@
value="buildSchema"/>
</properties>
</persistence-unit>
<persistence-unit name="NamedUniqueConstraintTest" transaction-type="RESOURCE_LOCAL">
<description>PU for order named unique constraint testing</description>
<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
<mapping-file>org/apache/openjpa/persistence/jdbc/unique/named-constraint-orm.xml</mapping-file>
<class>org.apache.openjpa.persistence.jdbc.unique.NamedUniqueA</class>
<class>org.apache.openjpa.persistence.jdbc.unique.NamedUniqueB</class>
<properties>
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
</properties>
</persistence-unit>
</persistence>

View File

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence orm_2_0.xsd"
version="2.0">
<persistence-unit-metadata>
<xml-mapping-metadata-complete/>
</persistence-unit-metadata>
<package>org.apache.openjpa.persistence.jdbc.unique</package>
<entity name="NamedUniqueA" class="NamedUniqueA">
<table name="NX_UNIQUE_A">
<unique-constraint>
<name>ucxa_f1_f2</name>
<column-name>f1x</column-name>
<column-name>f2x</column-name>
</unique-constraint>
<unique-constraint>
<name>ucxa_f3_f4</name>
<column-name>f3x</column-name>
<column-name>f4x</column-name>
</unique-constraint>
</table>
<secondary-table name="NX_UNIQUE_SECONDARY">
<unique-constraint>
<name>ucxa_sf1</name>
<column-name>sf1x</column-name>
</unique-constraint>
</secondary-table>
<attributes>
<id name="aid">
</id>
<basic name="f1">
<column name="f1x" unique="true"/>
</basic>
<basic name="f2">
<column name="f2x"/>
</basic>
<basic name="f3">
<column name="f3x"/>
</basic>
<basic name="f4">
<column name="f4x"/>
</basic>
<basic name="f5">
<column name="f5x"/>
</basic>
<basic name="f6">
<column name="f6x"/>
</basic>
<basic name="sf1">
<column name="sf1x" table="NX_UNIQUE_SECONDARY" />
</basic>
<basic name="sf2">
<column name="sf2x" table="NX_UNIQUE_SECONDARY" />
</basic>
<many-to-many name="bs">
<join-table name="NX_UNIQUE_JOINTABLE">
<join-column name="FK_A_XML" referenced-column-name="aid" nullable="false"/>
<inverse-join-column name="FK_B_XML" referenced-column-name="bid" nullable="false"/>
<unique-constraint>
<name>ucxa_fka_fkb</name>
<column-name>FK_A_XML</column-name>
<column-name>FK_B_XML</column-name>
</unique-constraint>
</join-table>
</many-to-many>
</attributes>
</entity>
<entity name="NamedUniqueB" class="NamedUniqueB">
<table name="NX_UNIQUE_B">
<unique-constraint>
<name>ucxb_f1_f2</name>
<column-name>f1x</column-name>
<column-name>f2x</column-name>
</unique-constraint>
</table>
<attributes>
<id name="bid">
<generated-value strategy="TABLE"
generator="testGeneratorXML" />
<table-generator name="testGeneratorXML"
table="NX_UNIQUE_GENERATOR" pk-column-name="GEN1_XML"
value-column-name="GEN2_XML">
<unique-constraint>
<name>ucxb_gen1_gen2</name>
<column-name>GEN1_XML</column-name>
<column-name>GEN2_XML</column-name>
</unique-constraint>
</table-generator>
</id>
<basic name="f1">
<column name="f1x" unique="true"/>
</basic>
<basic name="f2">
<column name="f2x"/>
</basic>
<element-collection name="f3">
<column name="f3x" nullable="false"/>
<collection-table name="NX_U_COLL_TBL">
<unique-constraint>
<name>ucxb_f3</name>
<column-name>f3x</column-name>
</unique-constraint>
</collection-table>
</element-collection>
</attributes>
</entity>
</entity-mappings>

View File

@ -363,11 +363,17 @@ is sent to the database before the SQL inserting <literal>B</literal> to avoid a
unique constraint violation.
</para>
<para>
<classname>UniqueConstraint</classname> has a single property:
<classname>UniqueConstraint</classname> has these properties:
</para>
<itemizedlist>
<listitem>
<para>
<literal>String name</literal>: The name of the constraint. OpenJPA will choose
a name if you do not provide one, or will create an anonymous constraint.
</para>
</listitem>
<listitem>
<para>
<literal>String[] columnNames</literal>: The names of the columns the
constraint spans.
</para>
@ -390,7 +396,7 @@ column of the <literal>ART</literal> table:
</para>
<programlisting>
@Entity
@Table(name="ART", uniqueConstraints=@Unique(columnNames="TITLE"))
@Table(name="ART", uniqueConstraints=@UniqueConstraint(name="TITLE_CNSTR", columnNames="TITLE"))
public class Article {
...
}
@ -402,6 +408,7 @@ The same metadata expressed in XML form:
&lt;entity class="org.mag.Article"&gt;
&lt;table name="ART"&gt;
&lt;unique-constraint&gt;
&lt;name&gt;TITLE_CNSTR&lt;/name&gt;
&lt;column-name&gt;TITLE&lt;/column-name&gt;
&lt;/unique-constraint&gt;
&lt;/table&gt;