I'm trying out pre-defining my database structure using SQL schema and then utilising it within my Kotlin code. I'm using Spring Boot and Spring Data JDBC.
Here's what I currently have so far:
My User class:
data class User(
#Column(name = SqlQueries.Users.Entries.id) // "user_id"
val id: String?,
#Column(name = SqlQueries.Users.Entries.firstName) // "user_first_name"
var firstName: String = "Joe",
#Column(name = SqlQueries.Users.Entries.lastName) // "user_last_name"
var lastName: String = "Bloggs",
#Column(name = SqlQueries.Users.Entries.username) // "user_username"
var username: String = "${firstName}.${lastName}",
#Column(name = SqlQueries.Users.Entries.password) // "user_password"
#JsonIgnore
var password: String = "password1",
#Column(name = SqlQueries.Users.Entries.isActive) // "user_is_active"
val isActive: Boolean = true,
)
My UserRole class:
data class UserRole(
#Column(name = SqlQueries.Lookups.UserRoles.Entries.id)
val id: Int? = null,
#Column(name = SqlQueries.Lookups.UserRoles.Entries.roleName)
val name: String = "",
)
My Schema:
-- ===================================================================================
-- Lookup Tables
-- ===================================================================================
-- Creates our User Table if one does not exist within the database already.
CREATE TABLE IF NOT EXISTS table_users(
user_id VARCHAR(60) DEFAULT RANDOM_UUID() UNIQUE PRIMARY KEY,
user_first_name VARCHAR NOT NULL,
user_last_name VARCHAR NOT NULL,
user_username VARCHAR NOT NULL UNIQUE,
user_password VARCHAR NOT NULL,
user_is_active VARCHAR NOT NULL
);
-- ===================================================================================
-- Lookup Tables
-- ===================================================================================
-- Creates our Roles Lookup Table if one does not already exist within the database.
CREATE TABLE IF NOT EXISTS lookup_roles(
role_id SMALLINT AUTO_INCREMENT UNIQUE PRIMARY KEY,
role_name VARCHAR NOT NULL UNIQUE
);
-- ===================================================================================
-- Junction Tables
-- ===================================================================================
-- Creates our User/Role Junction table if one does not already exist within the database.
-- This will be the foundation of a many to many relationship between the two entities.
CREATE TABLE IF NOT EXISTS junction_user_role(
user_id VARCHAR(60),
role_id SMALLINT,
CONSTRAINT pk_user_role PRIMARY KEY (user_id, role_id),
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES table_users (user_id),
CONSTRAINT fk_role FOREIGN KEY (role_id) REFERENCES lookup_roles (role_id)
);
As you can see, I've created a User, Role and User/Role table. This is designed to have a many to many relationship.
I'm in the dark with being able to "access" the relationship as a variable which I can use later on.
In my previous "prototype" which had a different design concept I used the following format:
User class:
...
#ManyToMany(fetch = FetchType.EAGER)
#Column(name = "user_roles")
var userRoles: MutableSet<Role> = mutableSetOf(),
...
How would I define and implement this with the new way of doing it?
I don't really speak Kotlin, but I try anyway. Please use ChatGPT or personal experience to fix Kotlin errors ;-)
The first step is to identify the aggregates an what belongs to which aggregate.
I'd suggest that you have a UserRole and a User aggregate, with the later owning the relationship.
This implies that you need to add the relationship to the User, which is your aggregate root for the User aggregate.
Since Role is a different aggregate you'd reference it by id, and you need a separate entity to hold that id
data class User (
...
#MappedCollection(idColumn="user_id")
val roles: Set<RoleRef>
)
data class RoleRef(
val roleId: AggregateReference<Role, Int>
)
If you want to navigate from UserRole to all the User entities having that role, you'd create a repository method in the UserRepository for this.
How this works is detailed in https://spring.io/blog/2018/09/24/spring-data-jdbc-references-and-aggregates
Also related: Spring Data JDBC many to many relationship management
Related
I tried to implement a reference table when it comes to ManyToMany relation in the context of DDD based on this article(https://spring.io/blog/2018/09/24/spring-data-jdbc-references-and-aggregates)
I have different naming for JdbcEntity because I have another Domain Entity named Book which handles domain logic. (I've decided to decouple Domain model and Persistence model which is JdbcEntity as below)
The problem is the class name BookJdbcEntity is mapped as a foreign key name in book_author automatically:
"PreparedStatementCallback; bad SQL grammar [SELECT `book_author`.`AUTHOR_ID` AS `AUTHOR_ID` FROM `book_author` WHERE `book_author`.`BOOK_JDBC_ENTITY` = ?]; nested exception is java.sql.SQLSyntaxErrorException: (conn=845) Unknown column 'book_author.BOOK_JDBC_ENTITY' in 'where clause'",
Is there any possible way to generate the below SQL statement? (book_id instead of BOOK_JDBC_ENTITY)
SELECT `book_author`.`AUTHOR_ID` AS `AUTHOR_ID` FROM `book_author` WHERE `book_author`.`book_id` = ?
Jdbc Entity:
#Table("book")
data class BookJdbcEntity(
#Id val id: Long,
val title: String,
val isbn: String,
val pages: Int,
val authors: Set<AuthorRef> = hashSetOf()
)
#Table("book_author")
data class AuthorRef(val authorId: Long)
Schema:
CREATE TABLE IF NOT EXISTS book
(
id bigint(20) NOT NULL,
title VARCHAR(100) NOT NULL,
isbn varchar(100) not null,
pages INTEGER not null,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS book_author
(
book_id bigint(20) NOT NULL,
author_id bigint(20) NOT NULL,
constraint book_id_fk foreign key (book_id) references book (id)
);
That's done with #MappedCollection
#Table("book")
data class BookJdbcEntity(
#Id val id: Long,
val title: String,
val isbn: String,
val pages: Int,
#MappedCollection(idColumn="book_id")
val authors: Set<AuthorRef> = hashSetOf()
)
Here the code is written in Go. I am using two tables where one table has a foreign key that refers to the other table's primary key. Let's say I have a database as following struct defined:
type User struct{
ID uint `gorm:"primary_key;column:id"`
Name string `gorm:"column:name"`
Place place
PlaceID
}
type Place struct{
ID uint `gorm:"primary_key;column:id"`
Name string `gorm:"column:name"`
Pincode uint `gorm:"column:pincode"`
}
And the sql schema is:
create table place(
id int(20) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
pincode uint(20) NOT NULL,
PRIMARY KEY (id),
)
create table user(
id int(20) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
place_id uint(20) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (place_id) REFERENCES place(id)
)
Now while inserting in user by gorm as:
place := Place{Name:"new delhi",Pincode:1234}
user := User{Name: "sam", Age: 15, Place: place}
err = db.Debug().Create(&user).Error
//It inserts to both user and place table in mysql
//now while updating to name in user table as Samuel and place as
//following
place := Place{Name:"mumbai",Pincode:1234}
err = db.Debug().Model(&User{}).Where("id =?",
1,).Update(&user{Name:"Samuel",Place:place}).Error
It updates the row in user table but creates a new row in place table.But it should update the matching row in place table and not create a new one
Is there any way to do it? Here I am not using auto migrate function to create db tables.
The answer to your question should be sought in a relations or Association Mode.
The example below shows how to add new associations for many to many, has many, replace current associations for has one, belongs to
db.Model(&user).Association("Place").Append(Place{Name:"mumbai",Pincode:1234})
Or you can replace current associations with new ones:
db.Model(&user).Association("Place").Replace(Place{Name:"mumbai",Pincode:1234},Place{Name:"new delhi",Pincode:1234})
Probably It's creating a new row because you didn't set the ID on Place{Name:"mumbai",Pincode:1234}.
I am trying to add a permission hierarchy for users e.g. child, parent, admin
My idea is to create a generic user table with usernames and passwords and accessibility which would be from 0 to 2 , 0 - child 1 - parent 2 - admin, but I do not know how to connect the user table to parent/child table since they have different variables.
Access picture of my database right now
To be clear parent/child wouldn't have username/password like in this picture anymore with user table.
Update:
DB using only one table for users
This table would keep the fields that are only for parents empty if this is a child etc. I would want feedback if the variable 'accessibility' makes sense that would be value from 0 to 2 which would allow me in code to check if it is a parent child or admin
What you want is a kind-of table inheritance that prevents two derived entities from sharing the same supertype instance.
Databases like Access and MS SQL Server do not support table-inheritance like how PostgreSQL does, but you can fake it:
TABLE Users (
UserId int PRIMARY KEY IDENTITY(1,1),
UserName nvarchar(50) NOT NULL,
PasswordHash binary(32) NOT NULL, -- assuming sha256
PasswordSalt binary(8) NOT NULL
)
TABLE Parents (
UserId int NOT NULL PRIMARY KEY,
-- insert parent-specific fields here
FOREIGN KEY UserId REFERENCES Users ( UserId )
)
TABLE Children (
UserId int NOT NULL PRIMARY KEY,
-- insert child-specific fields here
FOREIGN KEY UserId REFERENCES Users ( UserId )
)
This schema means that a Parent or Children entity cannot exist without a single specific User entity also existing. However because Access does not support true table-inheritance it cannot easily constrain UserId values such that only 1 Parent or Children row can have that value (i.e. there can be a Parent AND a Child that share the same UserId value).
Fortunately there's a hack that is mathematically correct (as far as relational-algebra is concerned) but which unfortunately breaks aspects of OOP in that the superclass (User) is now aware of it subclasses - but this might be desirable is certain circumstances...
...anyway. the trick is to add an enum value to User's primary key (so it's a composite-key) which identifies a singular subclass, then add a constant (enforced via CHECK CONSTRAINT) composite-key component to match in each "derived" table, like so (using pseudo-SQL - the relational-algebra is portable, but concepts like enums and check-constraints don't necessarily port to MS Access very well):
ENUM UserType ( HumanParent = 1, HumanChild = 2, Other = 3 )
TABLE Users (
UserId int IDENTITY(1,1),
UserType UserType NOT NULL,
UserName nvarchar(50) NOT NULL,
PasswordHash binary(32) NOT NULL, -- assuming sha256
PasswordSalt binary(8) NOT NULL
PRIMARY KEY ( UserId, UserType )
)
TABLE Parents (
UserId int NOT NULL,
UserType UserType NOT NULL,
-- insert parent-specific fields here
PRIMARY KEY ( UserId, UserType )
FOREIGN KEY ( UserId, UserType ) REFERENCES Users ( UserId, UserType )
CHECK CONSTRAINT UserType = 1
)
TABLE Children (
UserId int NOT NULL,
UserType UserType NOT NULL,
-- insert child-specific fields here
PRIMARY KEY ( UserId, UserType )
FOREIGN KEY ( UserId, UserType ) REFERENCES Users ( UserId, UserType )
CHECK CONSTRAINT UserType = 2
)
So at the cost of slight inefficiency (i.e. the extra storage needed for the UserType columns - and computational expense of evaluating the CHECK constraints) you gain guarantees of the correctness of your data.
Now have fun porting that to Access :)
I am using jpa for persisting my database along with java pojos
#Entity
#Table(schema = "CENTRALSERVICES", name = "APPLICATION")
public class Application {
#Id
#Column(name = "id", nullable = false, length = 128)
private long id;
}
My database is like this:
CREATE TABLE CENTRALSERVICES.APPLICATION(
id bigint(8) NOT NULL, PRIMARY KEY(id));
The problem is that each application object that I persist was having an Id till now, but the requirement changed and now its not guaranteed if Id will be there. I saw something with sequence but I want to assign a sequence generated value only when the Id is null.
How to solve this. Please help
I changed my database schema and now my primary key is autogenerated whereas id for my application object I have created another field which can be null.
I have 2 tables as follows:
create table Users
(
UserId int primary key identity not null
)
create table UserExternalKeys
(
UserIdRef int primary key not null,
ExternalKey varchar(50) unique not null
)
alter table UserExternalKeys
add constraint fk_UsersExternalKeys_Users
foreign key (UserIdRef)
references Users (UserId)
Each user can have a 0 or 1 external keys. Things are setup this way because adding a nullable unique column to SQL Server does not allow for more than 1 null value.
Based on Ayende's post, it seems like this could be handled using a <one-to-one> mapping. However, this would require the UserExternalKeys table to have its own primary key.
The new schema would look something like this:
create table Users
(
UserId int primary key identity not null,
ExternalKeyRef int null
)
create table UserExternalKeys
(
UserExternalKeyId int primary key identity not null,
ExternalKey varchar(50) unique not null
)
alter table Users
add constraint fk_Users_UsersExternalKeys
foreign key (ExternalKeyRef)
references UserExternalKeys (UserExternalKeyId)
I think this would work, but it feels like I would only be adding the UserExternalKeyId column to appease NHibernate.
Any suggestions?
If a user can have 0 or 1 external keys why not design the tables as:
create table Users
(
UserId int primary key identity not null
ExternalKey varchar(50) null
)
and use one of the known workarounds for this problem. If you're using SQL Server 2008 you can use a filtered index. If you're using an earlier version you can use a trigger, an indexed view (2005), or the nullbuster workaround.
You could also keep your original schema and map the relationship as one-to-many from Users to UserExternalKeys. Map the collection as a private member and expose access to it through a property:
private IList<UserExternalKeys> _externalKeys;
public string ExternalKeys
{
get
{
if (_externalKeys.Count() == 1)
{
return _externalKeys.ElementAt(0).ExternalKey;
}
else
{
// return null or empty string if count = 0, throw exception if > 1
}
}
set
{
if (_externalKeys.Count() == 0) { // add key and set value }
else { // set value if count = 1, throw exception if > 1 }
}
}