Spring boot api how to prevent duplicate data creation - spring

I have a POST API to create an entity called Person, and I check if person with the name and address exist or not, if not i create a person entity:
class Person {
UUID id
String name;
String address
}
public void createPerson(String name, String addr){
Person p = repository.findPersonByNameAndAddress(name, addr);
if (p != null) {
repository.create(name, addr);
}
}
If a client calls the POST with same data at the same time I will end up creating two person with same name and address (but different ID). How can i prevent this from happening?
I am using spring boot + JPA/Hibernate + postgres
Thanks!

Such constraints can be enforced only at the database layer. They cannot be handled at the application layer. If, as per your data model, two people cannot have the same name and address, you can add a unique constraint on (name, address) in your database.
ALTER TABLE person
add CONSTRAINT person_name_address UNIQUE (name, address);
With such a constraint, one of the two API calls will result in a SQLIntegrityConstraintViolationException which you can handle accordingly.

You can fix it by either of two ways:
Define a unique key constraint for both name and address
Find person by name and address by ignoring case using existsByNameIgnoreCaseAndAddressIgnoreCase
class PersonDao {
public void createPerson(String name, String addr) {
boolean isExists = repository.existsByNameIgnoreCaseAndAddressIgnoreCase(name, addr);
if (!isExists) {
PersonEntity person = new PersonEntity();
person.setName(name);
person.setAddress(addr);
repository.save(person);
}
}
}

Related

How to access PostgreSQL RETURNING value in Spring Boot DAO?

I want to return the auto-generated id of entity. PostgeSQL is able to automaticaly selects certain column via RETURNING, but I have a hard time trying to find how to retrieve this value in Spring Boot.
I would want something like:
public int createUser(User user) {
String sql = "INSERT INTO user (name, surname) VALUES (?,?) RETURNING id";
return jdbcTemplate.update(sql,
user.getName(),
user.getSurname(),
resultSet -> resultSet.getInt("id")
);
}
I know it's straightforward in Hibernate, then whether you use a Repository class or an EntityManager, the save method returns the saved entity, so you can just do:
int id = userRepository.save(user).getId();
Or is there a reason you want to persist it the way you do?

Posting a resource with ManyToOne relationship

This question is primarily on How to design a rest API and whats the best practice. I am trying to figure out REST endpoint for POST service, consider below sample code:
class Person {
String name;
#OneToMany
List<Address> addresses;
}
class Address {
String someAttribute;
#ManyToOne
Person person;
}
To add a new Address the rest endpoint could look like POST api/**v1**/address. The main problem is with the RequestBody since we will only have the id of Person so we need to pass the json like below:
{
"someAttribute": "someValue"
"personId": 1
}
And in Controller method, we can't simply use #RequestBody Address address as parameter to Rest endpoint method as we do not have personId in Address class.
I would like to understand whats the best practice here and how should the Rest endpoint be?
PS: Above code is a dummy code just to explain question easily.
update
to avoid confusion I have updated endpoint url.
Please go through #PathVariables annotations in Spring API .
Using #PathVariables you get the person_id and address_id from URL.
Creates a person (initially with n address)
POST api/{api_version}/person
Updates a person (only update Person details not address)
PUT api/{api_version}/person/{person_id}
Add a new address to person
POST api/{api_version}/person/{person_id}/addresses
Updates an existing person address
PUT api/{api_version}/person/{person_id}/addresses/{address_id}
Class Structure
class Address {
Integer id;
String someAttribute;
}
class Person {
Long id;
String name;
#OneToMany
List<Address> addresses;
}
Sample Resource Implementation
#RequestMapping(value="api/{api_version}/person/{person_id}/addresses/{address_id}", method = RequestMethod.PUT)
public ResponseEntity<ApiCustomResponse> updateAddress(
#PathVariable("person_id") Long person_id,
#PathVariable("address_id") Integer address_id, #RequestBody Address address) {
...
}

Spring-boot jpa how to find entity with max value

Lets tell I have two tables.
CREATE TABLE user (ID int AUTO_INCREMENT,PRIMARY KEY (ID));
CREATE TABLE points (ID int AUTO_INCREMENT, user_id int, points int,PRIMARY KEY (ID));
How can I use spring-boot jpa to request user and max points like this?
select u.ID,max(p.points) from user u, points p where u.id=p.user_id
Or any alternatives to solve this kind of problems?
Assuming you have a Repository of User:
public class User {
private int id;
private List<Point> points;
...
}
With a relationship to the Points object:
public class Point {
private int id;
private User User;
private int points;
...
}
I haven't tested, but you should be able to do:
User findFirstByIdOrderByPointPointsDesc(int userId)
Similar to example 18 in the docs.
The only problem you have, regardless of the query or Spring Data, is if you have two users with the same point values. If you need more logic around tie-breaking, it might be more worth it to write a #Query (with your query, plus the extra tie-breaking logic) or a #NativeQuery.
I usually create a class to hold result such as
public class Result {
private User user;
private int votes;
// getters and setters
}
And write a custom query in the repository to fetch the data
#Query(value = "SELECT new com.package.Result (u, MAX (p.points) )
FROM user u
JOIN points p
ON u.id = p.user_id
GROUP BY u")
List<Result> getPointsPerUser();
Replace com.package.Result with appropriate path to the Result class.
Below method can be written in Repo and used as Transaction as in dao layer, which will be accessible from service layer.
#Query(value = "SELECT max(transactionId) FROM TransactionPayloadInfo")
int getMaxTransactionId();
create a model of data.
public class Data {
private int id;
private int maxPoints;
// getters and setters method
}
And write your query like this for getting model of Data.
#Query(select packagename.Data(u.ID,max(p.points) ) from user u, points p where u.id=p.user_id)
Data findUserWithMaxVots();

Prevent duplicate child entries while saving parent entity in spring boot spring data jpa involving multiple thread access

We have a spring boot application where we are trying to save information related to notifications received from third-party systems. The information is saved only when there exists a subscription for a specific kind of notification.
In short, we have three different entities present.
1) A subscription entity which is a combination of
a) a subscriber or User
b) a subject on which the subscription is taken
c) type of notification on which a subscriber took the subscription with respect to a subject.
2) A subscriber entity
3) a subject entity
While implementing a save subscription scenario in spring data jpa we are facing an issue related to the duplication of data.
The way the functionality is envisioned we get the information related to both subject, subscriber, and type as part of the notification subscription post message.
{
"notificationTypeCodes" : [""],
"subjectId" : "Person1",
"subscriberId" : "USER1"
}
we then save this information in three different tables 1) subscription table
2) subjectId 3) subscriberId
The subscription table contains the relationship columns containing combination of id wrt ( notificationTypeCodes,subjectId,subscriberId).
The data wrt these ids are placed in their respective tables.
we are populating all three tables in the same transaction using the cascading approach. In order to prevent duplicate values being entered in any of the tables, we tried doing a getsubscritopn before save and set up a condition statement stating that it subscription does not exist them only save the subscription
subscription object = fetchsubscription();
if(subscription is not present)
{
save subscritopm)
}
else{
response stating subscritpion exists
}
But in case of multithreading scenarios, our check is falling as multiple threads are entering the if block before the subscription is saved and duplicate entries are getting crated in all three tables.
we cannot create constraints on a table as we can have scenarios where
different users(subscribers) can subscribe on same subjects and if we have constraints on a subject table this valid scenario might be rolled back.
Is there a way that we can handle the duplicate scenario the DB level. where we can throw a unique constraint exception. if we are storing a duplicate subscription.
Note: we don't have any constraints on the table as of now.
2) we don't want to use synchronized block in the service class
It would be great if someone can provide some insite into the same.
Thanks
in advance
I have now tried to explain the same problem with books publisher and bookspublisher entity
**************** bookspublisher entity************
package com.hellokoding.jpa.model;
import javax.persistence.*;
#Entity
#Table(name = "book_publisher")
public class BookPublisher
{
#Id
private int id;
public BookPublisher() {
}
public Book getBook()
{
return book;
}
public void setBook( Book aBook )
{
book = aBook;
}
public int getId()
{
return id;
}
public void setId( int aId )
{
id = aId;
}
public Publisher getPublisher()
{
return publisher;
}
public void setPublisher( Publisher aPublisher )
{
publisher = aPublisher;
}
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
#JoinColumn(name = "book_id")
private Book book;
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
#JoinColumn(name = "publisher_id")
private Publisher publisher;
}
Service to persists bookspublisher data.
The issue is when I am persisting the BookPublisher entity in multithreading environment duplicate data is getting created in both books and publisher tables.
The use case states that only the very first time when a new book or publisher comes it should be persisted in rest of the calls no duplicate entries should be allowed in the tables.
#Service
public class BookPublisherService
{
#Autowired
private BookPublisherRepository bookpublisherRepository;
#Transactional
public void saveSubscription( BookPublisherRequest aRequest )
{
String bookName= aRequest.getBook();
String publisher= aRequest.getPublisher();
BookPublisher subscription = new BookPublisher();
subscription.setBook( new Book( bookName) );
subscription.setPublisher( new Publisher( publisher ) );
bookpublisherRepository.save( subscription );
}
}
Is it possible that if book and publisher values already exists then the same id's are provided to the publisherbook entity and by having unique constrain on that single table(publisherbook ) i can throw publishbook already exists message.
These two statements seem contradictory:
we cannot create constraints on a table as we can have scenarios where
different users(subscribers) can subscribe on same subjects and if we have constraints on a subject table this valid scenario might be rolled back.
and
Is there a way that we can handle the duplicate scenario the DB level. where we can throw a unique constraint exception. if we are storing a duplicate subscription.
It sounds like you need a unique constraint on your Subscription entity to prevent the duplicates. i.e.
#Table(name = "subscriptions", uniqueConstraints = #UniqueConstraint(name = "uq_sub", columnNames = {"notificationTypeCode", "subscriberId", "subjectId"}))

Retrieve the record using Spring Data JPA

I'm having spring data jpa repository.I the entity contain primary key id int and ipaddress string. The table contain only 1 record at a time otherwise null.
How do i retrieve the record using JPA , if it is not found return null.
#Repository
public interface IpConfigRepository extends JpaRepository<IpConfig, Integer> {
//
IpConfig findIpConfig();
}
According to the naming convention, you should define the method with the name findById(Integer id) ( assume the Id is the primary key )
Suppose you have a class A as shown below
class A{
private int id;
private String data;
// getters and setters
}
You can now search the items by the following ways.
public interface ARepo extends JpaRepository<A,Integer>{
// get all the records from table.
List<A> findAll();
// find record by id
A findById(int id);
// find record by data
A findByData(String data);
// find by date created or updated
A findByDateCreated(Date date);
// custom query method to select only one record from table
#Query("SELECT * FROM a limit 1;")
A findRecord();
}

Resources