How to limit a ManyToMany with an extra where clause in JPA - java

I'm retrieving data from a legacy database, thus have no control at all over the schema.
I frequently need to check for a static value in a separate column to remove false matches.
create table mySource (
id int,
...
)
create table aRelation (
srcId int,
myFK int,
relationLimit varchar
)
create table aTarget (
id int,
...
myFK int,
aLimit varchar,
...
)
"relationLimit" and/or "aLimit" above must match a static value.
I can only find the standard annotations:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "aRelation",
joinColumns = {
#JoinColumn(name = "srcId") },
#inverseJoinColumns = {
#JoinColumn(name = "myFK", referencedColumnName = "myFK")
})
private List<ATarget> targets;
But I can't find any way to annotate the requirement(s)
aLimit = "Something"
relationLimit="SomethingElse"
If this is obvious in the documentation please tell me how and where to read it.

If you use Hibernate you can try #Filter annotation on entity.
#Filter(name="betweenLength", condition="aLimit = 'Something' and relationLimit='SomethingElse'")
More on this topic you can find here

Related

ManyToOne on column that is not unique

I have one table Message in which I keep both incoming and outgoing messages. A column "category" is kept to differentiate, that can have 1 = INCOMING, 2 = OUTGOING. Outgoing messages can have attachments. The following is my Message entity class:
#Entity("MESSAGE")
class Message{
Long id; //primary key
String additionalStringId;
private Long senderId;
private Long receiverId;
Category messageCategory;
#OneToMany(
mappedBy = "message",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<AttachmentEntity> outgoingAttachments= new ArrayList<>();
}
The problem is that AttachmentEntity is linked with the message via the "additionalStringId" and I can not impact the database schema.
class AttachmentEntity{
#ManyToOne
#JoinColumn(name = "additionalStringId", referencedColumnName = "additionalStringId")
private Message message;
}
I know it's not working snipping code but my problem appears when sender = receiver, when I try to send a message to myself. In that case in my database I will have two messages, with the same additionalStringId but with category = 1 and the other with category = 2. My attachment entity list tries to link to message but two messages are visible. What can I do to fix this problem?
I tried separating the two categories into separate entities with #Where and #DiscriminatorFormula and #DiscriminatorValue but I could not get it to work. What can I do?
Table DDLs:
CREATE TABLE "MESSAGE"
( "ID" NUMBER NOT NULL ENABLE,
"MESSAGE_ID" VARCHAR2(60) NOT NULL ENABLE,
"CATEGORY" NUMBER,
"SENDER_ID" NUMBER,
"RECEIVER_ID" NUMBER )
CREATE TABLE "OUTBOX_ATTACHMENT"
( "ID" NUMBER NOT NULL ENABLE,
"MESSAGE_ID" VARCHAR2(60) NOT NULL ENABLE,
)
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "additionalStringId", referencedColumnName = "id", nullable = false)
private Message message;
#OneToMany(mappedBy = "message", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<AttachmentEntity> outgoingAttachments = new ArrayList<>();
something like this might work.
I think the problem you are facing is not at the JPA level, but at the table definition level (DDL).
If you change the table definition as shown below, the FK relationship becomes easy to establish:
CREATE TABLE "MESSAGE"
( "ID" NUMBER NOT NULL,
"MESSAGE_ID" VARCHAR2(60) NOT NULL,
"CATEGORY" NUMBER,
"SENDER_ID" NUMBER,
"RECEIVER_ID" NUMBER,
constraint uq1 unique (category, message_id)
);
CREATE TABLE "OUTBOX_ATTACHMENT"
( "ID" NUMBER NOT NULL ,
category number not null constraint chk1 check (category = 2),
"MESSAGE_ID" VARCHAR2(60) NOT NULL ,
constraint fk1 foreign key (category, message_id)
references message (category, message_id)
);
As you can see the FK includes two columns: category, and message_id. But, as per requirements, in the attachement table category can only accept attachment to outgoing messages (value 2).

How to combine distinct and sort in spring data jpa specification query with joins

Example setup:
Entity
#Entity
class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
#ManyToMany(cascade = [CascadeType.PERSIST, CascadeType.MERGE])
#JoinTable(name = "book_authors",
joinColumns = [JoinColumn(name = "book_id")],
inverseJoinColumns = [JoinColumn(name = "author_id")])
var authors: MutableSet<Author> = HashSet()
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "publisher_id")
lateinit var publisher: Publisher
}
Both Author and Publisher are simple entities with just an id and a name.
The spring data jpa BookSpecification: (notice the distinct on query)
fun hasAuthors(authorNames: Array<String>? = null): Specification<Book> {
return Specification { root, query, builder ->
query.distinct(true)
val matchingAuthors = authorRepository.findAllByNameIn(authorNames)
if (matchingAuthors.isNotEmpty()) {
val joinSet = root.joinSet<Book, Author>("authors", JoinType.LEFT)
builder.or(joinSet.`in`(matchingContentVersions))
} else {
builder.disjunction()
}
}
}
Executing the query (pageable containing a sort on publisher.name)
bookRepository.findAll(
Specification.where(bookSpecification.hasAuthors(searchRequest)),
pageable!!)
The REST request:
MockMvcRequestBuilders.get("/books?authors=Jane,John&sort=publisherName,desc")
This results in the following error:
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Order by expression "PUBLISHERO3_.NAME" must be in the result list in this case;
The problem is in the combination of the distinct and sort. The distinct requires the publisher name to be in the select fields to be able to sort.
How can I fix this with Specification query?
You'll likely have to explicitly select the PUBLISHERO3_.NAME column like so:
query.select(builder.array(root.get("PUBLISHERO3_.NAME"), root.get("yourColumnHere")));
Joined columns are probably not included by default because they're out of scope with regards to the root generic type.
you can't do this. basically, if you have distinct and you want to sort, you can only use the selected columns.
what you can do is to use row_number() window function instead of distinct, and then select everything with row_number=1.
you can find an (a little bit old) example here: https://stackoverflow.com/a/30827497/10668681

Hibernate sql annotation

I am using Hibernate to interface with SQL Server 2016/Azure SQL Server currently, and have been having a great time with it so far. In my database, I have implemented system versioned temporal tables. I want to map (preferably lazily) two more variables by annotation only to my Hibernate entity that represent the original ValidFrom and UpdatedBy fields from the temporal history of the appropriate table.
For example, I have a class and table for Accounts. The Account [minus nonrelated columns, constraints, etc] table is as follows:
CREATE TABLE [dbo].[Account] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[UpdatedBy] INT NOT NULL,
[ValidFrom] DATETIME2 (7) GENERATED ALWAYS AS ROW START DEFAULT (sysutcdatetime()) NOT NULL,
[ValidTo] DATETIME2 (7) GENERATED ALWAYS AS ROW END DEFAULT (CONVERT([datetime2],'9999-12-31 23:59:59.9999999')) NOT NULL,
CONSTRAINT [FK_Account.UpdatedById_Account.Id] FOREIGN KEY ([UpdatedBy]) REFERENCES [dbo].[Account] ([Id]),
PRIMARY KEY CLUSTERED ([Id] ASC),
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo])
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE=[dbo].[AccountHistory], DATA_CONSISTENCY_CHECK=ON));
The SQL statement to get the data that I want looks like this (I imagine that I would select only UpdatedBy or ValidFrom per annotation, but they are together now to be concise):
SELECT UpdatedBy, ValidFrom FROM dbo.Account
FOR SYSTEM_TIME ALL
WHERE ValidFrom IN
(
SELECT MIN(ValidFrom) OVER (Partition BY Id) AS ValidFrom
FROM dbo.Account
FOR SYSTEM_TIME ALL
WHERE ID = $(passedInIdOfThisEntity)
)
Finally, my Hibernate entity/pojo looks something like this (again, redacting irrelevant variables):
#Entity
#Table(name = "Account")
public class Account implements Serializable {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "UpdatedBy")
private Account updatedBy;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidFrom", nullable = false, length = 27, insertable = false, updatable = false)
private Date validFrom;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidTo", nullable = false, length = 27, insertable = false, updatable = false)
private Date validTo;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "updatedBy")
private Set<Account> accountsUpdated;
// This is a stub of what I'm hoping you can help me add
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidFrom", fetch = FetchType.LAZY, insertable = false, updatable = false, somesqlselect = SQL_STATEMENT_FROM_ABOVE)
private Date createdOn;
#Column(name = "UpdatedBy", fetch = FetchType.LAZY, insertable = false, updatable = false, somesqlselect = SQL_STATEMENT_FROM_ABOVE)
private Account createdBy
// ... getters and setters below
}
I have been using Hibernate to a great extent, but have had trouble finding information on this, though I have found and used examples of implementing native queries for retrieving entities instead of using criteria queries. If you can help me solve this riddle to allow me to continue using criteria queries to retrieve data and populate these fields through annotation on demand, I would greatly appreciate it.
The temporal table constructs that you've described isn't something that I am aware that JPA or even Hibernate support natively. These are likely new features of the ANSI SQL standard which haven't made their way into proper support.
That said, that doesn't mean you cannot use frameworks like Hibernate to accomplish the task. As indicated in the comments, you can specify a named query and execute that in order to get the attributes you desire.
From a JPA 2.1 perspective, you use #SqlResultSetMapping and #ConstructorResult.
#SqlResultSetMapping(
name = "Account.getWithTemporalAttributes",
classes = {
#ConstructorResult(
targetClass = com.company.domain.AccountTemporalDetails.class,
columns = {
#ColumnResult(name = "col1"),
#ColumnResult(name = "col2")
})
})
To use this, you would do the following:
Query query = entityManager.createNativeQuery(
"SELECT a.col1 as col1, a.col2 as col2 FROM Account a",
"Account.getWithTemporalAttributes");
List<AccountTemporalDetails> results = query.getResultList();
That should allow you to use Native SQL queries, mapping them to a POJO which you can easily then use within your application without having to write boilerplate. The #ConstructorResult annotation is meant to mimic the JPQL SELECT NEW syntax. So you would just need to make sure that AccountTemporalDetails had a constructor that takes those arguments with the right types.

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;

#JoinTable to use Imported Key instead of primary key

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.

Categories