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
Related
I would like to obtain the same result of query using WHERE instead of GROUP BY. I have applications and each application has statuses with date. I need to return applications with their latest status date.
My query with ORDER BY: (the result is correct)
select a.guid, MAX(s.request_time) as last_request_time
from public.applications_status s inner join public.applications a on a.guid = s.guid_application
group by a.guid ;
result:
guid |last_request_time |
------------------------------------|-------------------|
330d32d5-2496-4cce-9d11-29e59333766a|2020-07-22 13:06:25|
5b46cda9-b954-4d8b-82cf-f1d83f77b175|2020-07-22 13:07:25|
34071189-ab3d-47ff-9ee1-aca6fa806bc9|2020-08-03 10:45:15|
a8961058-a6ee-4d71-b325-9aca83b22237|2020-08-03 10:45:39|
ff98695f-e1a8-439e-8a6c-7991348b6cd7|2020-07-29 14:38:18|
I try this but it return me only the one application with latest status date:
select a.guid, s.request_time
from public.applications_status s inner join public.applications a on a.guid = s.guid_application
where request_time = (select MAX(applications_status.request_time) from applications_status );
result:
guid |request_time |
------------------------------------|-------------------|
a8961058-a6ee-4d71-b325-9aca83b22237|2020-08-03 10:45:39|
Applications table
CREATE TABLE public.applications (
id bigserial NOT NULL,
guid varchar(40) NOT NULL,
"name" varchar(60) NOT NULL,
latest_status_date timestamp NULL,
latest_status bool NOT NULL,
id_production bigserial NOT NULL,
CONSTRAINT applications_guid_key UNIQUE (guid),
CONSTRAINT applications_pkey PRIMARY KEY (id),
CONSTRAINT uk_gtuqgycxk8ulkir3io2p49yn1 UNIQUE (guid),
CONSTRAINT fkaid_prod FOREIGN KEY (id_production) REFERENCES productions(id) ON DELETE CASCADE
);
Applications_status table
CREATE TABLE public.applications_status (
id bigserial NOT NULL,
status bool NOT NULL,
guid_application varchar(50) NOT NULL,
log varchar(200) NULL,
request_time timestamp NOT NULL,
CONSTRAINT status_pkey PRIMARY KEY (id),
CONSTRAINT fkaguid_application FOREIGN KEY (guid_application) REFERENCES applications(guid) ON UPDATE CASCADE ON DELETE CASCADE
);
Why I need this way? I try to return Applications with their latest status in Spring Boot using #Where annotation in #OneToMany relation in Entity.
#OneToMany(cascade = CascadeType.ALL, mappedBy = "application", fetch = LAZY)
#JsonManagedReference
#Where(clause = "request_time = (SELECT MAX(applications_status.request_time) FROM applications_status )")
#OrderBy("requestTime DESC")
private List<ApplicationStatus> applicationStatuses;
I also try to use #BatchSize(size = 1) but it doesn't work.
The question is tagged both "sql" and "postgres", so this is a Postgres solution.
Use distinct on:
select distinct on (a.guid) a.*, s.*
from public.applications_status s inner join
public.applications a
on a.guid = s.guid_application
order by a.guid, s.request_time desc;
distinct on is a very handy Postgres extension that returns one row (the "first" row) for each group in the parentheses. The particular row is based on the order by.
Through trial and error, I found a solution:
SQL query:
select a.guid, s.request_time
from public.applications_status s inner join public.applications a on a.guid = s.guid_application
where request_time = (select MAX(applications_status.request_time)
from applications_status
where applications_status.guid_application = s.guid_application );
Spring-Boot:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "application", fetch = LAZY)
#JsonManagedReference
#Where(clause = "request_time = (SELECT MAX(applications_status.request_time) FROM applications_status where guid_application = applications_status.guid_application )")
#OrderBy("requestTime DESC")
private List<ApplicationStatus> applicationStatuses;
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
I have a class with map inside. It is mapped with use of join table.
#Entity
#Table(name = "Sources")
#Lazy(false)
public class Sources {
#ManyToMany( fetch = FetchType.EAGER )
#JoinTable( name = "sources_lists", joinColumns = #JoinColumn( name = "list_id" ) )
#MapKeyColumn( name = "source_id" )
public Map<Integer, Source> getSources() { return sources; }
public void setSources( Map<Integer, Source> sourcesList ) { this.adSources = adSourcesList; }
private Map<Integer, Source> sources;
#Override
#Id #GeneratedValue(strategy = GenerationType.AUTO)
#Column( name="id", unique = true, nullable = false, updatable = false )
public Integer getId() { return super.getId(); }
}
I receive the following exception: "Unknown column 'sources0_.sources' in 'field list'".
When I change 'list_id' column name to the 'sources' things work, but I can't do this in production.
Tables are:
CREATE TABLE `sources` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`) );
CREATE TABLE `source` (
`DTYPE` varchar(31) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`className` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`));
CREATE TABLE `sources_lists` (
list_id` int(11) NOT NULL,
`source_id` int(11) NOT NULL,
KEY `FK54DCBD0B4307D0FC` (`source_id`),
KEY `FK54DCBD0B575FBECF` (`list_id`),
CONSTRAINT `FK54DCBD0B4307D0FC` FOREIGN KEY (`source_id`) REFERENCES `source` (`id`),
CONSTRAINT `FK54DCBD0B575FBECF` FOREIGN KEY (`list_id`) REFERENCES `sources` (`id`));
I realized that the issue was not with column, but with somethings else: I want to map Source objects by its id and Hibernate assumes that there are 3 columns in join table: parent object id (Sources class, list_id column), object id (Source class, source_id column) and a separate column for map key. I'll open other question to ask what is the way to map object in Hibernate by its id.
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.
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.