How to reference a composite primary key in GORM? - go-gorm

Golang's GORM library supports composite primary keys. But how to reference them from related models?
For example, suppose I have a User and a Note model:
type User struct {
OrganizationID uint `gorm:"primaryKey; not null"`
Name string `gorm:"primaryKey; not null"`
}
type Note struct {
ID uint `gorm:"primaryKey; not null"`
OrganizationID uint `gorm:"not null"`
UserName string `gorm:"not null"`
User User
}
The auto migrator creates the notes table like this, which fails:
CREATE TABLE "notes" ("id" bigserial NOT NULL,"user_name" text NOT NULL,"organization_id" bigint NOT NULL,PRIMARY KEY ("id"),
CONSTRAINT "fk_notes_user" FOREIGN KEY ("user_name") REFERENCES "users"("name"))
But I want it to do this instead:
CONSTRAINT "fk_notes_user" FOREIGN KEY ("user_name", "organization_id") REFERENCES "users"("name", "organization_id")
How can I accomplish this?

You can use ForeignKey and References tags. They are mentioned in the docs, although in the reversed (One-to-Many) context.
type User struct {
OrganizationID uint `gorm:"primaryKey; not null"`
Name string `gorm:"primaryKey; not null"`
}
type Note struct {
ID uint `gorm:"primaryKey; not null"`
OrganizationID uint `gorm:"not null"`
UserName string `gorm:"not null"`
User User `gorm:"ForeignKey:OrganizationID,UserName;References:OrganizationID,Name"`
}
AutoMigrate will generate the following sql:
CREATE TABLE `users` (`organization_id` integer NOT NULL,`name` text NOT NULL,PRIMARY KEY (`organization_id`,`name`))
CREATE TABLE `notes` (`id` integer NOT NULL,`organization_id` integer NOT NULL,`user_name` text NOT NULL,PRIMARY KEY (`id`),CONSTRAINT `fk_notes_user` FOREIGN KEY (`organization_id`,`user_name`) REFERENCES `users`(`organization_id`,`name`))

Related

How can we assign a field of user table say id that is primary key, to another table as a primary key? using hibernate

See I dont want any mapping but what I want is just a simple thing that is....
I have two Entity
Tasks(taskid,taskname.....,userId)
User_Credentials(userId,password)
So What I want is userId of User Credentials used as a foreign key in Task table.
So when I fire an api to save the task I have to also pass the all details of user Credentials That I dont want.
{
"taskName": "Test",
"taskStatus": "Open",
"taskDueDate": "2022-04-15",
"taskCreatedDate": "2022-04-10",
"taskDescription": "Demo",
"taskPriority": "High",
"isTaskActive": "Yes",
"userCredentials":{
"associateId":"108",
"password":"something#9122"
}
}
What I want is
{
"taskName": "Test",
"taskStatus": "Open",
"taskDueDate": "2022-04-15",
"taskCreatedDate": "2022-04-10",
"taskDescription": "Demo",
"taskPriority": "High",
"isTaskActive": "Yes",
"userCredentials":"108"
}
That I want to post and if userCredentials id 108 is not in User Credentials Table so it pop up an error and if it is there it will save just like a foreign key concept.
So please tell me ho to do that.
You can use ElementCollection to achieve this.
In Tasks entity
#Id
#Column(name = "task_id")
private Integer id;
#ElementCollection
#CollectionTable(name = "task_user", joinColumns = #JoinColumn(name = "task_id", nullable = false))
#Column(name = "user_id", nullable = false)
private List<Integer> userCredentials;
In UserCredentials entity
#Id
#Column(name = "user_id")
private Integer id;
UserCredentials is an independent entity with no mapping with Tasks. You will now be able to pass UserCredentials like this userCredentials: [108] in the Tasks.
Add the following in your application.yml to generate the create.sql script with ddl commands for all the entities. ddl-auto: validate will restrict hibernate from creating the tables on its own.
spring:
jpa:
hibernate:
ddl-auto: validate
properties:
javax:
persistence:
schema-generation:
create-source: metadata
scripts:
action: create
create-target: create.sql
generate-ddl: true
The following is the ddl command to add foreign key constraints for tables user_credentials and tasks. Execute it alongwith the create.sql in any DB client.
CREATE TABLE "task_user"
( "user_id" integer ,
"task_id" integer,
PRIMARY KEY ("user_id","task_id"),
CONSTRAINT user_id_fkey
FOREIGN KEY ("user_id") REFERENCES "user_credentials" ("user_id"),
CONSTRAINT task_id_fkey
FOREIGN KEY ("task_id") REFERENCES "tasks" ("task_id")
);
Note: The create.sql already has a task_user create command but it needs to be replaced with the above command.

Can't make two 1:1 relations in one model in Prisma. Ambiguous relation detected

I'm trying to make two 1:1 relations in one model in Prisma ORM, but got following error:
Error validating model "Person": Ambiguous relation detected. The fields placeOfBirth and placeOfDeath in model Person both refer to Place. Please provide different relation names for them by adding #relation(<name>).
My prisma schema:
model Place {
id Int #id #default(autoincrement())
name String
persons Person[]
}
model Person {
id Int #id #default(autoincrement())
name String
placeOfBirthId Int
placeOfDeathId Int
👉 placeOfBirth Place #relation(fields: [placeOfBirthId], references: [id])
placeOfDeath Place #relation(fields: [placeOfDeathId], references: [id])
}
Totally don't get it.
You have to add a name field to placeOfBirth and placeOfDeath. Then use these names to reference both of them in the Place model.
model Place {
id Int #id #default(autoincrement())
name String
Births Person[] #relation("Births")
Deaths Person[] #relation("Deaths")
}
model Person {
id Int #id #default(autoincrement())
name String
placeOfBirthId Int
placeOfDeathId Int
placeOfBirth Place #relation("Births", fields: [placeOfBirthId], references: [id])
placeOfDeath Place #relation("Deaths", fields: [placeOfDeathId], references: [id])
}

Spring Data JPA does not persist the oneToMany list

I am creating a new project and using Spring Data JPA to create some REST endpoints.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
I am able to put and persist to my primary class (customer), which works as long as the json file does not have any oneToMany data. However, when posting to customer, if there is oneToMany data I am getting errors.
The errors relate to the foreign key being null when trying to persist. I am not sure how Spring Data JPA should be using the annotation to let hibernate know what the value of the foreign key should be.
I have looked at numerous bi-directional OneToMany examples, as well as examples for creating foreign keys and have tried a number of modifications without success.
I also tried to use the spring.jpa.hibernate.ddl-auto=update to help create and update the database schema without any luck.
The customer
#Entity
#Table(name="customer")
#EntityListeners(AuditingEntityListener.class)
public class Customer extends Auditable<String> {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#OneToMany(fetch=FetchType.LAZY, mappedBy="customer", cascade={CascadeType.ALL})
private List<EmailAddress> emailAddresses;
.......
The emails
#Table(name="email_address")
#EntityListeners(AuditingEntityListener.class)
public class EmailAddress extends Auditable<String> {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="email_type")
private byte emailType;
#Column(name="email")
private String email;
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="customer_id")
#JsonIgnore
private Customer customer;
.....
The postman json test
{
"id": 1,
"firstName": "Bobby",
"lastName": "Smith",
"emailAddresses": [
{
"id": 1,
"emailType": 1,
"email": "bobby#bobby.com",
},
{
"id": 2,
"emailType": 1,
"email": "bobby#gmail.com",
}
]
}
BTW, I have confirmed that within the customer controller, that the emails are included in the request body of customer.
The customer controller
#PutMapping("/customers")
public Customer updateCustomer(#RequestBody Customer theCustomer) {
System.out.println("****email count "+theCustomer.getEmailAddresses().size());
for(EmailAddress index: theCustomer.getEmailAddresses()) {
System.out.println(index.toString());
}
customerService.save(theCustomer);
return theCustomer;
}
The customer service
#Override
public void save(Customer theCustomer) {
//Validate the input
if(theCustomer == null) {
throw new CustomerNotFoundException("Did not find the Customer, was null...");
}
customerRepository.save(theCustomer);
}
MySQL Script
--
-- Table structure for table `customer`
--
DROP TABLE IF EXISTS `customer`;
CREATE TABLE `customer` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(24) COLLATE utf8_bin NOT NULL,
`last_name` varchar(24) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Primary Customer Table';
--
-- Table structure for table `email_address`
--
DROP TABLE IF EXISTS `email_address`;
CREATE TABLE `email_address` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email_type` tinyint(4) unsigned NOT NULL COMMENT 'email type',
`email` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'email address',
`customer_id` int(11) NOT NULL COMMENT 'foreign key',
INDEX par_ind (customer_id),
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
KEY FK_EMAIL_CUSTOMER_idx (customer_id),
CONSTRAINT FK_EMAIL_CUSTOMER FOREIGN KEY (customer_id) REFERENCES customer (id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='email addresses';
Postman Complaint
{
"status": 400,
"message": "could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
"timeStamp": 1566840491483
}
Console Complaint
****email count 2
EmailAddress [id=1, type=1, email=bobby#bobby.com]
EmailAddress [id=2, type=1, email=bobby#gmail.com]
2019-08-28 17:33:07.625 WARN 8669 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1048, SQLState: 23000
2019-08-28 17:33:07.626 ERROR 8669 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : Column 'customer_id' cannot be null
2019-08-28 17:33:07.629 ERROR 8669 --- [nio-8080-exec-2] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
2019-08-28 17:33:07.735 WARN 8669 --- [nio-8080-exec-2] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement]
Therefore, with a post or put, I am not sure why the Spring Data JPA save does not satisfy the foreign key constraint for entities with oneToMany relationships. I am guessing it is either some missing annotations or something wrong with my sql script. Not sure why the update data does not persist to the email_address table. Does the emailAddress entity require some type of getter/setter for customer_id?
public class Customer extends Auditable<String> {
#OneToMany(fetch=FetchType.LAZY, mappedBy="customer", cascade={CascadeType.ALL})
private List<EmailAddress> emailAddresses;
}
public class EmailAddress extends Auditable<String> {
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="customer_id")
private Customer customer;
}
The mappedBy here means that the relationship between Customer and EmailAddress (i.e. the value of customer_id in customer table ) are determined by EmailAdress#cutomer but not Customer#emailAdresses.
What you are trying to show it just the content of Customer#emailAddress which will be ignored by Hibernate when deciding which DB values to be updated/inserted for this relationship. So you have to make sure EmailAddress#customer are set correctly.
For example , you can have the following method to add an email address to a Customer
public class Customer {
#OneToMany(fetch=FetchType.LAZY, mappedBy="customer", cascade={CascadeType.ALL})
private List<EmailAddress> emailAddresses;
public void addEmailAddress(EmailAddress email){
//As said Hibernate will ignore it when persist this relationship.
//Add it mainly for the consistency of this relationship for both side in the Java instance
this.emailAddresses.add(email);
email.setCustomer(this);
}
}
And always call addEmailAddress() to add an email for a customer. You can apply the same idea for updating an email address for a customer.

JPA/Hibernate not using all fields in composite primary key

I have a many-to-one relationship as below (I have removed columns that do not contribute to this discussion):
#Entity
#SecondaryTable(name = "RecordValue", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "RECORD_ID", referencedColumnName = "RECORD_ID") })
Class Record {
#Id
#Column(name = "RECORD_ID")
long recordId;
#OneToMany(mappedBy="key")
Set<RecordValue> values;
}
#Entity
class RecordValue {
#EmbeddedId
RecordValuePK pk;
#Column
long value;
#ManyToOne
#MapsId("recordId")
private Record key;
}
#Embeddable
class RecordValuePK {
#Column(name = "RECORD_ID")
#JoinColumn(referencedColumnName = "RECORD_ID", foreignKey = #ForeignKey(name = "FK_RECORD"))
long recordId;
#Column(name = "COLLECTION_DATE")
LocalDate collectionDate;
}
When hibernate creates tables, the RecordValue table has primary key consisting of only RECORD_ID and NOT COLLECTION_DATE.
What could be the problem?
Hibernate debug log shows the following:
DEBUG - Forcing column [collection_date] to be non-null as it is part of the primary key for table [recordvalue]
DEBUG - Forcing column [key_record_id] to be non-null as it is part of the primary key for table [recordvalue]
DEBUG - Forcing column [record_id] to be non-null as it is part of the primary key for table [recordvalue]
.
.
Hibernate:
create table Record (
RECORD_ID bigint not null,
primary key (RECORD_ID)
)
Hibernate:
create table RecordValue (
COLLECTION_DATE date not null,
VALUE bigint not null,
key_RECORD_ID bigint not null,
RECORD_ID bigint not null,
primary key (RECORD_ID)
)
Removing the #SecondaryTable specification has resolved this issue. The #SecondaryTable specification was forcing both tables to have the same the primary key. The found this solution after reading this blog:
https://antoniogoncalves.org/2008/05/20/primary-and-secondary-table-with-jpa.

How to set auto increment value to the primary key in ActiveAndroid

I'm new to active Android and I have done CRUD operation, but I am unable to set auto increment for the primary key. I already tried below code but it is of not use to me:
#Table(name = "Employee", id = "EmpId")
public class Employee extends Model {
#Column(name = "empid")
public long empid;
#Column(name="name")
public String name;
}
Here employee is my tablename. I have primary 2 fields, one is empid and name. I need to set auto increment value to the primary key.
How can I do that?
To do an update with a unique column as your pseudo primary key, the annotation would look something like this:
#Column(name = "empid", unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
public long empid;
As we can read in a documentation:
One important thing to note is that ActiveAndroid creates an id field
for your tables. This field is an auto-incrementing primary key.
Moreover, if you would like to create custom primary key in you model, you can check solution mentioned in GitHub issue connected with ActiveAndroid, which looks like this:
#Table(name = "Employee", id = "EmpId")
public class Employee extends Model {
#Column(name = "id")
public long id;
#Column(name="name")
public String name;
}
Then, id field is custom primary key, which will be auto-incremented.