I work with Spring Boot and have 2 PostgreSQL tables: USERS and CITIES. FOREIGN KEY (USERS.city_id) REFERENCES CITIES (id). CITIES has an unique constraint for city name field. I receive an object in the #PostMapping method of the controller and try to save it via service layer. All is fine while I don't send an object with same city name field, and I don't know how to solve it. Postman JSON example:
*1st attempt*
{
"name": "JHON",
"city": {
"name": **"MOSCOW"**
}
} ---> ALL OK
*2nd attempt*
{
"name": "TOM",
"city": {
"name": **"MOSCOW"**
}
}--->**org.postgresql.util.PSQLException: ERROR: Duplicate key value violates unique constraint "cities_name_key"
Details: The key "(name) = (MOSCOW)" already exists.**
Entity:
#Entity
#Table(name = "cities")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
}
#Entity
#Data
#Accessors(chain = true)
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "city_id")
private City city;
}
Tables:
cities
id SERIAL PRIMARY KEY NOT NULL,
name TEXT UNIQUE
users
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(10),
city_id INT,
CONSTRAINT users_cities_id_fk FOREIGN KEY (city_id) REFERENCES cities (id)
and Service
#Service
#RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public User getUser(Long id){
return userRepository.findById(id).get();
}
public User saveUser(User user){
return userRepository.save(user);
}
}
Ok, I found an answer for question.
So if you have fields with unique constraints in the your tables, you have to define a constructor inside the entity for the primary key field. And don't hope that Lombok will do it. It means that annotation like #AllArgsConstructor does not help for this case.
#Entity
#Table(name = "cities")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
//add this *********************
public City(Long id) {
this.id = id;
}
}
Related
I have just started with Spring boot.
I have 3 tables User, UserNotes, and Notes.
User ( userID, EmailID)
UserNotes (UserID, NotesID)
Notes (notesID, Title, Message )
When I add an email, UserID has to be autogenerated. And when I add any note corresponding to the user, Notes Id in UserNotes table must be autogenerated and it must be added to Notes table.
I have done it in MySQL by making userID in User table as primary key and UserID in UserNotes as foreign key referencing to it. Similarly NotesID in UserNotes as primary key and notesID in Notes table as a foreign key for that.
Everything is working fine when I am using MySQL queries for this. But now I want to use Spring Data JPA for this. But I am facing difficulty in understanding how to map these relationships having multiple tables. I have tried OneToMany and ManyToOne relationship but it didn't work out
MySQL Queries for the reference
1) Create table User(userID int AUTO_INCREMENT, emailID varchar(40), PRIMARY KEY(userID));
2) Create table UserNotes(userID int, NotesID int AUTO_INCREMENT, PRIMARY KEY(NotesID), Foreign key(UserID) references User(UserID) on DELETE CASCADE);
3) Create table Notes(notesID int, title varchar(100), message varchar(500), Date date, PRIMARY KEY(notesID), FOREIGN KEY(notesID) references UserNotes(NotesID) on DELETE CASCADE);
An untested example using Hibernate, JPA, Lombok:
User entity
#Entity
#Table(name = "USER")
#SequenceGenerator(name = "userSeqId", sequenceName = "user_seq_id", allocationSize = 1)
#NoArgsConstructor
#Setter
#Getter
public class User {
#Id
#GeneratedValue(generator = "userSeqId", strategy = GenerationType.AUTO)
private Long id;
#NotBlank
#Column(name = "EMAIL", unique = true)
private String email;
}
Notes entity
#Entity
#Table(name = "NOTES")
#SequenceGenerator(name = "notesSeqId", sequenceName = "notes_seq_id", allocationSize = 1)
#NoArgsConstructor
#Setter
#Getter
public class Notes {
#Id
#GeneratedValue(generator = "notesSeqId", strategy = GenerationType.AUTO)
private Long id;
#NotBlank
#Column(name = "TITLE")
private String title;
#NotBlank
#Column(name = "MESSAGE")
private String message;
}
UserNotes entity
#Entity
#Table(name = "USER_NOTES")
#NoArgsConstructor
#Setter
#Getter
public class UserNotes {
#EmbeddedId
private UserNotesKey id;
#NotNull
#ManyToOne
#MapsId("USER_ID")
#JoinColumn(name = "USER_ID")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#NotNull
#ManyToOne(cascade = CascadeType.PERSIST)
#MapsId("NOTES_ID")
#JoinColumn(name = "NOTES_ID")
#OnDelete(action = OnDeleteAction.CASCADE)
private Notes notes;
}
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#Setter
#Getter
#EqualsAndHashCode
public class UserNotesKey implements Serializable {
#Column(name = "USER_ID")
private Long userId;
#Column(name = "NOTES_ID")
private Long notesId;
}
Repositories
public interface UserRepository extends JpaRepository<User, Long> {
}
public interface NotesRepository extends JpaRepository<Notes, Long> {
}
public interface UserNotesRepository extends JpaRepository<UserNotes, UserNotesKey> {
List<UserNotes> findByUser(User user);
}
Test service
#Service
#Transactional
#RequiredArgsConstructor
public class TestService {
private final UserRepository userRepository;
private final UserNotesRepository userNotesRepository;
public User saveUser(User user) {
User newUser = new User();
user.setEmail(user.getEmail());
return userRepository.save(user);
}
public UserNotes saveNotes(User user, Notes notes) {
UserNotes userNotes = new UserNotes();
userNotes.setUser(user);
userNotes.setNotes(notes);
return userNotesRepository.save(userNotes);
}
public List<Notes> getNotes(User user) {
return userNotesRepository.findByUser(user)
.stream()
.map(UserNotes::getNotes)
.collect(Collectors.toList());
}
}
I have an entity:
#Entity
#Table(name = "person")
public class Person {
#EmbeddedId
private PersonId id;
#OnDelete(action = OnDeleteAction.CASCADE)
#MapsId("a_phoneNumberId")
#ManyToOne
private PhoneNumber phoneNumber;
#OnDelete(action = OnDeleteAction.CASCADE)
#MapsId("b_addressId")
#ManyToOne
private Address address;
...
with embedded id:
#Embeddable
public class PersonId implements Serializable {
private int a_phoneNumberId;
private int b_addressId;
...
Note: a_ and b_ prefixes are used to order columns in primary key.
Everything works as expected and hibernate generates a table with columns: phoneNumber_id and address_id.
Is it possible to rename those columns, as I want to have a snake_case name - phone_number_id?
So far I tried
#AttributeOverride annotation:
#Entity
#Table(name = "person")
public class Person {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "a_phoneNumberId", column = #Column(name = "phone_number_id"))
})
private PersonId id;
#Column annotation for the id:
#Embeddable
public class PersonId implements Serializable {
#Column(name = "phone_number_id")
private int a_phoneNumberId;
but it changed nothing.
I have a Customers table that has AddressEmbedded in it.
I also have a hardcoded table Countries where I already have a region for every country, country is the primary key.
I want to join AddressEmbedded and Countries so I used the ManyToOne and put CountryEntity in AddressEmbedded.
But I'm getting an error that mapstruct can't generate setCountry.
So the question is, how do I make AddressEmbedded.setCountry(string country)?
It's supposed to do a call to the db to get the corresponding region for that country, but it seems wrong to add a db call in a setter.
Here are the entity definitions:
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Data
#Embeddable
public class AddressEmbedded {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "country")
private CountryEntity country;
}
#Data
#Entity
#Table(name = "Countries")
public class CountryEntity {
#Id
#Column(name = "country")
private String country;
#Column(name = "region")
private String region;
}
#Data
#Entity
#Table(name = "Customers")
public class CustomerEntity {
#Embedded
private AddressEmbedded address;
}
This was solved with mapstruct mappings
#Mappings(
#Mapping(target = "address.country", source = "countryEntity")
)
CustomerEntity fromSubmitCustomerDetailsRequestToCustomerEntity(SubmitCustomerDetailsRequest request, CountryEntity countryEntity);
#Mappings(
#Mapping(target = "address.country", source = "customerEntity.address.country.country")
)
GetCustomerDetailsResponse fromCustomerEntityToGetCustomerDetailsResponse(CustomerEntity customerEntity);
I have the CountryEntity in fromSubmitCustomerDetailsRequestToCustomerEntity because before I call it I validate that I have a country that exists.
I have the below JSON as input:
{
"type": "Student",
"numOfPeople": "1",
"tenantMembers": [
{
"firstName": "Chris",
"lastName": "C"
}
],
"tenantDetails": {
"firstName": "John",
"lastName": "J",
"email" "xyz#gmail.com"
}
}
I want to use this to do a save:
tenantRepo.save(tenant);
This should save the parent "Tenant" and the children "TenantMembers" and "TenantDetails".
But when I do it does with NULL 'tenant_id's in the children. (If I have foreign keys in the DB gives 'tenant_id' can't be null constraint exception)
My question is: Is this possible in Hibernate?
My models:
Parent class:
#Entity
#Table(name = "tenant")
public class Tenant {
#GeneratedValue
#Id
private Long id;
private String type;
#Column(name = "num_of_people")
private String numOfPeople;
#OneToMany(mappedBy = "tenant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TenantMember> tenantMembers;
#OneToOne(mappedBy = "tenant", cascade = CascadeType.ALL)
private TenantDetails tenantDetails;
TenantMember child class:
#Entity
#Table(name = "tenant_member")
public class TenantMember {
#GeneratedValue
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
TenanatDetails child class:
#Entity
#Table(name="tenant_details")
public class TenantDetails {
#GeneratedValue
#Id
private Long id;
#OneToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
EDIT:
Following up Dragan Bozanovic's suggestion, tried using #JsonIdentityInfo
for the three tables:
#Entity
#Table(name = "tenant")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Tenant {
#Entity
#Table(name="tenant_details")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantDetails {
#Entity
#Table(name = "tenant_member")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantMember {
and did the following to save:
#RequestMapping(value = "/set", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Tenant test(#RequestBody Tenant tenant) {
Tenant t = new Tenant();
t.setType(tenant.getType());
t.setNumOfPeople(tenant.getNumOfPeople());
tenantRepo.save(t);
tenant.setId(t.getId());
tenant.getTenantDetails().setTenant(tenant);
for(TenantMember member: tenant.getTenantMembers()) {
member.setTenant(tenant);
}
return tenantRepo.save(tenant);
}
Would this be the best approach that is possible?
Hibernate does save the children (hence the constraint violation) because of the cascading options you specified, but it does not save the relationship information (join column value) in your case.
TenantMember and TenantDetails are the owners of the association with Tenant (mappedBy attributes in the association annotations in Tenant).
That means that you have to properly update the tenant field in the TenantMember and TenantDetails instances, because Hibernate ignores inverse side of the association when maintaining the relationship.
So consider the following 2 tables:
table: User
id (pk)
...
and table UserProfile:
UserProfile
user_id(pk, and fk from User.id. fk is named profile_user_fk)
...
given this tables, I have the entity classes:
#Entity
#Table(name="User")
public class User implements Serializable {
private int id;
private UserProfile profile;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, unique = true)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#OneToOne(mappedBy = "user")
public UserProfile getProfie() {
return profile;
}
public void setProfile(UserProfile p) {
profile = p;
}
...
}
And the User class:
#Entity
#Table(name="UserProfile")
public class UserProfile implements Serializable {
private User user;
#OneToOne
#PrimaryKeyJoinColumn(name="profile_user_fk")
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
...
}
I don't want to add an extra column in UserProfile because I think this column is meaningless. User.id should be sufficient to indicate the identity of UserProfile records.
So I supposed the #OneToOne annotations will tell hibernate user is a pk and also fk and should refer to User.id for its own id; however executing the code shows:
org.hibernate.AnnotationException: No identifier specified for entity: xxx.UserProfile
Apparently what I thought was wrong - but I've no idea what can I do to fix it without altering the schema.
Any helps please. Thanks!
The error
No identifier specified for entity: xxx.UserProfile
says
In your Entity class (UserProfile), you have not defined a primary key. You must specify
either #Id annotation or an #EmbeddedId annotation. Bcoz, every class defined as Entity
with #Entity annotation, needs an #Id or #EmbeddedId property.
Modify your UserProfile class as below :-
#Entity
#Table(name="UserProfile")
public class UserProfile implements Serializable {
private long uProfileId;
private User user;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "uProfileId", nullable = false, unique = true)
public long getUProfileId() {
return uProfileId;
}
public void setUProfileId(long uProfileId) {
this.uProfileId = uProfileId;
}
#OneToOne
#PrimaryKeyJoinColumn(name="profile_user_fk")
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
...
}
Read: #Entity & #Id and #GeneratedValue Annotations
Entity Class without Primary Key :
If you don't want to add primary key in UserProfile table then you can use #Embedded & #Embeddable annotations or <component> tag in xml mapping. For more explanation on this look at below posts:-
Using <component> or #Embedded & #Embeddable annotations
Hibernate and no PK
Hibernate/persistence without #Id