How to implement deletion of user? - java

I’m developing my test project that presents library. As far as I develop it, I meet questions continuously.
Could you help me with one of them?
My model design includes User and LeasingHistory entities, so when user borrows a book LeasingHistory table fills with corresponding record.
create table USER
(
ID BIGINT not null serial primary key,
ADDRESS VARCHAR(255) not null,
DOCUMENT_ID VARCHAR(255) not null,
FIRST_NAME VARCHAR(255) not null,
SUR_NAME VARCHAR(255) not null
);
create table LEASING_HISTORY
(
ID BIGINT not null serial primary key,
BOOK_INSTANCE_ID BIGINT not null,
USER_ID BIGINT not null,
START_DATE DATE(10),
ARRANGED_END_DATE DATE(10),
ACTUAL_END_DATE DATE(10),
foreign key (BOOK_INSTANCE_ID) references BOOK_INSTANCE,
foreign key (USER_ID) references USER
);
Now I have to implement user deletion. How should I do it? How is it implemented in Google, Facebook or in StackOverFlow?
I don’t think that delete orphans is appropriate strategy in this case because history shouldn’t be cleaned up.
I suppose I can add isActive field to User table. In this case I can clear personal info and assign isActive = false when user presses “Delete Account” button.
What are your thought about it?
UPDATE:
I think that an user should have a possibility to recover his account thus in this case #killjoy option is better. Can you check me, do I understand it correctly? I've introduced additional table UserInternalData and in case of user deletion I will clear all data (besides id) from User entity and set isDeleted to true and dateStamp to current date in 'UserInternalData'
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#NoArgsConstructor
#AllArgsConstructor
#Getter
#EqualsAndHashCode
#ToString
class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String surName;
#JsonFormat(pattern="yyyy-MM-dd")
private LocalDate dateOfBirth;
}
#Entity
#NoArgsConstructor
#Getter
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public final class User extends Person {
#Builder
public User(Long id, String firstName, String surName, LocalDate dateOfBirth, String documentId, String address) {
super(id, firstName, surName, dateOfBirth);
this.documentId = documentId;
this.address = address;
}
private String documentId;
private String address;
}
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#EqualsAndHashCode
#ToString
public final class UserInternalData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
private User user;
boolean isDeleted;
#JsonFormat(pattern="yyyy-MM-dd")
private LocalDate dateStamp;
}
Am I right?

A few approaches to consider.
Add an isActive column (or equivalently, an isDeleted column) to the USER table. Clear (or set) the bit when the user is deleted. You probably also want to wipe out other sensitive info. This leaves open the possibility of reactivating deleted users in the future.
Replace the USER_ID field in all of the affected rows in LEASING_HISTORY with a special reserved ID that represents a deleted user (possibly with a name like "Deleted User").
As per #killjoy, normalize the two tables by adding a third table that relates USER_ID to BOOK_INSTANCE_ID, and remove the USER_ID column from LEASING_HISTORY. Then you can simply delete the affected rows from this third table without losing the book leasing history.
Addendum
On second thought, (3) might not be such a good approach. The idea was to separate the USER_ID foreign key out into a separate table, but this does not solve the dangling deleted foreign key problem.
Perhaps a more workable approach is:
Do not constrain USER_ID to be a foreign key. That way, you can safely delete USER rows without needing any cleanup at all. Of course, you still need to insure referential integrity (programmatically) at the time you create each new LEASING_HISTORY row.
I prefer (1) because you are not deleting any user history, just the sensitive user info.

Related

JPA (Hibernate) OneToOne Impedance Mismatch

I am studying JPA and Hibernate to build a Spring Boot webapp, and there's something that bugs me.
It is related to impedance mismatch in One To One relationships.
Let's say I have two domain entities, A and B, that have a one to one relationship.
This is what I would like to have:
in the Java Classes, I would like to have A hold a reference to B;
in the Database, I would like to have the table for "b" objects have a column with the foreign key to "a" keys.
Is there a way to do this with JPA and Hibernate in Spring Boot?
I report here the problem with real-world classes and code.
In my domain I have basically people and signatures.
Therefore, in my Java Code, I have Person #Entity and a Signature #Entity.
In Java, it makes sense to have the Person object own a Signature object.
So, here is the Person class:
#Entity
#Table(name = "people")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name="first_name")
#NotNull
#NotBlank
#Size(min = 3, max = 100)
private String firstName;
#Column(name="last_name")
#NotNull
#NotBlank
#Size(min = 3, max = 100)
private String lastName;
// ??? which annotations?
private Signature signature;
// I omit constructors, getters and setters for brevity
And this is the Signature class:
#Entity
#Table(name = "signatures")
public class Signature {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name="name")
#NotNull
#NotBlank
private String name;
#Column(name="type")
#NotNull
private String type;
#Column(name="image")
#NotNull
#NotEmpty
#Lob
#Type(type="org.hibernate.type.MaterializedBlobType")
private byte[] image;
// I omit constructors, getters and setters for brevity
As you can see, Ids should be generated automatically, and I would like my Person class to have a reference to its Signature, and not vice-versa.
On the contrary, this is the DB schema I'd like to use:
CREATE SCHEMA signatures;
CREATE TABLE signatures.people (
id BIGSERIAL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE signatures.signatures (
id BIGSERIAL,
type VARCHAR[16] NOT NULL,
name VARCHAR[100] NOT NULL,
image BYTEA NOT NULL,
person BIGINT NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_signature_people FOREIGN KEY (person) REFERENCES signatures.people (id) ON DELETE CASCADE ON UPDATE CASCADE
);
As you can see here, I would like the Signatures table to have a foreign key to the People table, and not vice-versa.
Is this possible?
The #OneToOne mapping is a bit of an odd-ball. When the relationship is bi-directional, you can decide the owning side, but in a unidirectional relationship the declaring entity will always be the one with the foreign key.
One option is to make the relationship bi-directional, but hide the other direction in code.
The other way is to use a #OneToMany mapping, which will create the foreign key in the "many" table. This is also consistent with the database schema, as multiple child table rows could then link to the same parent row at least theoretically, especially if there's not a constraint to make sure they're unique.

How to separate data into two tables with Spring Boot?

Now I have two tables, the first table called StudentBase and has three columns: id, firstname and lastname. The second table called ResearchAssistant and has two columns: id and course. I designed the tables like this because there are different kinds of students and research assistant is one of them. The two table could be joint together with the primary key id.
I'm writing an endpoint /researchAssistant and take following content as request body of POST method.
{
"firstname":"Jack",
"lastname":"Peter",
"course":"MATH"
}
What I want is that saving firstname and lastname into StudentBase table and save course into ResearchAssistant table. And generate a same id for both.
The first idea comes to my mind is building 3 model classes: StudentBase(id, firstname, lastname), ResearchAssistant(id, course) and ResearchAssistantMixed(firstname, lastname, course). I use ResearchAssistantMixed class as the request body class. After getting the data I will seperate it into a new StudentBase object and a ResearchAssistant object, then I store them seperately.
This process seems really stupid and the performance should be quite low. Do you have some better ideas? How does Spring Boot deal with such cases? Thank you!
This is a database problem and not a spring-boot problem. This is how I would approach (I'm assuming you're using some relational DB like MySql and hibernate for ORM):
Database Tables:
student_base
- id (primary key)
- first_name
_ last_name
research_assistant
- id (primary key)
- student_base_id (foreign key referencing id of student_base)
- course
You can now have equivalent entity classes in Java (for hibernate):
#Entity
#Table(name = "student_base")
public class StudentBase {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
// getters and setters
}
#Entity
#Table(name = "research_assistant")
public class ResearchAssistant {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne(optional = false)
#JoinColumn(name = "student_base_id")
private StudentBase studentBase;
#Column(name = "course")
private String course;
// getters and setters
}
Now in your DAOs, you don't need to do much, just persist a student_base record and use the returned object to persist a research_assistant record. For example:
StudentBase studentBase = persist(new StudentBase(1, "abc", "xyz");
persist(new ResearchAssistant(1, studentBase, "pqr");
You can (and should) have two separate classes to accept the request object of the post API (don't use entity classes to accept request data).

OneToOne bidirectional mapping foreign key auto fill

I have a relationship one-to-one between two tables, but the foreign key is on the one I don't need to map, the DBA did this in favor of future changes.
Let's imagine we have User and Address, today every user has only one address, and it will be mapped this way, but DBA believe in the future it could be a one to many mapping, so the foreign key of user is on the address, but the application have instances of users, which is important to fetch address automatically.
We did it right, as follow:
#Entity
#Table(name = "user")
class User {
#Id
#Column(name = "user_id")
private Long id;
//...
#OneToOne(cascade = CascadeType.MERGE, mappedBy = "user")
private Address address; // this attribute is crucial
}
#Entity
#Table(name = "address")
class Address {
#Id
#Column(name = "address_id")
private Long id;
#OneToOne
#JoinColumn(name = "user_id")
private User user; // this attribute is not needed for the business at all, but mappedBy requires it
//...
}
Database:
-- SQL:
CREATE TABLE user
(
user_id INT NOT NULL,
-- ...
CONSTRAINT user_pk PRIMARY KEY (user_id)
);
CREATE TABLE address
(
address_id INT NOT NULL,
user_id INT NOT NULL,
-- ...
CONSTRAINT address_pk PRIMARY KEY (address_id),
CONSTRAINT address_user_id_fk FOREIGN KEY (user_id) REFERENCES user (user_id),
CONSTRAINT address_user_id_uk UNIQUE (user_id) -- it says it's a one to one relation for now, if in the future it won't be anymore, just remove this constraint
);
The problem is when save a instance of user with a new instance of address, the user's attribute of address is null, so I was expecting Hibernate was smart enough to set it the value from the user's instance it comes from.
I'd been looking for a solution for a couple of days, but still didn't find how to solve this, meanwhile I'm setting the value manually, but I expect I don't need to do so.
The standard solution is to properly update both sides of the bidirectional association (although only the owning side needs to be updated for the association to be saved to the database). Add to the Address setter in the User class:
public void setAddress(Address address) {
this.address = address;
address.setUser(this);
}
Also, you may want to extend cascading options for the address property to include PERSIST as well, so that it is always persisted together with its user:
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "user")
private Address address;
Then you can set an address to a user and persist both:
user.setAddress(address);
session.persist(user);
If "private User user" is not needed, delete it and delete also mappedBy in 'User' entity. Use a uni-directional relation.
In the case of your example, mappedBy means that the owner of the association is the 'Address' entity, so save the instance of Adress and not User. Like :
adress.setUser(user);
session.save(adress);
You need to add CasecadeType.PERSIST to make the creation Address casecade with creation of User.
In your java code, you need to do:
user.setAddress(address);
address.setUser(user);
session.persist(user);
Then your user will be created with an address.
If when you just want to read the Address of a new created User, then you need to do :
// your code to persist a new User and Address
session.flush();
session.refresh(user);
If it's not what you want, then you need to share your own Java code and give a detailed description on what you're expecting.

JPA - Mapping OneToMany association between the same table using an intermediate table

I'm creating an application where one large aspect is the ability for users to share content with friends. I'm trying to represent this in the object model and I'm having trouble getting the association to work properly. I'm using a mapping table that records the friender and the friendee, both of which are represented by the primary key (id) of the user. A user can have many friends, and also be referenced by other users. This is what the schema looks like:
Users:
int user_id (PK)
varchar(32) email
varchar(64) password
Users_Map:
int users_map_id (PK)
int friendee_id (FK references users(user_id))
int friender_id (FK references users(user_id))
And this is how I have the User entity set up:
#Data
#Entity
#Table(name = "users")
public class User extends AbstractPersistable<Long> {
#Id
#Column(name = "user_id")
private Long id;
#Column
private String email;
#Column
private String password;
#OneToMany
#JoinTable(name = "users_map",
joinColumns = { #JoinColumn(name = "friender_id") },
inverseJoinColumns = { #JoinColumn(name = "friendee_id") })
private List<User> friends;
}
I run into the following error when deploying the application:
org.hibernate.AnnotationException: A Foreign key refering
com.x.webapp.data.entity.User from
com.x.webapp.data.entity.User has the wrong number of
column. should be 2
I've tried quite a few other configurations, including adding a "referencedColumnName" attribute to each #JoinColumn, but they have also yielded errors. I'm also not entirely sure whether the schema I currently have is the best way to go about mapping users together.
I appreciate any help!
Removing the extension of AbstractPersistable fixed the problem - that contained an #Id reference and clashed with the #Id reference I put inside of User.

HIbernate one-to-one annotation isn't generating foreign key GerericGenerator in dependent table

I am trying to create OneToOne relation between a Person and Auth table. The problem is when the DB table "Auth" is generated, I'm not seeing the foreign key in the AUTH table that should reference Person. The object is to have the Auth table use the same Primary Key of the Person Table.
#MappedSuperclass
public abstract class DomainBase {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Version
#Column(name="OPLOCK")
private Integer version;
}
#Entity
#Table(name = "person")
public class Person extends DomainBase {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="auth_id")
private Auth auth;
}
#Entity
public class Auth {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="person")
})
#Column(name="person_id")
private int personId;
---------------------------------
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Person person;
}
My Database scripts after hibernate DB generation.
CREATE TABLE auth
(
person_id integer NOT NULL,
activate boolean,
activationid character varying(255),
last_login_attempt_date timestamp without time zone,
last_login_attempt_timezone character varying(255),
last_login_date timestamp without time zone,
last_login_timezone character varying(255),
nonlocked boolean,
num_login_attempts integer,
CONSTRAINT auth_pkey PRIMARY KEY (person_id),
CONSTRAINT uk_d68auh3xsosyrjw3vmwseawvt UNIQUE (activationid)
)
WITH (
OIDS=FALSE
);
ALTER TABLE auth
OWNER TO postgres;
It seems that the problem is you declare twice the #OneToOne annotation between "person" table and "auth" table, without specify the relation between them. Take a look at the hibernate documentation, at the point 2.2.5.1, there is some examples about using one-to-one association.
For me, the best way is to set up the association in one table, the one that declare the foreing key column, and to use the mappedBy parameter in the other object. In your code, this will be :
#Entity
#Table(name = "person")
public class Person extends DomainBase {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="auth_id")
private Auth auth;
}
#Entity
public class Auth {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="person")
})
#Column(name="person_id")
private int personId;
#OneToOne(mappedBy = "auth")
private Person person;
....
}
This is the second example in the hibernate documentation, introduce just after the sentence "In the following example, the associated entities are linked through an explicit foreign key column". I tested this code, and the "auth_id" column appeared.

Categories

Resources