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());
}
}
Related
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;
}
}
I am trying to save a "User" object that is related by a OneToMany relationship to a "Volunteer" object.
When I try to save it, it only works when I provide the primary IDs for both these objects. However, what I need is to save the entity and let the database dictate the ID's via autoIncrement. I am not sure how am I suppose to do this or even if it's possible.
Json Mapping that works:
{
"id":8,
"userName": "user8",
"password": "pass1234",
"volunteersId": 6,
"volunteers": [{
"id":6,
"committeesId": 2,
"outreachDate": "2019-12-07",
"usersId": 8
}]
}
Json Mapping that I need (but will not work):
{
"userName": "user8",
"password": "pass1234",
"volunteersId": 6,
"volunteers": [{
"committeesId": 2,
"outreachDate": "2019-12-07",
}]
}
So I am thinking maybe there's a way to connect the foreign keys so that I wont have to explicitly add the autoIncrement IDs (usersId, volunteersId).
User controller:
#Controller
public class UserController {
#RequestMapping(value = "/v1/users", method = RequestMethod.POST)
public ResponseEntity<Object> saveUsers( #RequestBody UserEntity request){
try {
return ResponseEntity.ok(userService.saveUser(request));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
User service:
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
public Page<UserEntity> saveUser(UserEntity user){
userRepository.save(user);
Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending());
return userRepository.findAll(pageable);
}
}
User Repository:
public interface UserRepository extends JpaRepository<UserEntity, Long> {
public List<UserEntity> findAllByOrderByIdAsc();
public List<UserEntity> findAllByOrderByIdDesc();
public Page<UserEntity> findByUserNameContaining(String userName, Pageable pageable);
}
User Entity:
#Entity
#Table(name = "users")
public class UserEntity {
#Id
private long id;
#Column(name = "username")
private String userName;
private String password;
#Column(name = "volunteers_id", nullable = true)
private Long volunteersId;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "users_id")
private List<VolunteerEntity> volunteers = new ArrayList<>();
// omitted getters and setters
}
Volunteer Entity:
#Entity
#Table(name = "volunteers")
public class VolunteerEntity {
#Id
private long id;
#Column(name = "committees_id")
private long committeesId;
#Column(name = "outreach_date")
private Date outreachDate;
#Column(name = "users_id")
private Long usersId;
// omitted getters and setters
}
Any ideas or suggestions how to save this whole entity? I am wondering if this is really possible to save as in one whole process. Though if not, I am thinking of just saving them independently (User info first, then Volunteer next) but just in case it would be possible, it would really be a great help
You need to add #GeneratedValue anotation next to the #id
#Id
#GeneratedValue(strategy = GenerationType.selectOne)
private long id;
In case of sequence you need to add an extra anotation
#Id
#SequenceGenerator(name = "customName", sequenceName = "sequenceNameInDatabase")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="customName")
private long id;
This will make the primary id generation auto
#Entity
#Table(name = "user")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
I've read examples but have my personal question to you.
I have 2 tables:
Role:
id, name
User:
id, login, name, role_id
Role entity
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<User> user = new HashSet<>();
//getters and setters
User entity
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",insertable = false, updatable = false)
private long id;
#Column(name = "login")
private String login;
#Column(name = "user_name")
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
//getters and setters
And repository:
public interface UserRepository extends JpaRepository<User, Long> {
String Q_GET_ALL_USERS = "from User u left join Role r on u.role_id=r.id";
#Query(Q_GET_ALL_USERS)
Collection<User> getAllUsers();
This code is showing: Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Path expected for join! [from com.example.jpa.model.User u left join Role r on u.role_id=r.id]
How I understand entity can't contains 'id' (in my case in Role) for references and I should remove this field. But entity should have '#Id'.
In this case I should create new column in 'Role'? or I can use more beautiful decision?
I put all project to bb
To use join in HQL (JPQL) you don't need on clause
String Q_GET_ALL_USERS = "select u from User u left join u.role";
This query doesn't have any sence because of you don't use role in the where clause.
If you want to get users with a fetched role you can use join fetch
String Q_GET_ALL_USERS = "select u from User u left join fetch u.role";
Update
Your schema for User and Role is not commonly used. I advice to you make #ManyToMany association from user to roles and remove any user association from the Role
#Entity
#Table(name = "user")
public class User {
#ManyToMany(fetch = FetchType.LAZY)
private Set<Role> roles;
}
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
}
No, you should create a new column in User.
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "role_id")
private Role role;
Thank you all for answers. Right entities and query below (plus tables schema).
Tables (queries)
CREATE TABLE role (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(45) NOT NULL
);
CREATE TABLE user (
id INT NOT NULL PRIMARY KEY IDENTITY,
login VARCHAR(45) NOT NULL,
user_name VARCHAR(45) NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (role_id) REFERENCES role (id)
);
Entities:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",insertable = false, updatable = false)
private long id;
#Column(name = "login")
private String login;
#Column(name = "user_name")
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
//getters and setters
}
and
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<User> user = new HashSet<>();
//getters and setters
}
Repository
public interface UserRepository extends JpaRepository<User, Long> {
String Q_GET_ALL_USERS = "select u from User u left join u.role";
#Query(Q_GET_ALL_USERS)
Collection<User> getAllUsers();
}
#v-ladynev proposed alternative decision(use only #ManyToMany in User). More details you can find in comments under this answer.
When I check this decision I will update this answer (I hope I don't forget it :-))
Models
#Entity
#Table(name = "sys_std_user")
public class StdUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "class_id")
public int classId;
#Column(name = "user_name")
public String userName;
}
#Entity
#Table(name = "sys_std_profile")
public class StdProfile {
#Id
#Column(name = "pro_id")
public int proId;
#Column(name = "full_name")
public String fullName;
}
Controllers
#PersistenceUnit
private EntityManagerFactory emf;
#GetMapping("/join")
public List actionJoinTable() {
EntityManager em = emf.createEntityManager();
List arr_cust = em
.createQuery("SELECT u.classId, u.userName, p.fullName FROM StdUser u, StdProfile p WHERE u.classId=p.proId")
.getResultList();
return arr_cust;
}
Result:
[
[
1,
"Ram",
"Ram Pukar Chaudhary"
],
[
2,
"Raja",
"Raja Kishor Shah"
]
]
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
I am trying to use an #JoinColumn as an #Id using JPA and I am getting SerializationExceptions, "Could not serialize."
UserRole.java:
#Entity
#Table(name = "authorities")
public class UserRole implements Serializable {
#Column(name = "authority")
private String role;
#Id
#ManyToOne
#JoinColumn(name = "username")
private User owner;
...
}
User.java:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
protected Long id;
#Column(name = "username")
protected String email;
#OneToMany(mappedBy = "owner", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected Set<UserRole> roles = new HashSet<UserRole>();
....
}
"username" is set up as a unique index in my Users table but not as the primary key.
Is there any way to make "username" act as the ID for UserRole? I don't want to introduce a numeric key in UserRole. Have I totally lost the plot here?
I am using MySQL and Hibernate under the hood.
That mapping doesn't really make sense. ID has to be unique, but ManyToOne says 'lots of these have the same User.'