How to prevent duplicate entries Spring Jpa - java

I'm learning how Spring framework works and as an example I'm trying to save cities and countries which users can log using the API endpoints. However, I can't figure out how to prevent duplicate entries.
For example I'm adding 2 cities in a country using the endpoint (photo below) but in the Country table I get duplicate values. How can I prevent duplicate values ? Thanks in advance.
#Getter
#Setter
#Entity
#Table(name = "COUNTRY")
public class CntCountry {
#Id
#SequenceGenerator(name = "CntCountry", sequenceName = "CNT_COUNTRY_ID_SEQ")
#GeneratedValue(generator = "CntCountry")
private Long id;
#Column(name = "COUNTRY_NAME", length = 30, nullable = false)
private String countryName;
#Column(name = "COUNTRY_CODE", length = 30, nullable = false)
private String countryCode;
}
#Getter
#Setter
#Table(name = "CITY")
#Entity
public class CtyCity {
#Id
#SequenceGenerator(name = "CtyCity", sequenceName = "CTY_CITY_ID_SEQ")
#GeneratedValue(generator = "CtyCity")
private Long id;
#Column(name = "CITY_NAME", length = 30, nullable = false)
private String cityName;
#Column(name = "PLATE_NUMBER", length = 30, nullable = false)
private Long plateNumber;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "FK_COUNTRY")
private CntCountry country;
}
EDIT:
#PostMapping("/city")
public ResponseEntity<CtyCityDto> save(#RequestBody CtyCitySaveRequestDto ctyCitySaveRequestDto){
CtyCityDto ctyCityDto = ctyCityService.save(ctyCitySaveRequestDto);
return ResponseEntity.ok(ctyCityDto);
}
#Service
#AllArgsConstructor
public class CtyCityService {
private CtyCityDao ctyCityDao;
public CtyCityDto save(CtyCitySaveRequestDto ctyCitySaveRequestDto){
CtyCity ctyCity = CtyCityMapper.INSTANCE.convertToCtyCity(ctyCitySaveRequestDto);
ctyCity = ctyCityDao.save(ctyCity);
CtyCityDto ctyCityDto = CtyCityMapper.INSTANCE.convertToCtyCityDto(ctyCity);
return ctyCityDto;
}
}
public interface CtyCityDao extends JpaRepository<CtyCity,Long> {
}
#Data
public class CtyCityDto {
private Long id;
private String cityName;
private Long plateNumber;
private CntCountry country;
}

I'm not really following your naming conventions, and I think your DTO classes are just complicating things for you at this point... But in general terms, because the entities you're sending have no id value associated with them, JPA assumes they are different objects and adds them to the database with new id's because it hasn't been told anywhere that similar items might in fact be the same object, it needs to be told.
I can think of 2 ways to prevent entity duplication in your database.
1. The easiest way would be to set your Country and City names (or other attributes) to be "unique", you can do this in your entity classes simply by adding unique = true to the column data on the item you wish to be unique.
//In Country.java
#Column(name = "COUNTRY_NAME", length = 30, nullable = false, unique = true)
private String countryName;
//In City.java
#Column(name = "CITY_NAME", length = 30, nullable = false, unique = true)
private String cityName;
Although, you will then need to handle exceptions thrown if a duplicate is provided, in Spring Boot the best way to handle this is with a #ControllerAdvice but that's another subject.
2. Check if the entity exists by name or some other value. A common approach might be something like the following:
//In your service
public Object saveCountry(Country country){
Country existingCountry = countryRepository.findByName(country.getName()).orElse(null);
if(existingCountry == null){
//Country does not already exist so save the new Country
return countryRepository.save(country);
}
//The Country was found by name, so don't add a duplicate
else return "A Country with that name already exists";
}
//In your Country repository
Optional<Country> findByName(countryName);
In case my answer doesn't make sense, I have thrown together an example following my first suggestion (using the unique column attribute and a controller advice) which you can view/clone from here

Related

Oder a one-many list in entity Java Spring

I have the following entity, and i want to order the list of typeValuesList by the field TypeValues -> name (string) in descending order. I want to do this conditionally only when the description is equals to "1" for example. I tried with the #OrderBy annotation but that does not seem to work conditionally, what are my options in this case, can i go with a query?
#Entity
#Table(name="DEVICE")
public class Device extends AbstractEntity {
#Column(name = "DESCRIPTION", length = 300)
#Size(max = 150)
private String description;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="TYPE_ID", nullable = false)
private List<TypeValues> typeValuesList = new ArrayList<>();
}
#Entity
#Table(name="TYPE_VALUES")
public class TypeValues extends AbstractEntity{
#Column(name = "NAME", length = 50)
#Size(max = 25)
private String name;
}
Since you want to do it conditionally the best solution is to sort your collection as needed upon retrieval
in the calling method after retrieving the Device:
Collections.sort(device.getTypeValuesList(), Comparator.comparing(TypeValues::getName));

How Can I Proxy A Referenced Entity Before Saving?

I'm using Spring Boot 2.3.0 along with Spring Data JPA and Spring MVC. I have the following 2 entities:
Country.java
#Entity
#Table(name = "countries")
public class Country
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id")
private Integer id;
#Column(name = "country_name" , nullable = false , length = 50)
#NotNull #Size(max = 50)
private String name;
#Column(name = "country_acronym" , length = 3)
#Size(max = 3)
private String acronym;
//Getters-Setters
//Equals-Hashcode (determines equality based only on name attribute)
}
City.java
#Entity
#Table(name = "cities")
public class City
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "city_name")
#Size(max = 50)
private String name;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "city_country")
private Country country;
//Getters-Setters
//Equals/Hashcode (all attributes)
}
What I want to achieve is to save cities through REST calls. One catch is that I want in the body of the request to provide only the name of the country and if that country exists in the countries table, then it must be able to find the reference by itself , else it should first insert a new country and then match the reference.
For complete reference, let me provide the Repository and Controller classes:
CityRepository.java
#Repository
public interface CityRepository extends JpaRepository<City,Integer>
{
}
MainController.java
#RestController
public class MainController
{
#Autowired
private CityRepository cityRepository;
#PostMapping(value = "/countries")
private void insertCountry(#RequestBody #Valid Country country)
{
countryRepository.save(country);
}
#PostMapping(value = "/cities")
public void insertCities(#RequestBody #Valid City city)
{
cityRepository.save(city);
}
}
A sample body of a request:
{
"name": "Nikaia",
"country": {
"name": "Greece"
}
}
The error I get is that Hibernate always tries to save the country, it never looks if it exists (I get a constraint violation). I guess that the country never gets proxied by Hibernate because it isn't yet a persisted entity. Is there a way I can easily solve that using Data JPA ? Or should I go a level lower and play with the EntityManager? Complete code samples would be greatly appreciated.
It's logical. For your desire feature fetch the country by country name. If exist then set that in city object.
#PostMapping(value = "/cities")
public void insertCities(#RequestBody #Valid City city)
{
Country country = countryRepository.findByName(city.getCountry().getName());
if(country != null) city.setCountry(country);
cityRepository.save(city);
}
And findByName in CountryRepository also
Country findByName(String name);

Is there any way to audit , with hibernate-envers, an entity having an #Embedded in its EmbeddedId

I have an entity BlocRecord having a composite code BlocRecordId, and in its composite code there is an #Embedded (relation code ManyToOne) pointing to another entiy Record and want to Audit the entity BlocRecord.
The entity BlocRecord
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "blocRecord")
#Access(value = AccessType.FIELD)
#Audited
public class BlocRecord {
#EmbeddedId
private BlocRecordId blocRecordId = new BlocRecordId();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumns({
#JoinColumn(name = "record_identifier_", referencedColumnName = "identifier_", unique = false, nullable = false),
#JoinColumn(name = "record_recordType_", referencedColumnName = "recordType_", unique = false, nullable = false)})
#MapsId("record")
private Record record;
...
}
The id class BlocRecordID
#Embeddable
public class BlocRecordId implements Serializable {
#Embedded
private RecordId record;
#Column(name = "source_")
String source ;
#Column(name = "messageType_")
String messageType ;
The entity Record
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "records")
#Access(value = AccessType.FIELD)
#Audited
public class Record {
#EmbeddedId
private RecordId recordId = new RecordId();
#OneToMany(targetEntity = BlocRecord.class, fetch = FetchType.LAZY, mappedBy = "record")
private Set<BlocRecord> blocRecord = new java.util.HashSet<>();
...
}
The idClass of the entity Record
#Embeddable
public class RecordId implements Serializable{
#Column(name = "identifier_")
String identifier ;
#Column(name = "recordType_")
String recordType ;
}
Hibernate-envers fails when trying to generate the metadata of the field record in the embeddable BlocRecordId, the The flowing exception is thrown
org.hibernate.MappingException: Type not supported: org.hibernate.type.ComponentType
at org.hibernate.envers.configuration.internal.metadata.IdMetadataGenerator.addIdProperties(IdMetadataGenerator.java:121)
at org.hibernate.envers.configuration.internal.metadata.IdMetadataGenerator.addId(IdMetadataGenerator.java:230)
at org.hibernate.envers.configuration.internal.metadata.AuditMetadataGenerator.generateFirstPass(AuditMetadataGenerator.java:642)
at org.hibernate.envers.configuration.internal.EntitiesConfigurator.configure(EntitiesConfigurator.java:95)
at org.hibernate.envers.boot.internal.EnversServiceImpl.doInitialize(EnversServiceImpl.java:154)
at org.hibernate.envers.boot.internal.EnversServiceImpl.initialize(EnversServiceImpl.java:118)
at org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl.produceAdditionalMappings(AdditionalJaxbMappingProducerImpl.java:99)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:288)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:83)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:417)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:86)
at org.hibernate.boot.MetadataSources.buildMetadata(MetadataSources.java:179)
Do you have any idea how to resolve the issue ?
Thanks
At the moment, Envers does not support the idea of nesting an embeddable inside an embeddable when we map the identifier columns like your example illustrates. The only valid mappings that Envers presently does support is if the attribute in the embeddable is a #ManyToOne or a #Basic type.
You can work around this problem but it involves being a bit more explicit and not using RecordId. What I mean is rewrite BlocRecordId to be the following:
#Embeddable
public class BlocRecordId implements Serializable {
#Column(name = "identifier_")
String identifier;
#Column(name = "recordType_")
String recordType;
#Column(name = "source_")
String source;
#Column(name = "messageType_")
String messageType;
#Transient
private RecordId recordId;
/** Helper method to assign the values from an existing RecordId */
public void setRecordId(RecordId recordId) {
this.identifier = recordId.getIdentifier();
this.recordType = recordId.getRecordType();
}
/** Helper method to get the RecordId, caching it to avoid multiple allocations */
public RecordId getRecordId() {
if ( recordId == null ) {
this.recordId = new RecordId( identifier, recordType );
}
return this.recordId;
}
}
I agree this is less than ideal but it does at least work around the current limitation of the code. I have gone added and added HHH-13361 as an open issue to support this. You're welcomed to contribute if you wish or I'll work in getting this supported for Envers 6.0.

How can i get object while sending as json from Spring MVC without infinity loops

So, i'm trying to get my Student Object to my androidClient from server, but i have ifinity loops. If i use #JsonBackReference/#JsonManagedReference or #JsonIgnore i won't get Object like in case of infinity loop, so i have a question how to do this? Here my classes:
Student.java
#Entity
#Table(name ="student")
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#GenericGenerator(name="increment", strategy = "increment")
private int id_student;
#Column(name = "studentName", nullable = false)
private String studentName;
#Column(name = "formOfAducation")
private String formOfAducation;
#ManyToOne
#JoinColumn(name ="id_group")
#JsonBackReference
private Group group;
#Column(name = "studentCourse")
private String studentCourse;
#Column(name = "studentSpecializatio")
private String studentSpecializatio;
#Column(name = "studentBookName")
private String studentBookName;
#Column(name = "studentGender")
private String studentGender;
public Student(){
}
//getter-setters
}
Group.java
#Entity
#Table(name = "party")
public class Group {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#GenericGenerator(name="increment", strategy = "increment")
private int id_group;
#Column(name = "groupName", nullable = false)
private String groupName;
#OneToMany(targetEntity = Student.class, mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonManagedReference
private List<Student> student;
public Group(){
}
On client i have the same classes, and when i'm trying to pass only id from group i cant deserialize it on client. Hope you'll help me. Or there's no way to do this, so how can i edit Student object from client?
There are two possible ways.
The first one is to create DTO objects and initialize them from the entities. Thus you manually stop on desired level without loops.
The second way is to unproxy the entities to break lazy collection loading which leads to the loops.

Hibernate annotation for lookup column

I have three tables : po_dtl and workbook_types.
Each purchase order line (po_dtl) will have "workbook_code" column to refer to workbook_types.
Basically, po_dtl.workbook_code is only a lookup code to workbook_types.workbook_code.
The simplified java class are
#Entity
#Table(name="po_dtl")
public class PurchaseOrderDtl {
#Id
#Column(name = "po_dtl_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "po_dtl_s")
#SequenceGenerator(name = "po_dtl_s", sequenceName = "po_dtl_s", allocationSize = 1, initialValue = 1)
private long id;
#Column(name = "price_base", length = 7)
private int priceBase;
// ??? What is the annotation for workbook_Types?
private WorkbookType workbookType;
....
}
While the workbook_types
#Entity
#Table(name = "workbook_types")
public class WorkbookType {
#Id
#Column(name = "workbook_code")
private String workbookCode;
#Column(name = "description", length = 255)
private String description;
#Column(name = "file_root_path", length = 255)
private String fileRootPath;
....
}
What i ussualy use (without hibernate) is :
workbook_types.workbook_code contains string "wbook_finance",
"wbook_marketing"
each po_dtl.workbook_code will contains "wbook_finance" or
"wbook_marketing"
workbook_types will only contains 2 rows with unique workbook_code, while po_dtl will contains many rows, but each row must contains only "wbook_finance" or "wbook_marketing"
Thanks
As i understand your question, you need #ManyToOne
Or use enum if new workbook_type's added only at developing stage.
#Entity
class Country { //Country Dictionary
}
#Entity
class User {
#ManyToOne
private Country country;
}
see How to design tables for data dictionary when using hibernate?

Categories