Here is my tables definition
CREATE TABLE IF NOT EXISTS `store` (
`store_id` INT NOT NULL AUTO_INCREMENT,
`store_name` VARCHAR(1024) NOT NULL,
`store_user` INT NOT NULL,
`store_address` INT NOT NULL,
`store_type` INT NOT NULL,
`created_date` DATETIME NOT NULL,
`updated_date` DATETIME NOT NULL,
PRIMARY KEY (`store_id`)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `store_address` (
`address_id` INT NOT NULL AUTO_INCREMENT,
`address_line_1` VARCHAR(1024) NOT NULL,
`address_line_2` VARCHAR(1024) NOT NULL,
`address_line_3` VARCHAR(1024) NULL,
`city` VARCHAR(45) NOT NULL,
`locality` VARCHAR(100) NOT NULL,
`pincode` CHAR(6) NOT NULL,
`latitude` DECIMAL(8,6) NULL,
`longitude` DECIMAL(9,6) NULL,
`state` VARCHAR(45) NOT NULL,
`created_date` DATETIME NOT NULL,
`updated_date` DATETIME NOT NULL,
PRIMARY KEY (`address_id`),
CONSTRAINT `FK_STR_STR_ADR`
FOREIGN KEY (`address_id`)
REFERENCES `store` (`store_address`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
I am trying to have a 1-1 mapping between store and its address. Considering the DDL is ok, while generating JPA entities Store.java look like this:
#Entity
#Table(name="store")
public class Store
{
#Basic
#Column(name="created_date", nullable=false)
private Date createdDate;
#OneToOne(fetch=FetchType.LAZY, mappedBy="store", cascade=CascadeType.MERGE)
private StoreAddress storeAddress;
#Basic
#Column(name="store_address", columnDefinition="INT")
private int storeAddress2;
/////
Why is there a field storeAddress2 in Store.java? I think this is failing my insertion of a store. Any help?
Considering the DDL is ok [...]
The DDL is not OK, it is erroneous. As the tables are presently structured, the foreign key constraint is backward. store_address.address_id is the referenced key; the constrained column -- that is, the foreign key column -- should be store.store_address.
Moreover, be aware that putting the address into its own table and establishing a NOT NULL foreign key referencing it means that every store must have an address recorded, yet a store address does not have to correspond to any store. If you want the address to be optional then make store.store_address nullable, though that still permits addresses to exist that do not correspond to a store.
Alternatively, even though JPA prefers a forward mapping from parent to child such as you have presented, it is possible to map it in the other direction, so that store addresses cannot exist in the DB without a corresponding store, but stores do not have to have addresses recorded. In the DDL, that would correspond to deleting store.store_address, and creating store_address.store_id as a foreign key referencing store.store_id.
Update:
Here is some DDL to clarify my comments about the FK constraint. This is how an FK relationship between store and store_address should be written, given the column definitions as presented in the question:
CREATE TABLE IF NOT EXISTS `store` (
`store_id` INT NOT NULL AUTO_INCREMENT,
`store_address` INT NOT NULL,
-- ...
PRIMARY KEY (`store_id`),
CONSTRAINT `FK_STR_STR_ADR`
FOREIGN KEY (`store_address`)
REFERENCES `store_address` (`address_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION
)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `store_address` (
`address_id` INT NOT NULL AUTO_INCREMENT,
-- ...
PRIMARY KEY (`address_id`)
-- the FK constraint does NOT go here
)
ENGINE = InnoDB;
Note also that as I already wrote, this does not enforce a 1-1 relationship in the DB. If you want that then you could put a UNIQUE constraint on store.store_address, but it might be better to instead link the PKs of store and store_address. That way you can also prevent orphan store_address rows from being allowed. That could look like this:
CREATE TABLE IF NOT EXISTS `store` (
`store_id` INT NOT NULL AUTO_INCREMENT,
-- ... no store_address ...
PRIMARY KEY (`store_id`)
-- ... no FK constraint here ...
)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `store_address` (
-- addresses do not have independent IDs:
`store_id` INT NOT NULL,
-- ...
PRIMARY KEY (`store_id`),
CONSTRAINT `FK_STR_STR_ADR`
FOREIGN KEY (`store_id`)
REFERENCES `store` (`store_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION
)
ENGINE = InnoDB;
That does permit a store to exist without a corresponding address, which may be sensible, even though you say you don't want that.
Really, though, if a store must not exist without exactly one corresponding address, and a store address must not exist without a store, then why are you mapping these as separate tables? It gains you nothing except, maybe, less manual adjustment to automatically-generated entity classes. It's definitely a loss in the performance and DB complexity arenas.
Note, too, that JPA has annotations for mapping two closely-associated entities to the same table, if you insist that the addresses should be separate entities from their associated stores. Look into the #Embeddable and related annotations.
Related
I have a spring-boot application with a MySql database. I have a many-to-many relationship between a pizza table and a topping table so I've made an extra table where I store these relationships. The tables:
CREATE TABLE topping (
id SERIAL PRIMARY KEY,
topping_name VARCHAR(64) NOT NULL,
price INT NOT NULL,
spicy bool NOT NULL
);
CREATE TABLE pizza (
id SERIAL PRIMARY KEY,
pizza_name VARCHAR(64) NOT NULL
);
CREATE TABLE pizza_with_topping (
pizza_id BIGINT UNSIGNED NOT NULL,
topping_id BIGINT UNSIGNED NOT NULL,
CONSTRAINT pizza_with_topping_ibfk_1
FOREIGN KEY(pizza_id)
REFERENCES pizza(id),
CONSTRAINT pizza_with_topping_ibfk_2
FOREIGN KEY(topping_id)
REFERENCES topping(id)
);
In spring-boot I found that I have to extend the CrudRepository interface and I can call the findAll() method from this to get the contents of a table. At the momment I get the contents of all 3 tables like so:
Iterable<Pizza> pizzasInDb = pizzaRepository.findAll();
Iterable<Topping> toppingsInDb = toppingRepository.findAll();
Iterable<PizzaWithTopping> pizzaToppingConnectionTable = pizzaWithToppingRepository.findAll();
After this based on these 3 tables I manually create objects that contain both the pizza's name and it's toppings. Since I have set foreign keys in pizza_with_topping table I was wondering if there is a better way to get this model? Maybe with the call of built-in functions that automatically makes this model object for me based on the foreign keys.
Yes, there is. You can model your domain with many-to-many relationships using JPA. If you are using annotation this cam be achieve using #ManyToMany
There is an example here
I use Spring Boot and Flyway with this initialization script:
CREATE TABLE ADDRESS(
ID bigserial NOT NULL PRIMARY KEY
);
CREATE TABLE ROLE(
ID bigserial NOT NULL PRIMARY KEY
);
CREATE TABLE PERSON(
ID bigserial NOT NULL PRIMARY KEY,
FIRST_NAME VARCHAR(255),
LAST_NAME VARCHAR(255),
ADDRESS bigserial NOT NULL REFERENCES ADDRESS (ID),
ROLE bigserial REFERENCES ROLE (ID) -- notice here is no 'not null'
);
All the relationship between the tables is that:
Each PERSON has 0-1 ROLE. So, each ROLE belongs to 0-n PERSON. Hence, this relationship is nullable.
Each PERSON has 1 ADDRESS. So, each ADDRESS belongs to 1-n PERSON. Hence, this relationship is not-null.
As soon as I start the application (I have also tried to post the query straight to the PostgreSQL database schema), there is somehow generated constraint not-null between the PERSON and ROLE tables.
Using DataGrip, I select SQL Scripts -> Generate DDL to Query Console and get the DDL for the tables (see below, new lines and roles definitions omitted for sake of brevity).
To my surprise, the NOT NULL is there although I haven't defined such constraint. How to get rid of it aside from altering table?
create table if not exists address
(
id bigserial not null
constraint address_pkey primary key
);
create table if not exists role
(
id bigserial not nullconstraint role_pkey primary key
);
create table if not exists person
(
id bigserial not null
constraint person_pkey primary key,
first_name varchar(255),
last_name varchar(255),
address bigserial not null
constraint person_address_fkey references address,
role bigserial not null -- why is 'not null' here?
constraint person_role_fkey references role
);
The version of PostgreSQL I use (through SELECT version()) is:
PostgreSQL 10.13, compiled by Visual C++ build 1800, 64-bit
"8.1.4. Serial Types":
The data types smallserial, serial and bigserial are not true
types, but merely a notational convenience for creating unique
identifier columns (similar to the AUTO_INCREMENT property supported
by some other databases). In the current implementation, specifying:
CREATE TABLE tablename (
colname SERIAL
);
is equivalent to specifying:
CREATE SEQUENCE tablename_colname_seq AS integer;
CREATE TABLE tablename (
colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;
Note the NOT NULL.
Don't use bigserial for the foreign key. That doesn't make much sense. Simply use bigint.
CREATE TABLE IF NOT EXISTS person
(...
role bigint REFERENCES role);
Possible solution 1:
Changing Biserial to Bigint does not remove the null constraint set to foreign key column when running flyway in springboot to write into postgres DB (at least for my case)
postgres:11.3-alphine 3.4
flyway: 8.0.5
To be secure, need to add scripts to alter columns to be nullable
ALTER TABLE mytable ALTER COLUMN mycolumn DROP NOT NULL;
Change Postgres column to nullable
Possible solition 2:
When Spring boot set JPA Hibernate ddl configuration to create, create-drop, update, flyway DB migration script will be updated by JPA entities properties. NOT NULL constraints can be added by JPA entities.
Change JPA Hibernate ddl configuration to none or validate will ensure only flyway script is used to create schema.
JPA Hibernate ddl configuration
I have this problem at hand which is as follows :
There is a many-to-many relationship between User & Group.The back-end has tables for the respective entity classes as well as an intersection table User_Groups which stores information about the association of a user to groups.The Groups table has two columns which are relevant to the problem at hand.The Structure of group table is as follows:
Table name - GROUP
CREATE TABLE IF NOT EXISTS
GROUP
(
GROUP_ID INT NOT NULL AUTO_INCREMENT ,
NAME VARCHAR(50) NULL ,
DESCRIPTION VARCHAR(50) NULL ,
CREATED_DATE DATE NULL ,
CREATED_BY VARCHAR(50) NULL ,
MODIFIED_DATE DATE NULL ,
MODIFIED_BY VARCHAR(50) NULL ,
GROUP_TYPE_ID INT NULL ,
PARENT_GROUP_ID INT NULL ,
PRIMARY KEY (GROUP_ID) ,
INDEX fk_groupTypeId_idx (GROUP_TYPE_ID ASC) ,
CONSTRAINT fk_groupTypeId FOREIGN KEY (GROUP_TYPE_ID )
REFERENCES efc_group_type (GROUP_TYPE_ID )
ON DELETE NO ACTION
ON UPDATE NO ACTION);
------------------------------------------------------------------------------------------
Table name - USER_GROUPS
CREATE TABLE IF NOT EXISTS
EFC_USER_GROUPS
(
USER_ID int(11) NOT NULL,
GROUP_ID int(11) NOT NULL,
CREATED_DATE date DEFAULT NULL,
PRIMARY KEY (USER_ID,GROUP_ID),
KEY FK_USERID_idx (USER_ID),
KEY FK_GROUPID_idx (GROUP_ID),
CONSTRAINT FK_GROUPID_USERGROUPS FOREIGN KEY (GROUP_ID)
REFERENCES efc_group (GROUP_ID),
CONSTRAINT FK_USERID_USERGROUPS FOREIGN KEY (USER_ID)
REFERENCES efc_user (USER_ID));
The parent_group_id is basically acting as a foreign column to the group_id. A group can have sub-groups and hence the need to store the parent_group_Id. For each sub-group added, we store the parent_group_id for that group under which sub-group was added. Now that you have some idea about the data model, I'll illustrate what my problem is:
I am trying to construct a tree like structure for the parent-child relationship between groups & sub-groups for a certain user. So corresponding to a certain user Id, I am able to get the groups that he is part of from the user_groups table.Then I am able to filter the groups into two lists, one representing the parent nodes and the other has all child nodes. Now the hierarchy between groups is stored in the group table. I am not able to figure out how to implement that in java. I already have the list with me but in that list each group can have parents above it and also children below it. I am not able to understand how to build the top to bottom hierarchy. My sole purpose is to build that XML constituting the parent-nodes down to last child node and send it to the front-end.
P.S : I have googled quite a bit.I am running short of ideas :(. Please help me
right now I'm having trouble mapping a linking table with Hibernate.
First of all I want to explain what I want to map:
I have 3 tables: Product , DocumentType, Language.
One Product can have each DocumentType (at the moment we have 7 DocumentTypes) in each specific Language (at the moment we have 3 Languages)
That means product "1" can have DocumentType "A" in language "EN", "ES" and "FR".
I created a linking table with 3 foreign keys which are also composite primary key.
Here is how my sql looks like.
CREATE TABLE Person(
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(255)
);
CREATE TABLE DocumentType(
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(255),
key varchar(255)
);
CREATE TABLE Language(
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(255),
code varchar(2)
);
CREATE TABLE Person_DocumentType_Language(
person_id int NOT NULL,
doc_id int NOT NULL,
lang_id NOT NULL,
FOREIGN KEY(person_id)
REFERENCES Person(id),
FOREIGN KEY(doc_id)
REFERENCES DocumentType(id),
FOREIGN KEY(lang_id)
REFERENCES Language(id),
PRIMARY KEY(person_id, doc_id, lang_id)
);
With the last linking table I could tell which Person has which DocumentTypes and in which Languages.
I'm mostly interested in the information which DocumentType has which Language for one Person.
Say I'm Person A. Now I want to know which DocumentTypes I have with which Languages. In SQL it would look like this I think:
Select doc_id, lang_id from Person_DocumentType_Language where person_id=1
Does that make sense? And how could I map this in Hibernate?
One approach is have a Map from DocumentType to Language. I'm a bit rusty on the exact annotation, but here is a start:
public class Person {
#Id
public int id;
#OneToMany(targetEntity=Language.class)
#MapKeyClass(Integer.class)
#CollectionTable(name="Person_DocumentType_Language")
#MapKeyColumn(name="doc_id")
public Map<DocumentType,Language> docTypeLang;
}
I've two tables that has defined as below; From user table, hospitalId and poliklinikId both references table relhospol, and if any row is deleted from relhospol, (if any user is related with it), I want to set the hospitalId and poliklinikId null, DDL says that. When i delete a row from SQLite Manager it nulls the User's hospitalId and PoliklinikId, However when i try to remove a row from application level (Java), it only removes from relhospol, it does not set null (hospitalId, PoliklinikId) What is the missing point ?
JDBC Driver: SQLite-jdbc-3.7.2
CREATE TABLE [USER] (
[ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[GROUPID] INTEGER CONSTRAINT [FK_USER_GID] REFERENCES [GROUP]([ID]) ON DELETE SET NULL ON UPDATE CASCADE,
[HOSPITALID] INTEGER,
[POLIKLINIKID] INTEGER,
[NAME] VARCHAR(50) NOT NULL,
[LOGINID] VARCHAR(15) NOT NULL,
[EMAIL] VARCHAR(50) NOT NULL,
[PASSWORD] VARCHAR(32) NOT NULL,
CONSTRAINT [FK_USER_RELHOSPOL] FOREIGN KEY([HOSPITALID], [POLIKLINIKID]) REFERENCES [RELHOSPOL]([HOSPITALID], [POLIKLINIKID]) ON DELETE SET NULL ON UPDATE CASCADE);
CREATE UNIQUE INDEX [AS] ON [USER] ([LOGINID]);
CREATE UNIQUE INDEX [AS1] ON [USER] ([EMAIL]));
CREATE TABLE [RELHOSPOL] (
[HOSPITALID] INTEGER CONSTRAINT [FK_RELHOSPOL_HOS] REFERENCES [HOSPITAL]([ID]) ON DELETE SET NULL ON UPDATE CASCADE,
[POLIKLINIKID] INTEGER CONSTRAINT [FK_RELHOSPOL_POL] REFERENCES [POLIKLINIK]([ID]) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT [sqlite_autoindex_RELHOSPOL_1] PRIMARY KEY ([HOSPITALID], [POLIKLINIKID]));
You can't set NULL a primary key:
CREATE TABLE [RELHOSPOL] (
[HOSPITALID] INTEGER CONSTRAINT [FK_RELHOSPOL_HOS] REFERENCES [HOSPITAL]([ID]) ON DELETE SET NULL ON UPDATE CASCADE,
[POLIKLINIKID] INTEGER CONSTRAINT [FK_RELHOSPOL_POL] REFERENCES [POLIKLINIK]([ID]) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT [sqlite_autoindex_RELHOSPOL_1] PRIMARY KEY ([HOSPITALID], [POLIKLINIKID]));
You are trying to remove a row from which table? Can you post the DELETE SQL?
Ok I've changed the RELHOSPOL's definition as below; When i delete row from RELHOSPOL from the SQL Manager with executing a DELETE command, it nulls the necessary rows at the USER table. Whenever I delete a row from application with the method written below, it only deletes row from RELHOSPOL, and does not SET NULL the necessary rows at USER table
CREATE TABLE [RELHOSPOL] (
[ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[HOSPITALID] INTEGER,
[POLIKLINIKID] INTEGER);
CREATE UNIQUE INDEX [UNIQUE_RELHOSPOL] ON [RELHOSPOL] ([HOSPITALID], [POLIKLINIKID]);
Delete Method is :
public void deletePoliklinikFromHospital(int hospitalId, int poliklinikId) throws SQLException{
String query = "DELETE FROM [RELHOSPOL] WHERE (HOSPITALID = ? AND POLIKLINIKID = ?)";
try {
PreparedStatement statement = db.prepareStatement(query);
statement.setInt(1, hospitalId);
statement.setInt(2, poliklinikId);
statement.executeUpdate();
} catch (SQLException e) {
throw new SQLException(e.getMessage());
}
}
It is just a guess, but maybe you are not aware, that you have to enable foreign key support with PRAGMA foreign_keys = ON; everytime you connect to the database? If this is not done, your statements will be parsed, but not enforced. This could explain, why it is working from SQLite Manager.
One should also be aware, that old versions of Sqlite (<3.6.19) will tolerate the syntax but too will not enforce anything.
As sidenote:
As #Neuquino writes the ON DELETE SET NULL statements for table RELHOSPOL don't seem to make sense.