I’m looking for some help in converting the following tables into Spring Data Entities. I was somewhat able to do it but I can’t figure out enforcing the on delete and on update constraints. I have a users table that will contain info about a user. The conversations table will then have a conversation id and 2 columns to represent both id’s of the participating users. I’m writing my Solution in Kotlin, but I’m fine with advice in Java as well. Using Postgres. At the end of the day, I just want to end up with these 2 tables with the ability to change the primary key in the users table and have that carry over to the conversations table.
CREATE TABLE users (
id uuid PRIMARY KEY,
bio character varying(255),
first_name character varying(255) NOT NULL,
last_name character varying(255) NOT NULL,
password character varying(255) NOT NULL,
username character varying(255) NOT NULL UNIQUE,
zip_code character varying(255) NOT NULL
);
CREATE TABLE conversations (
convoid BIGSERIAL PRIMARY KEY,
user_id1 uuid REFERENCES users(id) on delete cascade on update cascade,
user_id2 uuid REFERENCES users(id) on delete cascade on update cascade
);
Here's what I tried:
#Data
#NoArgsConstructor
#Entity
#Table(name = "users")
data class User(
#Id //primary key
var id: UUID = UUID.randomUUID(),
#Column(nullable = false)
var firstName: String,
#Column(nullable = false)
var lastName: String,
#Column(nullable = false, unique = true)
var username: String,
#Column(nullable = false)
var password: String,
#Transient
var confirmPassword: String,
#Column(nullable = false)
var zipCode: String,
#Column(nullable=true)
var bio: String?
)
#Data
#NoArgsConstructor
#Entity
#Table(name = "conversations")
data class Conversation(
#Id //primary key
#GeneratedValue(strategy = GenerationType.IDENTITY)
val convoID: Long,
#ManyToOne(cascade = [CascadeType.ALL])
#JoinColumn(name = "user_id1")
val user1: User, //FK of user table PK
#ManyToOne(cascade = [CascadeType.ALL])
#JoinColumn(name = "user_id2")
val user2: User, //FK of user table PK
#Transient
var participants: List<WebSocketSession>
)
I'm trying to update the primary key in the users table but it isn't allowing me due to foreign key constraint. Clearly the Cascade isn't working.
In JPA relations only work in 1 way, in other to have Converation deleted when User is deleted you need to have #OneToMany relation to Converation from User. Same thing applies for update as well.
So adding property like this inside User should solve your problem:
#OneToMany(cascade = CascadeType.ALL)
val conversations: List<Conversation>
UPDATE
JPA creates separate table, it is usually solved by adding mappedBy = "mappingFieldName" to #OneToMany, but considering your case where you have 2 fields this is a bit tricky and much broader problem. You will either have to do a lot of custom querying or simply keep duplicate entries, meaning that for each new Conversation you will actually have 2 of them, so then you can say mappedBy = "user1", just remember to every time save 2 Conversations, 1 where user1 is actual user1 and another where you swap user1 and user2.
Related
I'm currently trying implement a many to many relationship in Hibernate. I followed: https://www.baeldung.com/hibernate-many-to-many however I'm getting an exception that says:
java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`playlist_comparer`.`playlist_tracks`, CONSTRAINT `fk_pt_track_id` FOREIGN KEY (`track_id`) REFERENCES `tracks` (`id`))
Here's the liquibase for my join table:
- createTable:
tableName: playlist_tracks
columns:
- column:
constraints:
primaryKey: true
primaryKeyName: pk_playlist_track_id
name: id
type: bigint
autoIncrement: true
- column:
name: playlist_id
type: VARCHAR(255)
constraints:
foreignKeyName: fk_pt_playlist_id
references: playlists(id)
- column:
name: track_id
type: VARCHAR(255)
constraints:
foreignKeyName: fk_pt_track_id
references: tracks(id)
and the mapping on my Hibernate objects:
Playlist.java:
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "playlist_tracks", //
joinColumns = {#JoinColumn(name = "track_id", nullable = false)}, //
inverseJoinColumns = {#JoinColumn(name = "playlist_id", nullable = false)})
private Set<Track> tracks = new HashSet<>();
Track.java:
#ManyToMany(mappedBy = "tracks")
private Set<Playlist> playlists = new HashSet<>();
I save the tracks into the DB first with no association to any playlists in one #Transactional method, leave the transactional boundary and then enter a new #Transactional method where I add the tracks to the Playlist object and then try to save that at which point I get the exception at the top.
Anyone have any ideas?
Turns out I just had the join columns the wrong way round on the join table annotation.
I have the following two tables in my database:
CREATE TABLE ACCOUNT (
ID NUMBER(19, 0) NOT NULL,
AA_ID VARCHAR2(11 CHAR),
BB_ID VARCHAR2(8 CHAR),
STATUS NUMBER(1, 0) DEFAULT 1,
CREATED_AT TIMESTAMP(6) NOT NULL,
LAST_MODIFIED_AT TIMESTAMP(6)
) TABLESPACE $tbsp;
CREATE TABLE MOBILE_INSTANCE (
ID NUMBER(19, 0) NOT NULL,
ACCOUNT_ID NUMBER(19, 0) NOT NULL,
APPLICATION_ID VARCHAR2(36 CHAR) NOT NULL,
TOKEN VARCHAR2(255 CHAR) NOT NULL,
DEVICE VARCHAR2(13 CHAR),
STATUS VARCHAR2(10 CHAR),
CREATED_AT TIMESTAMP(6) NOT NULL,
LAST_MODIFIED_AT TIMESTAMP(6)
) TABLESPACE $tbsp;
And the connection between them:
ALTER TABLE MOBILEBANK_INSTANCE ADD CONSTRAINT FK_MOBILEBANKINSTANCE_ACCOUNT FOREIGN KEY (ACCOUNT_ID) REFERENCES ACCOUNT(ID);
So far, this is working like a charm...
Via JPA, I have created the entites for these tables - please see the following snippet of my entities:
In the MobileInstance entity I have marked the #ManyToOne connection as following:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ACCOUNT_ID")
private Account accountId;
And this is how it looks like in my Account entity class:
#OneToMany(mappedBy = "accountId", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<MobileInstance> mobileInstanceList = new ArrayList<>();
As you see, the accountId field (which is in the Account table actually a Long value) is marked as an Account object.
Later on, when I have already an account object, I whish the create a mobileInstance object as well, with the appropiate account ID.
But when I call:
mobileInstance.setAccountId
it requieres an account parameter... When I assigne one to it and try to save it in the DB, I got the error:
ERROR SqlExceptionHelper:131 - ORA-00904: "APPLICATION_ID": invalid identifier
I assume, it is because the Application ID field in the Database is a Number field, and I assigne to it a whole Account object which I do not want to change to Long..
Is there a way the code to recognize that only use the primary key of my Account table?
My POJO is using JPA and when I apply unique value in the column is not working for me I tried to use the UniqueConstraint and also not working for me .
below is my code
#Entity
#Table(name = "users", uniqueConstraints={#UniqueConstraint(columnNames = {"user_id"}),#UniqueConstraint(columnNames = {"username"}),#UniqueConstraint(columnNames = {"email"})})
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="user_id",unique=true,nullable = false)
private int UserId;
#Column(name = "username" ,unique=true,nullable = false)
private String Username;
#Column(name = "email",unique=true ,nullable = false)
private String email;
#Column(name = "firstname",nullable = false)
private String firstname;
#Column(name = "lastname", nullable = false)
private String lastname;
#Column(name = "password", nullable = false)
private String password;
#Column(name = "active")
private int active;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name="user_role", joinColumns=#JoinColumn(name="user_id"), inverseJoinColumns=#JoinColumn(name="role_id"))
private Set<Role> roles;
below is the generated table in the database (MySQL)
| users | CREATE TABLE users (
user_id int(11) NOT NULL,
username varchar(255) NOT NULL,
active int(11) DEFAULT NULL,
email varchar(255) NOT NULL,
firstname varchar(255) NOT NULL,
lastname varchar(255) NOT NULL,
password varchar(255) NOT NULL,
PRIMARY KEY (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
Hibernate log after spring start
Hibernate: create table users (user_id integer not null, username varchar(255) not null, active integer, email varchar(255) not null, firstname varchar(255) not null, lastname varchar(255) not null, password varchar(255) not null, primary key (user_id)) engine=InnoDB
Hibernate: alter table users drop index UKfnranlqhubvw04boopn028e6
Hibernate: alter table users add constraint UKfnranlqhubvw04boopn028e6 unique (username, email)
Hibernate: alter table users drop index UK_r43af9ap4edm43mmtq01oddj6
Hibernate: alter table users add constraint UK_r43af9ap4edm43mmtq01oddj6 unique (username)
Hibernate: alter table users drop index UK_6dotkott2kjsp8vw4d0m25fb7
Try like this:
#Entity
#Table(name="users",uniqueConstraints=#UniqueConstraint(columnNames={"user_id","username","email"}))
public class Users {
Apparently there's nothing wrong with this code. unique=true in #Column is a shortcut for #UniqueConstraint(columnNames = {"user_id"} and other particular constraints. You could only use one of them except for multiple unique constraints.
But if you are confused why the generated CREATE TABLE doesn't contain these unique constraints, check the log, you can find alter table commands just after CREATE TABLE.
In this case commands could be,
alter table users add constraint UK_some_id unique (username)
alter table users add constraint UK_some_id unique (email)
Hope this help.
I had the same problem here, set length to your unique columns.. It worked here. Do
#Column(name = "userName", length = 50, unique = true)
if you set unique in #Column
that's mean let your JPA provider create the database for you - it will create the unique constraint on the specified column.
But after creating a database, or you alter it once created,
then unique doesn't have any effect
in jpa the id is already unique so to make username and email unique add the statement
unique=true
to the corresponding #Column annotation.
PS : remember the unique cannot be null when insert new item you should drop the table before run the application and remove uniqueConstraints in table annotation
Set the column to have the fixed length make it work because mysql size for unique constrains limited to 1000 bytes
#Column(name = "username", length = 200, unique = true)
Use #javax.persistence.Column(unique = true).
Dont forget to restart your application.
&& you cant alter your column if there are still duplicated values in it.
drop your table, and recreate it with your generated ddl. (if ure not usin springboot)
&& if youre using springboot, go to your application.properties & set spring.jpa.hibernate.ddl-auto to update
This updates the schema if necessary.
The column needs to be non nullable for it to be unique!
#Column(unique = true) will NOT work
#Column(unique = true, nullable = false) will work
If the database or schema already exists before adding the uniqueConstraints then it won't work.
So drop the database or schema and run the application to create new database with these uniqueConstraints
DROP DATABASE database_name;
I have a user entity identified by two natural ids, something like
#Entity
#Table(name = "user", uniqueConstraints =
{ #UniqueConstraint(columnNames = "email"),
#UniqueConstraint(columnNames = "nick") })
public User()
{}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private id;
#Column(name = "email", unique = true, nullable = false, length = 31)
#NaturalId(mutable = true)
private String email;
#Column(name = "nick", unique = true, nullable = false, length = 31)
#NaturalId(mutable = false)
private String nick;
However, when I try to execute
session.byNaturalId(User.class).with(LockOptions.READ).using("email", "admin#mail.com").load();
it throws an exception
org.hibernate.HibernateException: Entity [pervasive.com.gmail.tigerjack89.forum.shared.model.entities.User] defines its natural-id with 2 properties but only 1 were specified
at org.hibernate.event.spi.ResolveNaturalIdEvent.<init>(ResolveNaturalIdEvent.java:75)
at org.hibernate.event.spi.ResolveNaturalIdEvent.<init>(ResolveNaturalIdEvent.java:52)
at org.hibernate.internal.SessionImpl$BaseNaturalIdLoadAccessImpl.resolveNaturalId(SessionImpl.java:2607)
at org.hibernate.internal.SessionImpl$NaturalIdLoadAccessImpl.load(SessionImpl.java:2722)
at pervasive.com.gmail.tigerjack89.forum.server.model.orm.StorageManager.getByNaturalId(StorageManager.java:217)
at pervasive.com.gmail.tigerjack89.test.local.MyHibernateTest.test1(MyHibernateTest.java:37)
at pervasive.com.gmail.tigerjack89.test.local.MyHibernateTest.main(MyHibernateTest.java:23)
Why is this? I think it's also due to the log of the SQL syntax generated by Hibernate. Indeed, it is strange (redundant) at this point and I think it's the cause of the exception
Hibernate:
alter table user
add constraint UK_t8tbwelrnviudxdaggwr1kd9b unique (email, nick)
Hibernate:
alter table user
add constraint UK_ob8kqyqqgmefl0aco34akdtpe unique (email)
Hibernate:
alter table user
add constraint UK_pvnbxcfihb58o5n2n1fnc7fh1 unique (nick)
EDIT: Reading the code again, I thought that the problem could be related to the #UniqueConstraints annotations. However, even if I try to remove one of them, Hibernate continues to genetate the above SQL syntax.
I would make the email address alone the NaturalId and then your query would work.
When two columns are identified as the NaturalId it creates a Composite Key.
Nick could still be used as a foreign Key.
You've a composite naturalId key (email,nick) so you can have multiples results with simple email arg.
you've to use
session
.byNaturalId(User.class)
.with(LockOptions.READ)
.using("email", "admin#mail.com")
.using("nick", "admin")
.load();
You can also use Metamodel
session
.byNaturalId(User.class)
.with(LockOptions.READ)
.using(User_.email.getName(), "admin#mail.com")
.using(User_.nick.getName(), "admin")
.load();
I am beginner in handling JPA with maven and JBOSS, with Restful to make my application I have the following problem arose me doing DEPLOY
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: com.company.test_resources_war_1.0-SNAPSHOTPU] Unable to build EntityManagerFactory
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: database.Photo column: fid_module (should be mapped with insert = \ "false \" update = \ "false \") "}}
Not that step, check all posles solutions, but did not find anything, can someone help me??
Thanks in advance
Below I show the SQL code in postgres that I have and I did the mapping.
I have three tables (activity, event and photo) where one of them (photo) refers to the other two (activity and event) but in a single column (photo.fid_module)
SQL Code (enginer database-->Postgresql):
CREATE TABLE activity (
id_activity integer not null,
name character varying(150),
description text,
CONSTRAINT id_activity_pk PRIMARY KEY (id_activity)
)
CREATE TABLE event (
id_event integer not null,
name character varying(150),
description text,
date timestamp without time zone,
CONSTRAINT id_event_pk PRIMARY KEY (id_event)
)
CREATE TABLE photo(
id_photo integer not null,
path character varying(150),
fid_module integer not null,
CONSTRAINT id_photo_pk PRIMARY KEY (id_photo),
CONSTRAINT fk_photo_activity FOREIGN KEY (fid_module)
REFERENCE activity (id_activity) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_photo_event FOREIGN KEY (fid_module)
REFERENCE event (id_event) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Now the mapping I did with the help of Netbenas and gave me the following code (I did the mapping for the three tables, but in presenting me the problem is in the class Photo.java).
#Entity
#Table(name = "photo")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "photo.findAll", query = "SELECT p FROM Photo p"),
#NamedQuery(name = "photo.findByFidPhoto", query = "SELECT p FROM Photo p WHERE p.fidphoto = :fidphoto"),
#NamedQuery(name = "photo.findByIdPhoto", query = "SELECT p FROM Photo p WHERE p.idphoto = :idphoto")})
public class Photo implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id_photo")
private Integer idPhoto;
#Column(name = "path")
private Recurso fidPath;
#JoinColumn(name = "fid_module", referencedColumnName = "id_activity")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private SliderWebHome fidModule;
#JoinColumn(name = "fid_module", referencedColumnName = "id_event")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Publicacion fidModule1;
public ModuloRecurso() {
}
.......
}
I am using JPA for persistence (but do mvn clean install and mvn jboss-as: deploy several pulls me hibernate dependencies) could anyone tell me what is my mistake or could solve this problem. Thank you.
You have two column mapped with the same name
#JoinColumn(name = "fid_module", referencedColumnName = "id_activity")
#JoinColumn(name = "fid_module", referencedColumnName = "id_event")
Change one of the name attribute!
Looking in your exception, you can read:
Repeated column in mapping for entity
As noted in another answer, your Java code specifies the same join-column name for two fields, which can't work.
If this Java code is generated by a netbeans mapping tool, as it seems from your note
Now the mapping I did with the help of Netbenas and gave me the following code ...
the bad Java mapping is probably caused by a bad combination of constraints in your SQL.
You have in your definition of the photo table:
CONSTRAINT fk_photo_activity FOREIGN KEY (fid_module)
REFERENCE activity (id_activity) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_photo_event FOREIGN KEY (fid_module)
REFERENCE event (id_event) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
which attempts to make the column fid_module a foreign key referencing activity and also a foreign key referencing event, which can't work.
If you need foreign keys from photo to both of those tables, you'll need to use two different columns.