Hello i have a task to build an application using spring boot and hibernate. The thing is i'm building the database through a data.sql file to persist data and create the database. Now i need to create a relationship table between my two tables. This should be a one to many relationship given that a worker can have a user account or may not have an account. The problem lies on how to build this relationship table when the application starts because the references should be the foreign keys user_id and worker_id which i do not have at startup because they are created automatically by hibernate so there's no way to hardcode them. So the question is how would i go about creating this relationchip table from data.sql.
data.sql:
INSERT INTO worker (name, last_name, status) VALUES
('manuel', 'dias', 'active'),
('sailin ', 'nal', 'active'),
('abraham', 'lincon', 'active'),
('mick', 'smith', 'active'),
('jose', 'perez', 'inactive'),
('luis', 'nuñez', 'inactive'),
('ruben', 'puertas', 'inactive'),
('anders', 'stone', 'inactive'),
('luis', 'alvarez', 'deleted'),
('claudio', 'martinez', 'deleted'),
('morfeo', 'rodriguez', 'active'),
('onetys', 'estrada', 'inactive'),
('rene', 'fajardo', 'active');
INSERT INTO users (username, password, user_type, status) VALUES
('madi', 'madi', 'worker', 'active'),
('sana', 'sana', 'worker', 'active'),
('abli', 'abli', 'worker', 'active'),
('mism', 'mism', 'worker', 'active'),
('jope', 'jope', 'worker', 'active'),
('lunu', 'lunu', 'worker', 'active'),
('rupu', 'rupu', 'worker', 'active'),
('anst', 'anst', 'worker', 'active'),
('lual', 'lual', 'worker', 'active'),
('clma', 'clma', 'worker', 'active');
Users.class:
#Entity
#Table(name = "users")
public class Users implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", nullable = false)
private int userId;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "userType")
private String userType;
#Column(name = "status")
private String status;
#ManyToOne
#JoinColumn(name = "worker_id")
private Worker worker;
Worker.class:
#Entity
#Table(name = "worker")
public class Worker implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "worker_id")
private int workerId;
#Column(name = "name")
private String name;
#Column(name = "last_name")
private String lastName;
#Column(name = "status")
private String status;
#OneToMany(mappedBy = "worker", cascade = CascadeType.ALL)
private Collection<Users> users;
So do i have to:
1: Create another Entity to be able to make a new table in the database holding the foreign keys to user and worker entities?
2: Can it be done with a script in data.sql.
I would create a column in users table referencing the worker id but the task explicitly says:
"make a third table to hold the relationship"
Thanks in advance
In OneToMany relationship, you can have all needed information on the entity of the many side. Creating another join table only makes sense on ManyToMany relationship, it would only add redundant data on OneToMany and violates normalization.
That being said, if you really need another table to define the two entities relationship, you can achieve this without creating another entity using #JoinTable.
#Entity
#Table(name = "users")
public class Users implements Serializable {
//....
#ManyToOne
#JoinTable(name = "worker_users",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "worker_id"))
private Worker worker;
}
#Entity
#Table(name = "worker")
public class Worker implements Serializable {
//...
#OneToMany(cascade = CascadeType.ALL) //remove mappedBy
#JoinTable(name = "worker_users",
joinColumns = #JoinColumn(name = "worker_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private Collection<Users> users;
}
It would create new worker_user table with this structure
|WORKER_ID |USER_ID |
Related
I am struggling with the following problem that I've been trying to solve. After checking solutions on StackOverflow and articles on Baeldung I still get different JPA errors when trying to map the following ONE-TO-ONE relationship between 2 Oracle SQL tables with composite PK in a SpringBoot application:
MASTER
ID
VERSION
1
2022.1
Constraint:
PK_MASTER PRIMARY KEY(ID, VERSION)
MASTER_DETAILS
MASTER_ID
VERSION
DETAILS
1
2022.1
details
Constraint:
PK_MASTER_DETAILS PRIMARY KEY(MASTER_ID, VERSION)
FK_MASTER_DETAILS FOREIGN KEY(MASTER_ID, VERSION) REFERENCES MASTER(ID, VERSION)
After some failures in trying to map it using the #OneToOne JPA annotation with both classes having #EmbeddedId set on the composite PK, I also installed JPA Buddy to check how it will be generated and that resulted in the following 4 classes:
Master.java
#Getter
#Setter
#Entity
#Table(name = "master")
public class Master {
#EmbeddedId
private MasterId id;
#OneToOne(mappedBy = "master")
private MasterDetails masterDetails;
}
MasterId.java
#Getter
#Setter
#Embeddable
public class MasterId implements Serializable {
private static final long serialVersionUID = 8254837075462858051L;
#Column(name = "id", nullable = false)
private BigDecimal id;
#Lob
#Column(name = "version", nullable = false)
private String version;
}
MasterDetails.java
#Getter
#Setter
#Entity
#Table(name = "master_details")
public class MasterDetails {
#EmbeddedId
private MasterDetailsId id;
#MapsId
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumns({
#JoinColumn(name = "master_id", referencedColumnName = "id", nullable = false),
#JoinColumn(name = "version", referencedColumnName = "version", nullable = false)
})
private Master master;
#Lob
#Column(name = "details", nullable = false)
private String details;
}
MasterDetailsId.java
#Getter
#Setter
#Embeddable
public class MasterDetailsId implements Serializable {
private static final long serialVersionUID = -8375336118866998644L;
#Column(name = "master_id", nullable = false)
private BigDecimal masterId;
#Lob
#Column(name = "version", nullable = false)
private String version;
}
When running the SpringBoot application with this JPA structure the run time error received is:
org.hibernate.PropertyNotFoundException: Could not locate field [id] on class [org.project.packages.MasterDetails]
After removing the #MapsId that cause this error the application starts but when trying to insert data in the tables I get the following error:
org.hibernate.id.IdentifierGenerationException: null id generated for:class org.project.packages.MasterDetails
Checking in the H2 test database I noticed that the FK on the Master_Details table was not present, but only the PK was set.
I would appreciate any help in pointing out how this problem can be solved: other annotations required (Cascade/FetchType) or in case there are any changes to be made to the database level (I also tried adding a separate identifier column in the Master_Details table defined as PK and only keep the FK to the Master table). Thanks in advance!
After many tries, I figured out to solve the issue.
I had to use a common key between the two entities and also FetchType.LAZY.
MasterDetails.class
public class MasterDetails {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="ID", column=#Column(name="MASTER_ID"))
})
private MasterId id;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumns({
#JoinColumn(name = "master_id", referencedColumnName = "id", nullable = false),
#JoinColumn(name = "version", referencedColumnName = "version", nullable = false)
})
private Master master;
#Lob
#Column(name = "guidance", nullable = false)
private String guidance;
}
Master.class
public class MasterSheet {
#EmbeddedId
private MasterId id;
#OneToOne(mappedBy = "master", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private MasterDetails masterDetails;
}
I need your help with the following problem: there is a spring boot project, it has two entities:
Bank and CreditDetails, the bank refers to the details as OneToMany, the details as ManyToOne.
In the Bank entity, the cascade type is ALL, but when I try to delete the bank, I get an error, what could be the problem?
Bank:
#Entity
#Table(name = "banks")
#Getter
#Setter
#NoArgsConstructor
public class Bank {
#Id
#Column(name = "bank_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#OneToMany(orphanRemoval = true,cascade = CascadeType.ALL,
mappedBy = "bank", fetch = FetchType.LAZY)
private List<CreditDetails> creditDetails = new ArrayList<>();
#OneToMany(orphanRemoval = true,cascade = CascadeType.ALL,
mappedBy = "bank", fetch = FetchType.LAZY)
private List<Client> clients = new ArrayList<>();
}
Credit Details:
#Entity
#Table(name = "credit_details")
#Getter
#Setter
#NoArgsConstructor
public class CreditDetails {
#Id
#Column(name = "credit_details_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "credit_limit")
private BigDecimal creditLimit;
#Column(name = "credit_percent")
private BigDecimal creditPercent;
#ManyToOne(targetEntity = Bank.class, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
#JoinColumn(name = "bank_id")
private Bank bank;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "creditDetails")
List<CreditOffer> creditOffers;
}
Entities in DB(H2)
create table banks
(
bank_id uuid primary key
);
create table credit_details
(
credit_details_id uuid primary key,
credit_limit bigint,
credit_percent numeric(5, 2),
bank_id uuid references banks (bank_id),
primary key (credit_details_id)
);
Stacktrace:
Referential integrity constraint violation: "CONSTRAINT_8: PUBLIC.CREDIT_DETAILS FOREIGN KEY(BANK_ID) REFERENCES PUBLIC.BANKS(BANK_ID) ('ae1ce5c1-b1eb-4ee7-a1a2-63d831b0fd0a')";
I reconstructed your setup using spring, hibernate, an H2 database and a Postgres database. For me everything worked as intended.
To test the entities I used a BankRepository:
public interface BankRepository extends CrudRepository<Bank, UUID> {}
and a very simple RestController:
private final BankRepository bankRepository;
#DeleteMapping
public void removeBank(#RequestParam String uuid) {
bankRepository.deleteById(UUID.fromString(uuid));
}
#PostMapping("/add")
public Bank addBank() {
var bank = new Bank();
var creditDetails = new CreditDetails();
creditDetails.setBank(bank);
bank.setCreditDetails(List.of(creditDetails));
bankRepository.save(bank);
return bank;
}
You might geht the error if you try to delete the Bank entity via some SQL directly or if you manually delete it from your database. Could you set
spring.jpa.show-sql: true
and post the generated JPA Queries? Mine looked like this:
Hibernate: select bank0_.bank_id as bank_id1_1_0_ from banks bank0_ where bank0_.bank_id=?
Hibernate: select creditdeta0_.bank_id as bank_id4_2_0_, creditdeta0_.credit_details_id as credit_d1_2_0_, creditdeta0_.credit_details_id as credit_d1_2_1_, creditdeta0_.bank_id as bank_id4_2_1_, creditdeta0_.credit_limit as credit_l2_2_1_, creditdeta0_.credit_percent as credit_p3_2_1_ from credit_details creditdeta0_ where creditdeta0_.bank_id=?
Hibernate: delete from credit_details where credit_details_id=?
Hibernate: delete from banks where bank_id=?
Job entity
#Column(name = "name")
private String name;
#ManyToMany
#JoinTable(name = "user_job",
joinColumns = #JoinColumn(name = "job_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private List<User> user;
User entity
#Column(name = "email")
private String email;
#ManyToMany
#JoinTable(name = "user_job",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles;
Role entity
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
Here we have a table user_job with 3 ids and I want to insert data in service layer. How I can do it and what repository I should implement or use existent like user/role/job?
class UserJobService{
public void setUserJob(User user, Job job, Role role){
}
}
The problem with #ManyToMany association is you can't delete a record directly from user_job table, using Hibernate. To delete the record, you need to load a user with all his jobs. So better to add UserJobEntity
#Entity
#Table(name = "USER_JOBS")
class UserJobEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "JOB_ID")
private Job job;
}
public UserJobEntity createUserJob(UserEntity user, JobEntity job) {
UserJobEntity userJob = new UserJobEntity();
userJob.setUser(user);
userJob.setJob(job);
return userJobRepository.save(userJob);
}
Probably you will want to add (user, job) unique constraint to user_jobs table.
Some advices
Use plurals for table names. user_jobs in place of user_job
Role is tabular data. So it shouldn't have a users List.
Don't use Set for associated collections. Definitely you will encounter "multiple bugs fetch exception" and this exception will help you to change queries. With Set you can have large cross products and even don't notice them.
I have 2 class
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String age;
#OneToOne
#JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
}
and
public class Address {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String building;
private String country;
#OneToOne(mappedBy = "address")
private User user;
}
in my table address, I have a few rows.
When I insert table user with data
{
"id":null,
"name":"Foo",
"age":"18",
"address":{
"id":1,
"building":"Too",
"country":"ABS"
}
}
Table user have 1 row with address_id =1.
I insert same data as above
Table user have 2 row with address_id =1.
My answer is: why 2 table connected by one to one can happen the above case?
You can find your answer here
Why #OneToOne is allowing duplicate associations?
Basically, #JoinColumn(name = "address_id", referencedColumnName = "id") alone doesn't serve the semantics of one-to-one in the database, you need to add unique=true into the #JoinColumn, which makes it #JoinColumn(name = "address_id", referencedColumnName = "id", unique = true).
Side-note: I suggest you drop your tables and then re-creating them before trying this out. If you are using Hibernate, you can set hibernate.hbm2ddl.auto to create-drop
I have 2 entities and want to perform an inner join on the ID of these two tables. How do I do that? After joining the tables, how do I get the values?
First entity: Employee.java
#Entity
#Table(name = "emp")
public class Employee {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "language", nullable = false)
private String language;
Second entity: Username.java
#Entity
#Table(name = "users")
public class Username {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "name", nullable = false)
private String name;
Thanks
I don't know it's helpful for your or not but,
You have to give relationship between those table first(Here i defined bidirectional relationship).
I suppose there is #OneToOne mapping. As like follow,
In Employee Table,
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "username_id")
private Username username;
#OneToOne(mappedBy = "employee")
private Employee employee;
Same way whenever you need those data base on requirement then Place Query as following way in your Employee Repository,
#Query(nativeQuery = true, value="<your-join-query>")
public Employee getEmployeeAllDetails();
For more brief detail follow this kind of tutorials which give you better idea regurding working mechenisum.
https://howtodoinjava.com/
https://www.baeldung.com/