Changing primary key generation with ManyToManyMapping - jpa

I am trying to find a work-around for the EclipseLink bug bugs.eclipse.org/bugs/show_bug.cgi?id=344448.
The issue is that EclipseLink chooses a primary key made of entity ID + hashmap value ID for each entry of the HashMap. I would like to modify to have the primary key of entity ID + hashmap key ID instead. This is not only an issue with the primary key constraint but also with the SQL statements generated by EclipseLink, eg delete, update, etc...
So... I saw there is a way to customise this behaviour using a Customiser and by grabbing the ManyToManyMapping of the hashmap field and modifying whatever is needed. I can't find much documentation on this so I am sort of trying out things... but so far no success.
public class ConfigureValuesFilter implements DescriptorCustomizer {
public void customize(ClassDescriptor descriptor) throws Exception {
ManyToManyMapping mapping = (ManyToManyMapping) descriptor
.getMappingForAttributeName("values");
// various attempts
}
I tried setting the primary key with mapping.setTargetRelationKeyFieldName ("value_KEY"); and the setTargetKeyFieldNames (why does it need both?) but this resulted in an array index out of bound error with the table (from the key) I want to use not available in the descriptor, see below.
Exception Description: The table [CASEVALUE] is not present in this descriptor.
Descriptor: RelationalDescriptor(testdb.EULADocument --> [DatabaseTable(EULADOCUMENT)])
Then I tried changing the delete query statement with setDeleteSQLString, but I am stuck on how to pass the parameter values
mapping.setDeleteSQLString("DELETE FROM LICENSE_EULADOCUMENT WHERE ((values_KEY = #VALUES_KEY) AND (License_ID = #LICENSE_ID))");
says it could not find the VALUES_KEY so sets it to NULL...
What is the recommended way to modify the behaviour?

Related

JPA: generate non pk unique and random alphanumeric value

I want to uniquely identity an entity without using the primary key. So I thought about generating an unique and random value. Moreover, value must be easy to read / manually copy and is expected to be 6 or 7 characters long.
Design
My entity A:
public class A{
// ...
#Column(name="value", unique=true, nullable=false, insertable=false, updatable=false)
private String value;
// ...
public String getValue(){
return value;
}
protected void setValue(String value){
this.value = value;
}
}
represented in the database by the table
CREATE TABLE IF NOT EXISTS schema.mytable{
-- ...
value TEXT NOT NULL DEFAULT generate_unique_value_for_mytable(),
-- ...
CONSTRAINT "un_value" UNIQUE (value),
-- ...
}
I thought letting the database handling this and then fetch the value...
Problem
With the current design, value is correctly generated in the database but when JPA fetches A entities, value field is empty.
I cannot remove insertable=false otherwise, it will hit against the NOT NULL constraint
If I remove insertable=false and I put some dummy data, the data overrides the value generated by generate_unique_value_for_mytable()
If I remove everything in the Column annotation, I can save the A entity but value is still empty
Ugly solution
I couldn't find a proof but it looks like having the database generating a value is a bad idea. I do have the same problem for a non-primary key field which is generated by a sequence: I cannot fetch the value from the database.
So my ugly solution is to decorate the create() method of the EJB responsible for A entities:
public class Aejb{
public void create(A entity){
// method kind of ensures randomness
String value = MyUtil.generateRandomValue();
A isThereAnyoneHere = findByValue(value);
while(isThereAnyoneHere != null){
String value = MyUtil.generateRandomValue();
isThereAnyoneHere = findByValue(value);
}
// unicity is ensured
entity.setValue(value);
em.persist(entity);
}
}
Questions
Can I fetch a non-primary key value generated by the database from a JPA entity? Value can be generated by a function or a sequence.
Is there a more elegant solution than my ugly workaround to provide an unique and random value?
Yes.You haven't mentioned your database, but it is possible for
Oracle to return the value inserted via triggers, and have
Eclipselink obtain this value in your model - see
https://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_returninsert.htm
Set the value using a #PrePersist method that will get executed
before the entity is inserted, but if you are relying on one or more database queries, you will run into performance issues, as inserting a new A will be expensive. You might instead just insert the random value and deal with the occasional conflict, and pick some random that has less chance of overlaps, like a UUID.
If I understand correctly, #Generated annotation should do the trick. This annotation sets the value from database DEFAULT field value.
Example:
#Generated(GenerationTime.INSERT)
#Column(name="value", unique=true, nullable=false, insertable=false, updatable=false)
private String value;
However there is a drawback: if you decide to set value of your field in Java, it would be overwritten by Hibernate using the result from DEFAULT in your database.
Self-answer to mark question as closed
Final solution
We finally went for a combination of
Stored procedures: the database will generate the value. The procedure also ensures that the value is unique across the table
Named queries: to fetch the generated value by the procedure. I did not use NamedStoredProcedures because we are using PostgreSQL and PostgreSQL JDBC driver did not support name parameters which raised some problems.
With this configuration, the EJB is sure to have at most one database call to fetch the requested value.
Response to other answers
Here is a summary of the other answers feedback for self-reference and next readers:
Oracle trigger: we're using PostgreSQL :(
UUID: We had the constraint of having our unique and random code human-readable. An end-user is assumed to be able to manually rewrite it. Consequently, we could not have a long String such as an UUID.
PrePersist: Other business actions take place after the code generation in the same transaction which means that those actions need to be redone in case of collision. I'm not very confident about managing JPA exception (transaction scope and so on) so I preferred not to play with it.
#Generated: This is a Hibernate specific feature. We're using EclipseLink
Database Trigger: If code were purely generated at database level, I encountered the same problems of not fetching the value: the value is properly generated as database level but the entity will have the value as null

Primary key update in Realm migration

I have several cases where I have to update some object models, including the property that I use as a primary key.
For example :
Merge the primary key name (e.g. Georges, Anna...), and the familyName (e.g. Johnson, Smith...) property, and use that new merged name (e.g. Georges Johnson...) as a primary key.
Make the identifier primary key from type Int to type String
But of course the documentation clearly states that :
Once an object with a primary key is added to a Realm, the primary key cannot be changed
You can always remove old objects and create new ones, but this would add a lot of complexity to re-create the relationships.
And I'm pretty sure realm may not be happy with the identifier type change either way (judging by the thrown exceptions that I encountered).
So I was wondering if there was a simpler way to do so, or if I had to do a lot of manual grunt work to achieve my very simple goals.
Katsumi from Realm here. Realm supports primary key migration.
Primary keys can be changed only during migration. So you'd like to change the existing primary key values, you can write migration block, then you can assign new values for each new objects. The values must be unique of course.
let config = Realm.Configuration(schemaVersion: 1, migrationBlock: { (migration, schemaVersion) in
migration.enumerateObjects(ofType: "Person", { (oldObject, newObject) in
newObject!["key"] = ...
})
})
You can change primary key type as well.
In that case, you also need to write a migration block and assign new values. Because the primary key property is cleared when changing the type. Also, you can merge or split existing primary key property. You can add a new property, then specify it as a primary key, then you also should write migration block and assign new unique values as well.
However, the latest version of Realm (2.8.0 and 2.8.1) made unintentional bug that doesn't allow to modify primary key even during migration. So if you're urgent, you should use the previous version (2.7.x), if you are not urgent, please wait to be fixed the bug in next release.

Auto generating a String non-primary key value using jpa

Kindly help me to find out a solution to my problem.
I have a property of type String in my entity which is not primary key.
This would act as system generated unique profile id for users in my project.
I want to auto generate this value like a random string of particular size.
Could you please help me to arrive at a solution.
I have tried the #Generator and custom id generator class. but still null is getting inserted to the field in db.
I am using maria db.
Hi you can use the hibernate events.
#PostPersist Executed after the entity manager persist operation is actually executed or cascaded. This call is invoked after the database INSERT is executed.
Starting with Java 5, the UUID class provides a simple means for generating unique ids. The identifiers generated by UUID are actually universally unique identifiers.
Please follow the sample example.
public class TestModel {
#Column("column_name")
private String uid;
//setter and getter for uid
#PrePersist
protected void onCreate() {
// set the uid
setUid(java.util.UUID.randomUUID());
}
}
Please find the below link for more information on #PrePersist.
https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/listeners.html

Setting Entity Framework Fields

I generated a Members table and a MembersType table which has a primary key which links to the Type foreign key in the Members table. The MembersType table is literally just 3 records, so that each Member can be of MemberType 1, 2 or 3.
Now the problem is that when Entity Framework generates the data layer and objects for the Members object, it creates a MemberType object in the Members object, but all I want to be able to do when setting it is:
Members.MemberType = 1;
but because of the above, I have to do this:
MemberTypes = db.MemberTypes.Where(x => x.MemberTypeId == 1).AsQueryable().First()
Is there anyway to stop it from generating an object on foreign keys so I can just set it as an int? Surely this is more quicker and resource efficient than querying the type table everytime too.
You have encountered one of everyone's least favorite features of EFv1. The problem is that everything is an Entity, so you can't get to foreign key values as primitives.
Your code sample shows how it has to be done in EFv1. The best you can do is cache those enum values up front so you don't have to keep getting them from the context. EFv4 does away with this restriction with "FK Properties," which is just a fancy way of saying raw foreign keys you can set directly.

Server-generated keys and server-generated values are not supported by SQL Server Compact

I just started to play with the entity framework, so I decided to connect it to my existing SQL Server CE database. I have a table with an IDENTITY(1, 1) primary key but when I tried to add an entity, I've got the above-mentioned error.
From MS Technet artice I learned that
SQL Server Compact does not support entities with server-generated keys or values when it is used with the Entity Framework.
When using the Entity Framework, an entity’s keys may be marked as server generated. This enables the database to generate a value for the key on insertion or entity creation. Additionally, zero or more properties of an entity may be marked as server-generated values. For more information, see the Store Generated Pattern topic in the Entity Framework documentation.
SQL Server Compact does not support entities with server-generated keys or values when it is used with the Entity Framework, although the Entity Framework allows you to define entity types with server-generated keys or values. Data manipulation operation on an entity that has server-generated values throws a "Not supported" exception.
So now I have a few questions:
Why would you mark key as server-generated if it is not supported and will throw an exception? It's hard to make sence from the quoted paragraph.
When I've tried to add StoreGeneratedPattern="Identity" to my entity's property, Studio complained that it is not allowed. What I'm doing wrong?
What is the best workaround for this limitation (including switching to another DB)? My limitations are zero-installation and using entity framework.
When I hit this limitation, I changed the type to uniqueidentifier
Use uniqueidentifier or generate a bigint/int key value manually is your best option.
Something like this perhaps ...
private static object lockObject = new object();
private static long nextID = -1;
public static long GetNextID()
{
lock (lockObject)
{
if (nextID == -1) nextID = DateTime.UtcNow.Ticks; else nextID++;
return nextID;
}
}
This assumes that you don't generate more than one record per tick during an application run (plus the time to stop and restart). This is a reasonable assumption I believe, but if you want a totally bullet proof (but more complex) solution, go read the highest ID from the database and increment from that.
SQL CE version 4.0 fixed this problem with its Entity Framework provider.
I just hit this issue too... mostlytech's answer is probably the best option, GUIDs are very easy to use and the risk of key collision is very low (although not inexistant).
Why would you mark key as server-generated if it is not supported and will throw an exception? It's hard to make sence from the quoted paragraph.
Because SQL Server (not Compact) supports it, and other third parties may support it too... Entity Framework is not only for SQL Server Compact ;)
In my case, all of my classes have the primary key named "ID"
I created an interface
public class IID
{
public Int32 ID { get; set; }
}
Then I create an extension method
public static Int32 GetNextID<T>(this ObjectSet<T> objects)
where T : class, IID
{
T entry = objects.OrderByDescending(u => u.ID).FirstOrDefault();
if (entry == default(T))
return 1;
return entry.ID + 1;
}
Then when I need a new ID, I just do this:
MyObject myobj = new MyObject();
myobj.ID = entities.MyTable.GetNextID();
the other option is to use SqlCeResultSet on the tables that have the identity column.
i have a primary key named ID with data type of INT32 and have Identity Column
Just do this
MyEntity Entity = new MyEntity();
String Command;
command = "Insert into Message(Created,Message,MsgType)values('12/1/2014','Hello World',5);
Entity.ExecuteStoreCommand(command);
--Exclude the primary key in the insert Statement
--Since the SQLCE do not support system generated keys
--Do not use LINQ because it supplies a default value to 0 for Primary keys that has a
data type of INT