I'm wondering if I can change a value of a primary key member of a composite primary key in a Grails Domain class? For example having this domain:
class StudentHistory implements Serializable {
String studentNumber
String schoolYear
Integer yearLevel
String section
Float average
String status
static mapping = {
...
id composite: ["studentNumber", "schoolYear", "yearLevel", "section"]
...
}
}
Let say, On the schoolYear: "2014-2015", a certain yearLevel: 1 student with studentNumber: "2011-488-MN-0" transferred section from section: "1D" to section: "1N". Now to perform this record update, we do something similar inside a service:
StudentHistory record = StudentHistory.find {
eq("studentNumber", "2014-488-MN-0")
eq("schoolYear", "2014-2015")
eq("yearLevel", 1)
eq("section", "1D")
}
record.setSection("1N")
record.save(flush: true, insert: false)
The problem is that the update on the primary key doesn't take effect but when I tried to update other non-Primary fields such as average and status, updating them works fine (I tried performing an SQL directly on the database to confirm). How can I update primary keys?
PS: Now, based on this design, I know some will suggest that why not just create another record, then just fetch the record that has been last entered? But what I am required to do is to update that composite primary key instead.
PPS: Please don't suggest on removing the old instance, and create a new one, copying the old details except for the section. I cannot do that since many tables are connected to this table.
I believe it is a good practice to avoid changing primary keys. Primary key is a unique identifier of an object and changing it effectively means creating a new object. So if your composite primary key is mutable (or can mutate) then you should use a surrogate key - an artificial primary key. At the same time you can create a unique constraint on the 4 fields currently being your primary key.
In your case it would be:
static mapping = {
...
}
static constraints = {
studentNumber(unique: ["schoolYear", "yearLevel", "section"])
}
Hope it makes sense.
Related
I am making a KMM app using SQLDelight for the cache and recently I changed my database entities to use Text(String) for the id field instead of Int, now i am getting an error when inserting, I might just be missing some sqlDelight knowledge
here is my table:
CREATE TABLE sidework_Entity(
id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
employees TEXT NOT NULL,
todoToday INTEGER AS Boolean DEFAULT 0
);
here is my insert method:
insertSidework:
INSERT OR REPLACE
INTO sidework_Entity(
id,
name,
employees,
todoToday
) VALUES (?,?,?,?);
here is my error:
statement aborts at 5: [INSERT OR REPLACE
INTO sidework_Entity(
id,
name,
employees,
todoToday
) VALUES (?,?,?,?)] datatype mismatch
I think it is most likely the Primary Key i have set on the id field or something of that sort but the documentation is a bit short.
The solution to this problem is actually running a migration. Although deleting the app to clear the database cache is a valid solution for testing purposes. For an app in production, this isn't the correct approach.
Documentation for running Migration
Stackoverflow answer with explanation
I have noticed that setting PK is not obligatory in Realm and simply can be omitted. But in documentation is stated that:
Indexes are created automatically for primary key properties.
And I'd like to clear up some questions:
1) What is the default value for PK is defined by Realm, if I don't assign it by myself. Is it hash or whatever ? (If I don't set PK and call [MyRealmObject primaryKey] it returns nil)
2) If this implicit PK is indexed by default ? Should I worry about it, because if it is not indexed, does it mean that it affects the general performance of this Entity (for example,fetching objects) ?
3) Is it a good practice to define PK every time for each RLMObject subclass or it isn't necessary for Realm and simply may rely on it's internal realization defined by Realm itself?
(Disclaimer: I work for Realm.)
Yep! Setting a primary key in Realm isn't obligatory, nor necessary, which is why it's completely up to the developer and the requirements of the app to determine whether it's necessary or not in their implementation.
In response to your questions:
1) There are no default values; you specify one of your own properties as a primary key. primaryKey returns nil by default since you need to override that yourself in order to indicate to Realm which property you want to act as a primary key. Some users have set integers as primary keys, but more often than not, using a UUID string is the most common.
2) There's no implicit primary key. You must use the [RLMObject primaryKey] method to explicitly state which property is the primary key, and THEN it will be indexed. :)
3) In my own (spare-time) development experience, I usually find having a primary key makes it a lot easier to identify and handle specific objects. For example, if you're passing an object across threads, you can simply pass the primary key value and use [RLMObject objectForPrimaryKey:] to refetch the object. Obviously this depends on your own implementation requirements. You probably shouldn't add a primary key unless you find out you really need one.
As an example, here's what you would add to your RLMObject subclass if you wanted to set a UUID string as a primary key:
#interface MyObject : RLMObject
#property NSString *uuid;
#end
#implementation MyObject
+ (NSString *)primaryKey
{
return #"uuid";
}
+ (NSDictionary *)defaultPropertyValues
{
#{#"uuid": [[NSUUID UUID] UUIDString]};
}
#end
I hope that helped!
Addendum: To elaborate upon some of the comments made below, primary keys are explicitly necessary for any Realm APIs that change their functionality depending on if an object with the same key already exists in the database. For example +[RLMObject createOrUpdateInRealm:] will add a new Realm object to the database if an object with that primary key doesn't already exist, and will simply update the existing object otherwise.
As such, in these instances where a primary key is a critical component of the subsequent logic, they are required. However, since these APIs are a subset of the different ways in which it is possible to add/update data in Realm, if you choose to not use them, you still not required to have a primary key.
The horse has been beaten to death already, but I couldn't help but reference the Realm code which throws an exception if a Realm Object is created or updated without having a primary key.
+ (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withValue:(id)value {
// verify primary key
RLMObjectSchema *schema = [self sharedSchema];
if (!schema.primaryKeyProperty) {
NSString *reason = [NSString stringWithFormat:#"'%#' does not have a primary key and can not be updated", schema.className];
#throw [NSException exceptionWithName:#"RLMExecption" reason:reason userInfo:nil];
}
return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, true);
}
I'm feeling a little slow today. I'm trying to do something that I think is very simple. I have a Domain class with a property called 'name'. I want 'name' to have an index, and I want the index to require that the 'name' is unique. I've set the unique constraint and tried creating an index. I can't make sense out of the Gorm docs as to how I add the unique attribute to the index. Here's some code:
class Project {
String name
static hasMany = [things:Things]
static mapping = {
name index:'name_idx'
}
static constraints = {
name(unique:true)
}
}
All is well with the above, except when do "show indexes from project" in mysql it shows my name key as not unique. I know the problem is that I am not specifying unique in the mapping, but quite frankly the docs for gorm are making my head hurt. I see all kinds of stuff about columns, but I can't find a single example anywhere on the web that shows what I want to do. I don't need complex mappings or compound keys, I just want to know the syntax to add the unique attribute to the mapping declaration above. Any advice welcome.
I also did a grails export-schema and see the following:
create index name_idx on project (name);
Nothing in that to indicate this index requires unique values
A related followup question would be once I succeed in making that index unique, what type of error should I expect when I go to save a Project instance and the name is not unique? Is there a specific exception thrown? I realize that even if I check that a given 'name' is unique there's still a possibility that by the time I save it there may be a row with that name.
I'm quite sure the syntax to do what I want is simple but I just can't find a simple example to educate myself with. I've been to this page but it doesn't explain HOW the uniqueness is enforced. I'd like to enforce it at the name index level.
The indexColumn allows additional options to be configured. This may be what you're looking for.
static mapping = {
name indexColumn:[name:'name_idx', unique:true]
}
Grails Documentation for indexColumn
If you put only the unique constraint the GORM send DDL to create an unique index on database.
static constraints = {
name nullable: false, unique: true
}
I have successfully used EF4 to insert rows automatically with a server generated GUID:
http://leedumond.com/blog/using-a-guid-as-an-entitykey-in-entity-framework-4/
Now how does one perform this task if there exists a RowID (guid) and ParentRowID (guid) with a primary-foreign key constraint between the two? What would I set .ParentRowID to?
NewCut = New Row With
{
.ParentRowID = .RowID
}
SaveChanges throws a fit every time.
The fact that the primary key is a GUID is in fact irrelevant because I attempted the same test using a standard autogenerated integer without success.
The solution is as simple as the one you have posted.
Just create both parent and child entities in code, do not set Entity Key and not forget to set StoreGeneratedPattern for all server-generated Guid columns.
Then perform either MasterEntityInstance.Children.Add(ChildEntityInstance), or ChildEntityInstance.MasterEntity = MasTerEntityInstance and call SaveChanges.
After the SaveChanges call both guid properties will be populated with the correct Guid values, and ChildEntity.MasterEntity Entity Key will be populated with the necessary MasterEntity Entity Key value.
I created simple domain class with map within it.
class Foo {
Map bar
}
Bar mapping will be created as sth like:
create table foo_bar (bar bigint, bar_idx varchar(255),
bar_elt varchar(255) not null);
...as stated in http://www.grails.org/GORM+-+Collection+Types:
The static hasMany property defines
the type of the elements within the
Map. The keys for the map MUST be
strings.
Now my question is - is it possible to create map of values other than Strings? I can achieve that using pure Hibernate (element mapping) - any ideas how to port this to Grails?
I think you meant if it's possible to create map of KEYS other than Strings.
It is not possible: all keys must be Strings, while values can be whatever type you want.
A way to achieve what you want is using some unique identifier for the type of class you want as key of your map.
Say you want a Map persisted in your database and say you have two instances: objectA and objectB you want to persist in your map, which name is "relationship":
relationship."objectA.toString()" = objectB
That should work. Changet toString() with hashCode(), getId() or whatever thing that gives you a unique String that identifies that object and only that, and you got it.