У меня есть отношение многие ко многим между Prequalification
и Company
сущностями (Partnership
). DDL для трех таблиц:
CREATE TABLE Prequalifications
(
id INTEGER NOT NULL IDENTITY,
user_id INTEGER NOT NULL,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES Users (id)
);
CREATE TABLE Partnerships
(
prequalification_id INTEGER NOT NULL,
company_id INTEGER NOT NULL,
ordinal_nbr SMALLINT DEFAULT 0 NOT NULL,
PRIMARY KEY (prequalification_id, company_id),
FOREIGN KEY (prequalification_id) REFERENCES Prequalifications (id),
FOREIGN KEY (company_id) REFERENCES Companies (id),
UNIQUE (prequalification_id, ordinal_nbr)
);
CREATE TABLE Companies
(
id INTEGER NOT NULL,
dnd_type VARCHAR(10) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (id) REFERENCES Organizations (id) -- just an inherited ID, never mind!
);
Обратите внимание на уникальное ограничение для PQ ID и порядкового номера Partnerships
: на PQ может быть только одна позиция компании.
Это было отображено в Prequalification
как @ManyToMany
+ @JoinTable
, включая @OrderColumn
для заказа компаний на PQ (столбец заказа Partnerships
- ordinal_nbr):
@Entity
@Table(name = "Prequalifications")
public class Prequalification implements Serializable
{
...
@ManyToMany
@JoinTable(name = "Partnerships", joinColumns = @JoinColumn(name = "prequalification_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "company_id", referencedColumnName = "id"))
@OrderColumn(name = "ordinal_nbr", nullable = false)
private List<Company> companies;
...
}
Вот метод обновления, который вызывается из GUI. Обратите внимание, что список в графическом интерфейсе включает в себя только внешние компании, и что внутренняя компания всегда находится в нулевом индексе:
@ManagedBean
@ViewScoped
public class PqHome implements DropListener, Serializable
{
...
private Prequalification pq;
private Integer userId;
private List<Company> participatingCompanies; // xetters omitted!
...
public void update()
{
// assign new owner
pq.setUser(userService.findSingleUser(userId));
Company internal = companyManager.getInternalCompany();
// find internal company
int index = participatingCompanies.indexOf(internal);
// if internal company missing or at non-zero index: we need to repair this
if ( index != 0 )
{
// if internal exists at some wrong place, remove it
if ( index > 0 )
{
participatingCompanies.remove(index);
}
// (re-)add at index zero
participatingCompanies.add(0, internal);
}
pq.setCompanies(participatingCompanies);
// update and get *new* merged instance
pq = pqService.update(pq);
participatingCompanies = null;
init(); // some not so important (re-)initialization...
}
...
}
На странице клиента JSF поле participatingCompanies
используется как:
<rich:pickList value="#{pqHome.participatingCompanies}"
var="company"
converter="#{companyConverter}"
orderable="true"
sourceCaption="Available companies"
targetCaption="Selected companies">
<f:selectItems value="#{companyManager.externalCompanies}" />
<rich:column>#{company.name}</rich:column>
</rich:pickList>
Не пугайтесь компонента RichFaces. Список ссылок #{pqHome.participatingCompanies}
просто содержит необязательные (внешние) компании.
![screenshot](https://i.stack.imgur.com/BC1FD.png)
Когда я нажимаю кнопку обновления (не показана), вызывается метод update
в компоненте PqHome
. При выполнении кода на GlassFish 3.1.2 с использованием EclipseLink 2.3.2 выдается следующее исключение:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '2-2' for key 'partnerships_multi_uq'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at com.mysql.jdbc.Util.getInstance(Util.java:386)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1039)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3609)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3541)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2002)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2163)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2624)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2127)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2427)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2345)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2330)
at com.mysql.jdbc.jdbc2.optional.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:875)
at com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:125)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:831)
... 128 more
Это исключение дублируется в журнале несколько раз. Другие PQ имеют такую же проблему, просто включают другие уникальные комбинации столбцов, например, '1-1'. Журнал сервера можно найти здесь: http://www.eclipse.org/forums/index.php/t/310923/
Что не так с кодом обновления? В Hibernate работает без проблем.