Schema migration of ids and n to m relationships in objectify - java

If have a User entity which previously had a String id. I'd like to migrate to a Long id which seems simple:
public class UserEntity {
/*#Id*/
#Index String oldId;
#Id Long newId;
/* other indexed fields I use for loading the entity */
private List<Ref<ReferencedEntity>> collections;
}
public class ReferencedEntity {
private List<Ref<User>> owners;
}
Since I load the user via different fields I can check if the user has a null newId and if so just null the old one save it back so the auto generator will set a new Id in the newId field.
The problem is now my n to m relationship to other entities. How should I migrate those? I have a Ref on both sides so I guess I just can load the refs on the user entity side and replace the other side with the new Id.
The general question is how to migrate n to m relationship if one side needs a new id?

You are probably thinking with a relational database strategy. When you update a value on one side, the other side will not be updated, therefore you have to update both entities. Since this is NoSql you have to think differently.
I would take this strategy.
First load the "old" user entity and save it again in the new structure. Once you have confirmed that all the data you loaded has been converted to the new object (I suggest using BigQuery), then you should spawn a task for each referenced entity using the indexed oldId and update the reference of the owners in the ReferencedEntity.
It will take a while to load but it is probably a safe way to do it.

Related

How to maintain unique records in DB table with multiple instances of SpringBoot application

I have a scheduler which polls data in every 4 hours and inserts into table based upon certain logic.
I have also used #Transactional annotation and also I am checking every time whether data already exists in the table or not.
If the record does not exist, it will insert. When I am multiple instances of SpringBoot application, each instance runs the scheduler and some data not all get duplicated.
It means I found that table contains duplicate record. The table where I am inserting is an existing table of the application and few columns have not been defined with unique
constraints. Please suggest me how I can maintain unique records in the database table even if scheduler runs from multiple instances. I am using Postgresql and SpringBoot.
So, the direct answer to you question is to have unique identifier for each record in your table. Unique id from external API will be a perfect match. If you don't have one - you can calculate it manually.
Consider an example:
#Entity #Table
public class Person {
private String fieldOne;
private String fieldTwo;
#Column(unique=true)
private String uniqueId;
//in case you have to generate uniqueId manually
public static Person fromExternalApi(String fieldOne, String fieldTwo) {
Person person = new Person();
person.fieldOne = fieldOne;
person.fieldTwo = fieldTwo;
person.uniqueId = fieldOne + fieldTwo;
}
}
Then you will have unique index on DB side based on uniqueId field, and DB prevent you from duplicates.
Important - you can't use
#GeneratedValue(strategy = GenerationType.IDENTITY)
because it will generate new id each time you save object to DB. In you case you can save same object multiple times from different instances.
But in you case I would suggest another solution. The idea is to run scheduled task only once. And this is already answered here

ID of java objects not synchronizing with database

The only link I found that's close to what I am experiencing is this one :
How do you synchronize the id of a java object to its associated db row?
and there's not much of a solution in it.
My problem is that my Java objects aren't updated after being added to my database despite the .commit()
em.getTransaction().begin();
System.out.println(eleve.getID());
em.persist(eleve);
em.getTransaction().commit();
System.out.println(eleve.getID());
which refers to this class
public class Eleve {
private String _nom;
private String _prenom;
private float _ptsMerite;
#Id
private int _IDEleve;
and yields this output :
0
0
I think I've done everything properly when it comes to the persistence since it does create the object in the database (mySQL) with correct ID's which I've set to be autoincrement.
I am using javax.persistence for everything (annotations and such).
Did you try to add the #GeneratedValue annotation at your ID field?
There are four possible strategies you can choose from:
GenerationType.AUTO: The JPA provider will choose an appropriate strategy for the underlying database.
GenerationType.IDENTITY: Relies on a auto-increment column in your database.
GenerationType.SEQUENCE: Relies on a database sequence
GenerationType.TABLE: Uses a generator table in the database.
More info: https://www.baeldung.com/jpa-strategies-when-set-primary-key
If you ever change to a more powerful framework it is likely that this manages your transactions (CMT) so you can't (or don't want) commit everytime you want to access the ID for a new entity. In these cases you can use EntityManager#flush to synchronize Entity Manager with database.

One to one relationship in hibernate

I am implementing one to one (one Employee<-> one Mobile) relationship in hibernate as follows. This code works fine, but as this is one to one relationship, assigning same mobile number to emp1 and emp2 should have created problem (it violates relationship) but code is accepting and adding 2 emps with same mobile (Confirmed from Database tables). Why is hibernates one to one relationship like one mobile<->many employees?
My Code:
#Entity
public class Employee {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#OneToOne
private Mobile mobile;
//...rest of the code
}
#Entity
public class Mobile {
#Id #GeneratedValue
private int id;
private long number;
//...rest of the code
}
Test Client main(...){
Mobile mobile = new Mobile(9999999999L);
Employee emp1 = new Employee("Raja");
Employee emp2 = new Employee("Raja");
emp1.setMobile(mobile);
emp2.setMobile(mobile);// VIOLATING 1-1 RELATIONSHIP
//...REST OF THE COMMON CODE
session.save(mobile);
session.save(emp1);
session.save(emp2);
session.getTransaction().commit();
}
DATABASE SHOWS BOTH EMP RECORDS WITH SAME MOBILE NUMBER (VIOLATION OF 1-1)
For one to one relations, you should always make sure that you have a unique constraint on your database (either generated by hibernate or manually created).
Hibernate won't check it because it would require to collect extra data every time to do the check. The database can do it more efficiently.
To do the check, hibernate would have to do an extra query. And if the database is configured correctly that extra query would cost time and resources without any gain.
If you don't have unique constraints and you define the relation bidirectional, you can get even more trouble.
Hibernate will save the conflicting records without complaining as you already discovered. And it would become impossible for hibernate to use the relation starting from the object on the other side (getting the Employee via the Mobile in your case). If mobile would be configured to get it's related employee eagerly, it would become impossible to get the mobile in memory after both employee's where saved.

Safe embedded entity with objectify

I have two entities.
#Entity
public class Recipe {
#Id
private Long id;
private List<Step> steps;
}
#Entity
public class Step {
#Id
private Long id;
private String instruction;
}
And the following Clound Endpoint
#ApiMethod(
name = "insert",
path = "recipe",
httpMethod = ApiMethod.HttpMethod.POST)
public Recipe insert(Recipe recipe) {
ofy().save().entities(recipe.getSteps()).now(); //superfluous?
ofy().save().entity(recipe).now();
logger.info("Created Recipe with ID: " + recipe.getId());
return ofy().load().entity(recipe).now();
}
I'm wondering how do I skip the step where I have to save the emebedded entity first. The Id of neither entity is set. I want objectify to automatically create those. But if don't save the embedded entity I get an exception.
com.googlecode.objectify.SaveException: Error saving com.devmoon.meadule.backend.entities.Recipe#59e4ff19: You cannot create a Key for an object with a null #Id. Object was com.devmoon.meadule.backend.entities.Step#589a3afb
Since my object structure will get a lot more complex, I need to find a way to skip this manual step.
I presume you are trying to create real embedded objects, not separate objects stored in the datastore and linked. Your extra save() is actually saving separate entities. You don't want that.
You have two options:
Don't give your embedded object an id. Don't give it #Entity and don't give it an id field (or at least eliminate #Id). It's just a POJO. 90% of the time, this is what people want with embedded objects.
Allocate the id yourself with the allocator, typically in your (non-default) constructor.
Assuming you want a true embedded entity with a real key, #2 is probably what you should use. Keep in mind that this key is somewhat whimsical since you can't actually load it; only the container object can be looked up in the datastore.
I suggest going one step further and never use automatic id generation for any entities ever. Always use the allocator in the (non-default) constructor of your entities. This ensures that entities always have a valid, stable id. If you always allocate the id before a transaction start, it fixes duplicate entities that can be created when a transaction gets retried. Populating null ids is just a bad idea all around and really should not have been added to GAE.
The concept of the embedded is that the embedded content is persisted inside the main entity.
Is this the behaviour you are trying to configure?
The default behaviour of a Collection (List) of #Entity annoted class is to refer them instead of embed them. As you current configuration, the List<Step> variable does not have any annotation to override the default configuration, which is a different entity related to another one.
The error you are getting is because Objectify, when it saves the recipe entity, is trying to get the key of each step to create the relationship (and save them in the recipe entity), but if the entity step is not saved yet on the datastore, does not have a key
If you are trying to persist the steps inside the recipe entity, you need to setup objectify like this
#Entity
public class Recipe {
#Id
private Long id;
private List<Step> steps;
}
public class Step {
private Long id;
private String instruction;
}
As you can see, I removed the #Id annotation (an embedded Entity does not require an ID because is inside another entity) and the #Entity from the Step class. With this configuration, Objectify save the step entities inside the recipe entity
Source: https://code.google.com/p/objectify-appengine/wiki/Entities#Embedded_Object_Native_Representation

Mapping one DB column to two seperate fields using JPA

I'm developing a code generator that have to generate JPA entities from database meta-model files. These model are from home-brewed modeling system which are being used to generate models other than JPA entities.
In these models some fields are mapping back to same database column. But it seems like JPA does not like that very much. When I try to run generated code I get
Exception [EclipseLink-48] (Eclipse Persistence Services - 2.6.0.v20140809-296a69f): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: Multiple writable mappings exist for the field [FACT_INVENT_TRANS_HIST_DM.TRANSACTION_ID]. Only one may be defined as writable, all others must be specified read-only.
Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[TransactionIdKey-->FACT_INVENT_TRANS_HIST_DM.TRANSACTION_ID]
Descriptor: RelationalDescriptor(InventTransHistFactDM --> [DatabaseTable(FACT_INVENT_TRANS_HIST_DM)])
As I can't change the models only option left is to make one of those fields read-only. And the JPA entities being generated are only used to read data from database it will not used for writing data. Is there a way to mark some fields as read only or tell EclipseLink that these entities are read only so it does not have to worry about the multiple writable mapping.
I tried using EclipseLink's #ReadOnly annotation in all entities but it did not help this issue.
There is no #ReadOnly in JPA.
There are however attributes "insertable"/"updatable" that you can set against a field via #Column to effectively do the same.
The question may be almost 6 years old, but it's still being found today, so I'd like to address another option:
public class Foobar {
#OneToOne
#JoinColumn(name="SELF_COLUMN_FOO", referencedColumnName = "FOREIGN_COLUMN_TO_JOIN")
public Foo foo;
#OneToOne
#JoinColumn(name="SELF_COLUMN_BAR", referencedColumnName = "FOREIGN_COLUMN_TO_JOIN")
public Bar bar;
}
This can be used where SELF_COLUMN is obviously the relevant column in the Foobar table, and FOREIGN_COLUMN_TO_JOIN would be single key in the other table you wish to join.
This will be useful where you want to have two (or more) attributes in a single class, but only one column to join on the foreign DB table. For example: An Employee may have a home phone number, cell number, and a work phone number. All are mapped to different attributes in the class, but on the database there's a single table of phone numbers and id's, and an identifier column, say VARCHAR(1) with 'H' or 'W' or 'C'. The real example would then be...
Tables:
PHONENUMBERS
PHONENUMBER_ID,
ACTUAL_NUMBER
EMPLOYEE
ID
HOMENUMBER VARCHAR(12),
CELLNUMBER VARCHAR(12),
WORKNUMBER VARCHAR(12)
public class Employee {
#OneToOne
#JoinColumn(name="HOMENUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone homeNum;
#OneToOne
#JoinColumn(name="CELLNUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone cellNum;
#OneToOne
#JoinColumn(name="WORKNUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone workNum;
}
As you can see, this would require multiple columns on the Entity's table, but allows you to reference a foreign key multiple times without throwing the 'Multiple writable mappings exist...' that you showed above. Not a perfect solve, but helpful for those encountering the same problem.

Categories

Resources