#JoinTable to use Imported Key instead of primary key - java

Using JPA/Hibernate 3.6/DB2.
I have got the following exception:
Caused by: org.hibernate.AnnotationException: SecondaryTable JoinColumn cannot reference a non primary key
caused by:
public class XRequest {
#ManyToOne
#JoinTable(
name = "RequestBatch",
joinColumns = #JoinColumn(name = "requestBatchID", referencedColumnName="requestBatchID"),
inverseJoinColumns = #JoinColumn(name = "requestVersionID")
)
private Requestversion requestversion;
}
requestBatchID is not a primary key, but an imported key from the RequestBatch table (and there, it is indeed the primary key). Why does JoinTable have to use a primary key? I mean, didn't I just define that this is a many-to-one association?
Why does it have to be a primary key?
To specify: This is what the tables look like.
XRequest (
requestId int (primary)
requestBatchId int (imported key from RequestBatch)
)
RequestBatch (
requestBatchId int (primary)
requestVersionId int
)
RequestVersion (
requestVersionId int (primary)
)
The wanted outcome is this SQL query to be built for me by Hibernate:
select xr, rv
from XRequest xr
left outer join RequestBatch rb on rb.requestBatchId = xr.requestBatchId
inner join RequestVersion rv on rb.requestVersionId = rv.requestVersionId

If you read the JPA Documentation on #JoinTable, you will see descriptions for joinColumns and inverseJoinColumns mention:
The foreign key columns of the join table which reference the primary
table of the entity...
I guess that is enough to understand the constraints.

Related

Spring boot delete is not cascading it is setting the foriegn key to null and then failing on a null constraint

Using deleteById in spring boot with a one to many relationship the query being generated tries to set the foreign key to null in the referenced entities instead of deleting them. I am using the default repository deleteById
I've set the Cascadetype to ALL and OrpahnRemoval to true on the definition of the foreign key in the entity and I've set ON DELETE CASCADE in the DDL that created the table.
Here is the delete operation in the controller class
#Transactional
#DeleteMapping("transferImage/{imageId}")
public void deleteTransferImage(#PathVariable int imageId) {
repository.deleteById(imageId);
}
Here is the reference from the parent to the child entity
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, OrphanRemoval = true)
#JoinColumn(name = "TRANSFER_IMAGE_ID")
private List<TransferPartition> partitions = new ArrayList<>();
Here is the definition of the foreign key in the child entity
#JsonIgnore
#ManyToOne
#JoinColumn(name = "TRANSFER_IMAGE_ID", referencedColumnName = "TRANSFER_IMAGE_ID")
private TransferImage image;
Here is the DDL used to create the two tables
CREATE TABLE TRANSFER_IMAGE (
TRANSFER_IMAGE_ID SERIAL CONSTRAINT TRANSFER_IMAGE_PK PRIMARY KEY,
IMAGE_NAME VARCHAR(50) CONSTRAINT TRANSFER_IMAGE_NAME_UK UNIQUE NOT NULL ,
REQUESTED_PART_SIZE_MB INTEGER NOT NULL,
SIZE_BYTES INTEGER NOT NULL,
IMAGE_MD5_HASH VARCHAR(100),
NUMBER_PARTITIONS INTEGER,
DELETED BOOLEAN NOT NULL
);
CREATE TABLE TRANSFER_PARTITION (
TRANSFER_PARTITION_ID SERIAL CONSTRAINT TRANSFER_PARTITION_PK PRIMARY KEY,
TRANSFER_IMAGE_ID INTEGER NOT NULL CONSTRAINT TRANSFER_PARTITION_IMAGE_FK REFERENCES TRANSFER_IMAGE ON DELETE CASCADE ON UPDATE CASCADE,
PARTITION_NUMBER INTEGER NOT NULL,
PARTITION_MD5_HASH VARCHAR(100) NOT NULL,
SIZE_BYTES INTEGER NOT NULL
);
Here is the query that appears in the log
Hibernate:
select
transferim0_.transfer_image_id as transfer1_13_0_,
transferim0_.deleted as deleted2_13_0_,
transferim0_.image_md5_hash as image_md3_13_0_,
transferim0_.image_name as image_na4_13_0_,
transferim0_.number_partitions as number_p5_13_0_,
transferim0_.requested_part_size_mb as requeste6_13_0_,
transferim0_.size_bytes as size_byt7_13_0_,
partitions1_.transfer_image_id as transfer5_14_1_,
partitions1_.transfer_partition_id as transfer1_14_1_,
partitions1_.transfer_partition_id as transfer1_14_2_,
partitions1_.transfer_image_id as transfer5_14_2_,
partitions1_.partition_number as partitio2_14_2_,
partitions1_.partition_md5_hash as partitio3_14_2_,
partitions1_.size_bytes as size_byt4_14_2_
from
transfer_image transferim0_
left outer join
transfer_partition partitions1_
on transferim0_.transfer_image_id=partitions1_.transfer_image_id
where
transferim0_.transfer_image_id=?
Hibernate:
update
transfer_partition
set
transfer_image_id=null
where
transfer_image_id=?
I was expecting that all the child entities(TransferPartition) that reference the parent (TransferImage) to be deleted when I delete the parent by its primary key. Instead I get a null constraint error referring to the foreign key column. It looks to me like the generated SQL is setting the foreign key column to null instead of deleting the row.
ERROR: null value in column "transfer_image_id" violates not-null constraint
Detail: Failing row contains (1, null, 1, asdfaa1-1, 20000000).
If I delete the image from the transfer_image table from the psql prompt the delete cascades properly and the referenced partitions are removed.
delete from transfer_image i where i.transfer_image_id = 1
Your problem is in the definition of the foreign key relation in the TransferImage class.
Instead of
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, OrphanRemoval = true)
#JoinColumn(name = "TRANSFER_IMAGE_ID")
private List<TransferPartition> partitions = new ArrayList<>();
you should use
#OneToMany(mappedBy="image", cascade = CascadeType.ALL, fetch = FetchType.EAGER, OrphanRemoval = true)
private List<TransferPartition> partitions = new ArrayList<>();
See also https://www.baeldung.com/hibernate-one-to-many

JPA ManyToMany self bidirectional reference by one field

I have one entity User. I need ManyToMany bidirectional relation User-User pairs in one field. How I can do it?
#Entity
#Table(name = "users")
public class User {
#Id
private int id;
private Set<User> pairs;
}
I tried like this:
#ManyToMany
#JoinTable(name = "pairs", joinColumns = {
#JoinColumn(name = "a"),
#JoinColumn(name = "b")
})
private Set<User> pairs;
And got next result: org.hibernate.AnnotationException: A Foreign key refering com.calm.model.entity.User from com.calm.model.entity.User has the wrong number of column. should be 1
Db scmema generated by ddl:
CREATE TABLE `users` (
`id` int(11) NOT NULL,
...
PRIMARY KEY (`id`)
)
And expected pairs table:
CREATE TABLE `pairs` (
`a` int(11) NOT NULL, //user 1
`b` int(11) NOT NULL, //user 2
PRIMARY KEY (`a`,`b`)
)
And expected behavior like:
SELECT b as id2 FROM pairs WHERE a = :id1
UNION
SELECT a as id2 FROM pairs WHERE b = :id1

Join 3 tables using javax.persistence in a OneToMany relationship

The 3 tables are "analyticalgroups", "labinstructions", "observedproperties". Each table has an "id" primary key column.
I'd like to use a 4th table ("analyticalgroups_observedproperties_labinstructions") to store the OneToMany relationship. Ultimately I'd like the output to be structured something like this:
analyticalGroup: {
id: "...",
observedPropertyLabInstructions: [
{observedProperty, labInstruction},
{observedProperty, labInstruction},
{observedProperty, labInstruction},
...etc...
]
}
I've followed some examples online, but can't get this to work. The problem is when I try this I get the following error:
"message" : "Error occurred at repository: PSQLException: ERROR: column observedpr0_.observedpropertyentitylabinstructionentitymap_id does not exist\n Position: 6550",
"errorCode" : "gaia.domain.exceptions.RepositoryException",
Here's the structure for the join table.
CREATE TABLE analyticalgroups_observedproperties_labinstructions
(
analyticalgroupid character varying(36) NOT NULL,
labinstructionid character varying(36) NOT NULL,
observedpropertyid character varying(36) NOT NULL,
CONSTRAINT fk_analyticalgroups_observedproperties_labinstructions_groupid FOREIGN KEY (analyticalgroupid)
REFERENCES analyticalgroups (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT fk_analyticalgroups_observedproperties_labinstructions_labinstr FOREIGN KEY (labinstructionid)
REFERENCES labinstructions (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT fk_analyticalgroups_observedproperties_labinstructions_observed FOREIGN KEY (observedpropertyid)
REFERENCES observedproperties (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE
)
#Entity
#Data
public class AnalyticalGroupEntity {
public static final String ENTITY_NAME = "analyticalGroups";
public static final String JOIN_OBSERVEDPROPERTIES_LABINSTRUCTIONS_TABLE_NAME =
ENTITY_NAME +
IDomainEntity.UNDERSCORE +
ObservedPropertyEntity.ENTITY_NAME +
IDomainEntity.UNDERSCORE +
LabInstructionEntity.ENTITY_NAME;
#Id
#Column(name = IDomainEntity.ID_KEY, nullable = false, columnDefinition = IDomainEntity.COLUMN_TYPE_UUID)
private String id;
#OneToMany
#JoinTable(
name = JOIN_OBSERVEDPROPERTIES_LABINSTRUCTIONS_TABLE_NAME,
joinColumns = #JoinColumn(name = LabInstructionEntity.ID_KEY, referencedColumnName = IDomainEntity.ID_KEY, table = "labinstructions")
)
#MapKeyJoinColumn(name = ObservedPropertyEntity.ID_KEY, referencedColumnName = IDomainEntity.ID_KEY, table = "observedproperties")
private Map<ObservedPropertyEntity, LabInstructionEntity> observedPropertyLabInstructions;
}
Hopefully I've laid this all out as clearly as necessary.
Your help is much appreciated. Thanks for reading!
edit Actually... it turns out this doesn't work. It successfully gets the data I want, buuuuut it also deletes every row in the join table whenever I make a GET request *flip table*
So bizarre!
#OneToMany
#JoinTable(
name = JOIN_OBSERVEDPROPERTIES_LABINSTRUCTIONS_TABLE_NAME,
joinColumns = #JoinColumn(name = "analyticalgroupid", referencedColumnName = IDomainEntity.ID_KEY, table = "labinstructions"),
inverseJoinColumns = #JoinColumn(name = LabInstructionEntity.ID_KEY, referencedColumnName = IDomainEntity.ID_KEY, table = "labinstructions")
)
#MapKeyJoinColumn(name = ObservedPropertyEntity.ID_KEY, referencedColumnName = IDomainEntity.ID_KEY, table = "observedproperties")
private Map<ObservedPropertyEntity, LabInstructionEntity> observedPropertyEntityLabInstructionEntityMap;

MySQLIntegrityConstraintViolationException when updating list of entities mapped as #ManyToMany, #JoinTable and #OrderColumn (with unique constraint)

I have a many-to-many relationship between Prequalification and Company entities (Partnership). The DDL for the three tables is:
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!
);
Please note the unique constraint on Partnerships's PQ ID and ordinal number: there can be only one position of a company per PQ.
This got mapped in Prequalification as #ManyToMany + #JoinTable including an #OrderColumn for the order of the companies per PQ (Partnerships's order column is 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;
...
}
Here's the update method which is called from the GUI. Note, that the list on the GUI only includes external companies and that the internal company is (supposed to be) at index zero always:
#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...
}
...
}
In the client JSF page the field participatingCompanies is used like:
<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>
Don't be intimidated RichFaces component. The list referencing #{pqHome.participatingCompanies} just contains optional (external) companies.
When I hit the update button (not shown), the update method on the PqHome bean is called. When executing the code on GlassFish 3.1.2 using EclipseLink 2.3.2 the following exception is thrown:
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
This exception is duplicated several times in the log. Other PQ's have the same problem, just involve other unique column combinations, e.g. '1-1'. The server log can be found here: http://www.eclipse.org/forums/index.php/t/310923/
What's wrong with the update code? It works without problems on Hibernate.
EclipseLink updates the #OrderColumn in the join table to maintain the order efficiently, so you cannot have a unique constraint on it. So, remove or defer the unique constraint.
You could log a bug on EclipseLink to have an option added to avoid this restriction.

JPA ManyToMany referencing issue

I have three tables. AvailableOptions and PlanTypeRef with a ManyToMany association table called AvailOptionPlanTypeAssoc. The trimmed down schemas look like this
CREATE TABLE [dbo].[AvailableOptions](
[SourceApplication] [char](8) NOT NULL,
[OptionId] [int] IDENTITY(1,1) NOT NULL,
...
)
CREATE TABLE [dbo].[AvailOptionPlanTypeAssoc](
[SourceApplication] [char](8) NOT NULL,
[OptionId] [int] NOT NULL,
[PlanTypeCd] [char](2) NOT NULL,
)
CREATE TABLE [dbo].[PlanTypeRef](
[PlanTypeCd] [char](2) NOT NULL,
[PlanTypeDesc] [varchar](32) NOT NULL,
)
And the Java code looks like this.
//AvailableOption.java
#ManyToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER)
#JoinTable(
name = "AvailOptionPlanTypeAssoc",
joinColumns = { #JoinColumn(name = "OptionId"),
#JoinColumn(name="SourceApplication")},
inverseJoinColumns=#JoinColumn(name="PlanTypeCd"))
List<PlanType> planTypes = new ArrayList<PlanType>();
//PlanType.java
#ManyToMany
#JoinTable(
name = "AvailOptionPlanTypeAssoc",
joinColumns = { #JoinColumn(name = "PlanTypeCd")},
inverseJoinColumns={#JoinColumn(name="OptionId"),
#JoinColumn(name="SourceApplication")})
List<AvailableOption> options = new ArrayList<AvailableOption>();
The problem arises when making a select on AvailableOptions it joins back onto itself. Note the following SQL code from the backtrace. The second inner join should be on PlanTypeRef.
SELECT t0.OptionId,
t0.SourceApplication,
t2.PlanTypeCd,
t2.EffectiveDate,
t2.PlanTypeDesc,
t2.SysLstTrxDtm,
t2.SysLstUpdtUserId,
t2.TermDate
FROM dbo.AvailableOptions t0
INNER JOIN dbo.AvailOptionPlanTypeAssoc t1
ON t0.OptionId = t1.OptionId AND t0.SourceApplication = t1.SourceApplication
INNER JOIN dbo.AvailableOptions t2
ON t1.PlanTypeCd = t2.PlanTypeCd
WHERE (t0.SourceApplication = ? AND t0.OptionType = ?)
ORDER BY t0.OptionId ASC, t0.SourceApplication ASC
[params=(String) testApp, (String) junit0]}
You are mapping a bidirectional association. That means you have to choose one side as the owner of the association. This side will be responsible for updating the relationship in the database.
If you choose AvailableOption as the owner of the relationship and you want a new PlanType for it, you have to add the plantype to the option. Adding the option only to the plantype will have no effect.
Here is the Mapping:
//AvailableOption.java
#ManyToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER)
#JoinTable(
name = "AvailOptionPlanTypeAssoc",
joinColumns = { #JoinColumn(name = "OptionId"),
#JoinColumn(name="SourceApplication")},
inverseJoinColumns=#JoinColumn(name="PlanTypeCd"))
List<PlanType> planTypes = new ArrayList<PlanType>();
//PlanType.java
#ManyToMany(
mappedBy = "planTypes"
)
List<AvailableOption> options = new ArrayList<AvailableOption>();
You may also refer to the hibernate annotation documentation chapter 2.2.5
Regards
David

Categories