JPA SortedMap mapping - avoid two columns with keys - jpa

I have two classes, CalculatedValue and Price. Price has map of CalculatedValue. Each CalculableValue instance has name, value and couple of other fields.
Here is mapping I use to describe a dependency between Price and the CV:
#OneToMany(
cascade = CascadeType.ALL,
fetch = FetchType.EAGER
)
#JoinColumn(name = "priceId")
private Map<String, CalculatedValue> calculatedValues =
new TreeMap<String, CalculatedValue>();
No join table, just mapping by priceId column which refers to Price unique Id.
Here is how generated table looks like:
CREATE TABLE PUBLIC.CALCULATEDVALUE (
UNIQUEID BIGINT NOT NULL,
KEY VARCHAR(2147483647) NOT NULL,
PRICEID BIGINT,
VALUE DOUBLE NOT NULL,
CALCULATEDVALUES_KEY VARCHAR(2147483647),
PRIMARY KEY (UNIQUEID)
);
ALTER TABLE PUBLIC.CALCULATEDVALUE
ADD FOREIGN KEY (PRICEID)
REFERENCES TEST.PUBLIC.PRICE (UNIQUEID);
Everything is working, but I want to know if it possible to to this:
Avoid automatic "CALCULATEDVALUES_KEY" column creation. I already have this value stored in KEY column and it would be nice to avoid duplication and somehow give a hint to JPA.
Trigger cascade delete of calculable value for each removed price (in case I'm running SQL delete statement)
Will such mapping work in case I'll use Date as a key? Not for this particular field, but for a bunch of other ones it will be useful. Assuming the same OneToMany relationship.
Thank you in advance!
PS. I'm using latest version of EclipseLink & H2 as database.
PPS. Didn't want to store the calculable values in array since I need to often find it buy key in Java.

For info on Maps see,
http://en.wikibooks.org/wiki/Java_Persistence/OneToMany
and,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Maps
and,
http://www.eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_cascadeondelete.htm#CIABIIEB
A few issues:
EclipseLink will use Hashtable by default for Map, if you want it to use TreeMap you need to define the field as TreeMap.
Do not give a #JoinColumn on a #OneToMany, this is only supported for advanced unidirectional #OneToMany, a normal #OneToMany should use a mappedBy and have an inverse #ManyToOne in the target entity. (this will fix your issue of the duplicate foreign key).
You need to specify the #MapKey for a map, otherwise it defaults to the id, which seems to be an integer here, not a string.
You can use #CascadeOnDelete in EclipseLink to cascade a delete on the database.

Related

Entity Framework on primary key and is null

I need to map some external database, I can't modify the schema. But the tables don't have primary key but rather columns like Client_ID, Calendar_ID but they are not null and entity can map them, much worse is if these columns in few tables can be null, then Entity Framework throws an error that it can't be mapped.
My question is: can I somehow disable entity tracking and map these tables without primary key and with columns as null?
Or can I use code-first approach, does it let me to create and map class with no primary key and all columns as is null?
Entity Framework must have a key field in order to work. It doesn't have to be a true primary key in the database but it needs to be unique. If you have tables that have a nullable field and no true primary key and you can't modify the database then maybe Entity Framework isn't the best fit for this project. It might work if you never try and load the entities with the null values, but it will throw errors when you do (as you have noticed).

How can I create multiple composite foreign key constraints which reference the same table?

I am using EclipseLink 2.4.0 and I am trying to find a way to generate the following DDL statements:
ALTER TABLE DELTA ADD CONSTRAINT DELTAFK1 FOREIGN KEY (APPKEY, NEWREVISION) REFERENCES REVISION (APPKEY, REVISION);
ALTER TABLE DELTA ADD CONSTRAINT DELTAFK2 FOREIGN KEY (APPKEY, OLDREVISION) REFERENCES REVISION (APPKEY, REVISION);
Each row in the DELTA table represents the changes between the two specified revisions and the primary key is made up of APPKEY, NEWREVISION, and OLDREVISION. Only the first alter statement is being generated with the following relationship annotations defined in the Delta.java class:
public class Delta {
#EmbeddedId
private DeltaPK deltaPk;
#ManyToOne
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name="appKey", referencedColumnName="appKey"),
#PrimaryKeyJoinColumn(name="newRevision", referencedColumnName="revision")
})
private Revision newRevision;
#ManyToOne
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name="appKey", referencedColumnName="appKey"),
#PrimaryKeyJoinColumn(name="oldRevision", referencedColumnName="revision")
})
private Revision oldRevision;
The name values of each PrimaryKeyJoinColumn, ‘appKey’, ‘oldRevision’, and ‘newRevision’ are all fields defined in the DeltaPK class and the referencedColumnName values are fields defined in the Revision class.
I’ve tried a bunch of variations, and the closest I’ve gotten was when I comment out the PrimaryKeyJoinColumn for ‘appKey’ for the oldRevision object. Then the second alter statement is generated, but it only includes the oldRevision value (not appKey), as you would expect. Any thoughts or suggestions on how to accomplish this would be appreciated.
I had to walk through the EclipseLink source code in the debugger for a while before I found out how to solve this one. It turns out there was one key part of the problem that I did not mention in my original question (because I didn't know it was part of the problem). The fields 'appKey' and 'revision' from the Revision class are not the primary key for that table, however, they do make up a uniqueness constraint:
#Table(
name = "REVISION",
uniqueConstraints = #UniqueConstraint(columnNames = {"appKey", "revision"})
)
It turns out that EclipseLink was generating the name of the foreign key constraint partially based on the ordering of the columnNames in this uniqueness constraint. This caused both of my foreign key constraints to be generated with the same name which ultimately led to the second one being ignored and not generated. (See the following methods in org.eclipse.persistence.tools.schemaframework.TableDefinition if you'd like to put together all the details.)
buildForeignKeyConstraint(List<String> fkFieldNames, List<String> pkFieldNames, TableDefinition targetTable, DatabasePlatform platform)
buildForeignKeyConstraintName(String tableName, String fieldName, int maximumNameLength, DatabasePlatform platform)
addForeignKeyConstraint(ForeignKeyConstraint foreignKey)
In short, when I simply reordered the values for cloumnNames like so:
#UniqueConstraint(columnNames = {"revision", "appKey"})
I got two differently named foreign key constraints (because of the two mappings between 'revision' and 'newRevison' and 'oldRevision') as shown below:
ALTER TABLE DELTA ADD CONSTRAINT DELTA_NEWREVISION FOREIGN KEY (NEWREVISION, APPKEY) REFERENCES REVISION (REVISION, APPKEY)
ALTER TABLE DELTA ADD CONSTRAINT DELTA_OLDREVISION FOREIGN KEY (OLDREVISION, APPKEY) REFERENCES REVISION (REVISION, APPKEY)
You may get slightly different output for different database platforms (I was using Derby), but I think the general issue and solution are the same. I hope I explained that clear enough to help someone else out in the future.

JPA 2 #JoinTable with keygeneration

Is there a way in JPA 2 to use a #JoinTable to generate a UUID key for the id of the row? I do not want to create new entity for this table (even if that would solve the problem) and I do not want to create it from the DB.
#ManyToMany
#JoinTable(name="Exams_Questions", schema="relation",
joinColumns = #JoinColumn(name="examId", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="questionId", referencedColumnName = "id"))
private List<Question> questions = new ArrayList<Question>();
db table
CREATE TABLE [relation].[Exams_Questions](
[id] [uniqueidentifier] PRIMARY KEY NOT NULL,
[examId] [uniqueidentifier] NOT NULL,
[questionId] [uniqueidentifier] NOT NULL,
Not sure exactly what the question is, but let me try a response.
For your first sentence alone, I would say "Yes" and "Possibly":
You'll need a separate #Entity class for the Question, and in that class you'd specify the mapping for id.
There is no way using spec JPA to specify auto-generation of a UUID value for a column. There are ways using OpenJPA and Hibernate. EclipseLink will allow you to create a custom generator for this purpose, and their example is, in fact, for a UUID.
If you'd like to expose properties of the join-table OR otherwise have JPA manage them (i.e. the id on the Exams_Questions table), then see this external link (found on this answer). You'll end up with #OneToMany relations from Exam/Question entities to the join table, and #ManyToOne relations from the join table to Exam/Question entities.
Exposing the join table as an entity will let you manage a separate key (uuid). If you don't need the uuid primary key, then don't do this - it's not necessary to solve the problem, as the examId/questionId combination is unique.

JPA: Give a name to a Foreign Key on DB?

I have a simple questions. How can I give a name to the Foreign Key relations that arise from the # ManyToOne annotation ?
With JPA 2.1 you can just do this with the foreignKey annotation:
import javax.persistence.ForeignKey;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
#ManyToOne
#JoinColumn(name = "company_id", nullable = false, foreignKey = #ForeignKey(name="FK_COMPANY__ROUTE"))
private Company company;
Do not confuse with the deprecated hibernate equivalent
As of JPA 2.1 it is possible to define foreign keys via #ForeignKey annotation.
Unfortunately, it is not very useful if you only need to change the name. If you specify custom name of the FK, you also have to specify SQL definition of the FK. That is at least the way it works in EclipseLink 2.5.0.
If you are interested in naming the column used in the foreign key, one may specify the name of the column used to create the foreign key, using the #JoinColumn annotation along with the #ManyToOne annotation. The value of the name attribute of the #JoinColumn annotation is used by the JPA provider to map the column name in the table to the entity's attribute.
However, the name of the foreign key constraint created itself cannot be configured. At the time of writing this, it is not possible to specify the name of the foreign key constraint using a JPA annotation or configuration parameter in the OR mapping files. If you need to change the name of the foreign key constraint, then you must create the DDL statement yourself, instead of relying on the JPA provider to do this.
I think #ForeignKey doesn't work with #JoinTables or I don't know how to set custom names by this, I have tried it on #JoinTable->foreignKey and #JoinColumn->foreignKey

Entity Framework Association with Non Key fields

Is it possible to create associates b/t 2 non-key fields in the Entity Framework?
Example: Take the 2 tables in a legacy application (i.e. keys/structure cannot change)
Order (
OrderId : int : PK
OrderNo : varchar
)
OrderDetails (
DetailRecordId : int : PK
OrderNo : varchar
)
In the Entity Framework, I want to create an association b/t Order and OrderDetails by the OrderNo field, which is not a primary key on either table or a FK relationship in the database.
This seems to me as not only should it be easy to do, but one reasons to use something like EF. However, it seems to only want to allow me to create associations using entity keys.
The Entity Framework allows you to claim that columns are keys and that FK constraints exist where none actually exist in the database.
That is because the SSDL (StorageModel part of the EDMX) can if necessary be manipulated by you and lie about the database.
The EF will then interact with the database as if the keys and foreign keys really do exist.
This should work, but all the normal caveats about referential integrity apply.
See my Entity Framework Tips
Hope this helps.
The problem with using non-key fields to define relationships is that the keys are not guaranteed to be properly navigatable. That could lead to a situation where you have a one to one relationship between two entities where there are more than one possible rows that fufill the relationship.
...when relating data from a database, the relationships should always be based on keys. The keys enforce the referential integrity.
One more workaround:
create view vOrder which will not include PK and create Entity from it.
Set PK in this entity to OrderNo
Now you will be able create association