Duplicate key value violates unique constraint with JPA in clustered heroku environment - java

We faced a problem in production which we could not reproduce so far. The error message is org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "token_pkey"
While it is clear what this error message is telling us, this error happened only once or better said during a time window of about 1 hour and until now and we cannot reproduce it.
We use Postgres 11.5 and did scale this Spring Boot App from 1 Node to 3 Nodes (and back to 1 Node). We are running this on Heroku. My current hypothesis is, that JPA does some caching of the next/last primary key value with #GeneratedValue(strategy = GenerationType.IDENTITY) and therefore if one node inserts a new row, the other node could still have the old value? If that is true, how can it be avoided?
Here is the code. Can you spot any problems with it?
create table token
(
id bigserial primary key,
user_id bigint unique references user_ (id),
access_token varchar(255),
code varchar(255),
expires_time_stamp bigint,
refresh_token varchar(255),
event_sync_token varchar(255)
);
Java JPA Entity Class
#Entity
public class Token {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
private String code;
private String accessToken;
private String refreshToken;
private Long expiresTimeStamp;
private String eventSyncToken;
}
Method creating the Token
public void initTokensFromCode(String code) {
// ... skipped some non relevant lines
Token token = googleTokenRepository.findByUserId(authenticationService.getCurrentUser().getId())
.orElse(new Token());
token.setUser(authenticationService.getCurrentUser());
token.setCode(code);
token.setAccessToken((String) jsonObject.get("access_token"));
token.setRefreshToken(refresh_token);
token.setExpiresTimeStamp(expiresTimestamp((long) jsonObject.get("expires_in")));
repository.save(token);
}
Repository (org.springframework.data.repository.CrudRepository)
public interface TokenRepository extends CrudRepository<Token, Long> {
Optional<Token> findByUserId(#Param("user_id") long userId);
}

Related

Entity not creating table in DB

I'm using Spring boot, and I Run this model.
package com.example.demo.Models;
import jakarta.persistence.*;
#Entity
#Table(name = "user")
public class UserModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = true)
private Long id;
private String name;
private String email;
private Integer priority;
/* Here are all the setters and getters*/
}
application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/dbdemo
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
Everything fine with Java.
Process finished with exit code 0
But in the console of my docker image of Postgres I get the following error:
ERROR: syntax error at or near "user" at character 14
STATEMENT: create table user(id bigserial not null, email varchar(255), name varchar(255), priority integer, primary key (id))
I'm not sure how to solve it, I'd appreciate any help.
create table public.user ( id serial not null,
email varchar(255),
name varchar(255),
priority integer,
primary key (id))
add the schema name before the table name
You need to specify the schema name in table and id fields like this:
#Table(name = "anomaly", schema = "schema_name")
public class Anomaly {
#Id
#SequenceGenerator(name = "id", allocationSize = 1, schema = "schema_name")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long anomalyId;
user is a reserved word in Postgresql thats why it show an error
use a different word like "users" "usersId".

Unable to add multiple entities in ManyToOne mapping due to Unique Key being created

I have been fighting this issue regarding two entities: User and ProfileInformation.
Each user should hold many ProfileInformation instances but I get the following error when creating a second ProfileInformation: Duplicate entry 'usernameInQuestion' for key 'UK_141232asdas78k552'.
When checking the database schema for ProfileInformation I noticed that the 'username' field was created as a unique key:
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_14113pekuxo8jklda2678k552` (`username`),
CONSTRAINT `FKt3ewldgdc9d75ji6j5ii7cbrc` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
I have a very similar relation between ProfileInformation and another entity with the same code (except the naming of the classes and #JoinColumn(name = "profileinformation_id") ) and this doesn't happen:
`profileinformation_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FKl5ww2gn1e1wgqcuvraa6p70ya` (`profileinformation_id`),
CONSTRAINT `FKl5ww2gn1e1wgqcuvraa6p70ya` FOREIGN KEY (`profileinformation_id`) REFERENCES `profileinformationtable` (`id`)
This is part of a bigger system and between all the confusion of making this work I attempted to replicate the already working similar situations.
public class ProfileInformation
[ ... ]
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name="username")
private User user;
The User class is the following (although from what I gather this really doesn't seem to matter much):
public class User
[ ... ]
#Id
#Column(name = "username", nullable = false)
private String username;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<ProfileInformation> infos;

How do I properly map entities where a primary key is composed of two foreign keys, one of which is composite itself?

I'm having a hard time trying to figure out how to properly do ORM on a certain database design.
My schema consists of three tables: a user table, a review table, and a vote table. Users can publish reviews for albums, and they can also assign a positive or negative rating to any review. Albums are provided from an external API, so their table is missing from the schema, but their IDs are referenced.
A user primary key simply consists of their username. A review primary key is composed of the reviewer's username, which is a foreign key, and the reviewed album ID. Finally, a vote primary key is composed of the voter's username, again a foreign key, and the voted review's primary key, consisting, as said earlier, of the reviewer's username and the reviewed album ID.
A user can publish a review for each individual album, and also can assign a vote for each individual review.
This is the ER model representing the schema:
To map the entities IDs, I'm using the #IdClass annotation, but I'm not sure I'm headed in the right direction. I also tried using the #EmbeddedId annotation, but the result is the same.
This is what my entities classes look like so far:
#Entity
public class User implements Serializable {
private static final long serialVersionUID = 1;
#Id #Column(name = "username")
private String username;
#Column(unique = true, nullable = false)
private String email;
#Column(name = "password", nullable = false)
private String password;
#Temporal(TemporalType.TIMESTAMP) #Column(name="signUpDate", nullable = false)
private Date signUpDate;
// empty constructor, getters, setters, equals and hashCode implementations
}
#Entity #IdClass(ReviewId.class)
public class Review implements Serializable {
private static final long serialVersionUID = 1;
#Id #ManyToOne #JoinColumn(name = "reviewerUsername", referencedColumnName = "username")
private User reviewer;
#Id #Column(name = "reviewedAlbumId")
private Long reviewedAlbumId;
#Column(name = "content", nullable = false, length = 2500)
private String content;
#Column(name = "rating", nullable = false)
private Integer rating;
#Temporal(TemporalType.TIMESTAMP) #Column(name = "publicationDate", nullable = false)
private Date publicationDate;
// empty constructor, getters, setters, equals and hashCode implementations
}
#Entity #IdClass(VoteId.class)
public class Vote implements Serializable {
private static final long serialVersionUID = 1;
#Id #ManyToOne #JoinColumn(name = "voterUsername", referencedColumnName = "username")
private User voter;
#Id #ManyToOne #JoinColumns({
#JoinColumn(name = "reviewerUsername", referencedColumnName = "reviewerUsername"),
#JoinColumn(name = "reviewedAlbumId", referencedColumnName = "reviewedAlbumId")
})
private Review review;
#Column(name = "vote") // #todo add attribute nullable = false
private Boolean vote;
// empty constructor, getters, setters, equals and hashCode implementations
}
These are my ID classes:
public class ReviewId implements Serializable {
private static final long serialVersionUID = 1L;
private User reviewer;
private Long reviewedAlbumId;
// empty constructor, getters, setters, equals and hashCode implementations
}
public static class VoteId implements Serializable {
private static final long serialVersionUID = 1L;
private User voter;
private Review review;
// empty constructor, getters, setters, equals and hashCode implementations
}
And here is the content of the MySQL script used to generate the schema:
DROP SCHEMA IF EXISTS albumReviewsDatabase;
CREATE SCHEMA albumReviewsDatabase;
USE albumReviewsDatabase;
CREATE TABLE user (
username VARCHAR(20) PRIMARY KEY,
email VARCHAR(254) NOT NULL UNIQUE,
password CHAR(60) NOT NULL,
signUpDate TIMESTAMP NOT NULL DEFAULT now()
) ENGINE = INNODB;
CREATE TABLE review (
reviewerUsername VARCHAR(20) NOT NULL,
reviewedAlbumId BIGINT(20) NOT NULL,
content TEXT NOT NULL,
rating SMALLINT UNSIGNED NOT NULL,
publicationDate TIMESTAMP NOT NULL DEFAULT now(),
CHECK (rating >= 0 AND rating <= 10),
PRIMARY KEY (reviewerUsername, reviewedAlbumId),
FOREIGN KEY (reviewerUsername) REFERENCES user(username)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = INNODB;
CREATE TABLE vote (
voterUsername VARCHAR(20) NOT NULL,
reviewerUsername VARCHAR(20) NOT NULL,
reviewedAlbumId BIGINT(20) NOT NULL,
vote BOOLEAN NOT NULL,
PRIMARY KEY (voterUsername, reviewerUsername, reviewedAlbumId),
FOREIGN KEY (voterUsername) REFERENCES user(username)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (reviewerUsername, reviewedAlbumId) REFERENCES review(reviewerUsername, reviewedAlbumId)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = INNODB;
I'm currently using OpenJPA as the persistence provider on a TomEE webprofile instance, and the used JPA version is 2.0.
Clearly I am misunderstating something about JPA's ORM, because when I deploy my application containing those entities I get the following exception:
<openjpa-2.4.2-r422266:1777108 fatal user error> org.apache.openjpa.util.MetaDataException: The id class specified by type "class application.model.Review" does not match the primary key fields of the class. Make sure your identity class has the same primary keys as your persistent type, including pk field types. Mismatched property: "reviewer"
The exception is thrown because of the Review class mapping, and not the Vote class; however, I am sure that by solving the issue on the Review class, the same will reappear for Vote.
I'd prefer to get away with using the #IdClass annotation instead of #EmbeddedId, but whichever of the two I will end up using is not an issue.
These relationships are "derived identities"; so your ID classes should look like this (note the types of the foreign key fields differ from the types of their corresponding entity fields):
public class ReviewId implements Serializable {
private static final long serialVersionUID = 1L;
private String reviewer; // matches name of #Id attribute and type of User PK
private Long reviewedAlbumId;
// ...
}
public static class VoteId implements Serializable {
private static final long serialVersionUID = 1L;
private String voter; // matches name of #Id attribute and type of User PK
private ReviewId review; // matches name of #Id attribute and type of Review PK
// ...
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
Also, as a side note, #IdClass is a bit Old School while #EmbeddedId is cleaner, eliminating the code duplicated across the entity and its key.

Spring boot REST CRUD - how to POST an entitiy with a one-to-one relationship?

I have a really simple domain model: An 'Alert' has one 'Type' and one 'Status'.
This is my schema:
create table `price_alert_status` (
`id` bigint(20) not null,
`status_name` varchar(64) not null,
primary key (`id`),
unique key (`status_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_status` values (0, 'INACTIVE');
insert into `price_alert_status` values (1, 'ACTIVE');
create table `price_alert_type` (
`id` bigint(20) not null,
`type_name` varchar(64) not null,
primary key (`id`),
unique key (`type_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_type` values (0, 'TYPE_0');
insert into `price_alert_type` values (1, 'TYPE_1');
create table `price_alert` (
`id` bigint(20) not null auto_increment,
`user_id` bigint(20) not null,
`price` double not null,
`price_alert_status_id` bigint(20) not null,
`price_alert_type_id` bigint(20) not null,
`creation_date` datetime not null,
`cancelation_date` datetime null,
`send_periodic_email` tinyint(1) not null,
`price_reached_notifications` tinyint(4) default '0',
`approximate_price_notifications` tinyint(4) null,
`notify` tinyint(1) not null default '1',
primary key (`id`),
constraint `FK_ALERT_TO_ALERT_STATUS` foreign key (`price_alert_status_id`) references `price_alert_status` (`id`),
constraint `FK_ALERT_TO_ALERT_TYPE` foreign key (`price_alert_type_id`) references `price_alert_type` (`id`)
) engine=InnoDB default charset=utf8;
Now, I'm going to show the respective entity classes:
Alert.java:
// imports omitted
#Entity
#Table(name = "price_alert")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"creationDate"},
allowGetters = true)
public class Alert implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private double price;
#OneToOne
#JoinColumn(name = "price_alert_status_id", nullable = false)
private Status status;
#OneToOne
#JoinColumn(name = "price_alert_type_id", nullable = false)
private Type type;
#Column(nullable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
#CreatedDate
private Date creationDate;
#Column(nullable = true)
#Temporal(TemporalType.TIMESTAMP)
private Date cancelationDate;
private boolean sendPeriodicEmail;
#Column(nullable = true)
private byte priceReachedNotifications;
#Column(nullable = true)
private byte approximatePriceNotifications;
private boolean notify;
// getters and setters omitted
}
Status.java:
//imports omitted
#Entity
#Table(name = "price_alert_status")
public class Status implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "status_name")
#NotBlank
private String name;
//getters and setters omitted
}
Type.java:
//imports omitted
#Entity
#Table(name = "price_alert_type")
public class Type implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "type_name")
#NotBlank
private String name;
//getters and setters omitted
}
Repositories:
AlertRepository.java:
//imports omitted
#Repository
public interface AlertRepository extends JpaRepository<Alert, Long> {
}
StatusRepository.java:
//imports omitted
#Repository
public interface StatusRepository extends JpaRepository<Status, Long> {
}
TypeRepository.java:
//imports omitted
#Repository
public interface TypeRepository extends JpaRepository<Type, Long> {
}
Now, the main controller:
AlertController.java:
#RestController
#RequestMapping("/api")
public class AlertController {
#Autowired
AlertRepository alertRepository;
#Autowired
StatusRepository statusRepository;
#Autowired
TypeRepository typeRepository;
#GetMapping("/alerts")
public List<Alert> getAllAlerts() {
return alertRepository.findAll();
}
#PostMapping("/alert")
public Alert createAlert(#Valid #RequestBody Alert alert) {
return alertRepository.save(alert);
}
#GetMapping("/alert/{id}")
public Alert getAlertById(#PathVariable(value = "id") Long alertId) {
return alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
}
#PutMapping("/alert/{id}")
public Alert updateAlert(#PathVariable(value = "id") Long alertId,
#Valid #RequestBody Alert alertDetails) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alert.setApproximatePriceNotifications(alertDetails.getApproximatePriceNotifications());
alert.setCancelationDate(alertDetails.getCancelationDate());
alert.setNotify(alertDetails.isNotify());
alert.setPrice(alertDetails.getPrice());
alert.setPriceReachedNotifications(alertDetails.getPriceReachedNotifications());
alert.setSendPeriodicEmail(alertDetails.isSendPeriodicEmail());
alert.setUserId(alertDetails.getUserId());
// TODO: how to update Status and Type?
Alert updatedAlert = alertRepository.save(alert);
return updatedAlert;
}
#DeleteMapping("/alert/{id}")
public ResponseEntity<?> deleteAlert(#PathVariable(value = "id") Long alertId) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alertRepository.delete(alert);
return ResponseEntity.ok().build();
}
}
So, I have two questions:
How can I create an alert, via POST, and associate existing status and type?
For example, this would be my cURL. I'm trying to indicate that I want to associate to this new alert the 'Status' and 'Type' existing objects, passing their respective IDs:
curl -H "Content-Type: application/json" -v -X POST localhost:8080/api/alert -d '{"userId": "1", "price":"20.0", "status": {"id": 0}, "type": {"id": 0}, "sendPeriodicEmail":false,"notify":true}'
Like the first question, how can I update an Alert, associating new existing 'Status' and 'Type' objects?
Thanks!
I think there is no out-of-the-box way to achieve this with a single POST request. The approach I see used most of the time is making an initial request to create the Alert, and subsequent requests to associate Status and Type.
You could take a look at how Spring Data Rest approaches the problem here:
https://reflectoring.io/relations-with-spring-data-rest/
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.association-resource
I'm not the biggest fan of Spring Data Rest though, since it forces some things (like hateoas) down your throat
,but you can easily implement the same approach manually.
You could argue that it's overkill to have separate calls to set the status and type of an alert, being both actually part of the alert, and I may agree actually. So if you don't mind slightly deviating from the rigidity of what people mostly call REST APIs (but are more like CRUD interfaces exposing your data model), it could make sense to take an AlertDto (with status and type ids) in your alert creation endpoint, retrieve status and type with these ids and create the Alert object you will eventually store.
Having said all of the above, I would avoid having tables for Status and Type if all they have is a name. I would have these names in the Alert itself and no relationships at all. Yes it may occupy more space on the database, but disk space is hardly a problem nowadays, and I'm guessing status and type are usually short strings.
I admit I am specially biased against this id-name lookup table pattern because we have dozens of these in one of our projects at work and they do nothing but generate a lot of useless code and complicate the DB schema.

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.

Categories

Resources