I am working on a bike rental system project. I am able to do CRUD operations via Rest API on Bike, and Customer and create a Contract object. For this, I have exposed CRUD endpoints(s) for
- /bike
- /customer
- /contract
The POST /contract/final endpoint should return a complete contract overview with contractid, bike details, and customer
The association I am using is as below
class Contract {
@Id
@GeneratedValue(strategy= GenerationType.SEQUENCE)
private id;
@ManyToOne(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="customerId")
private Customer customer;
@ManyToOne(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="bikeId")
private Bike bike;
}
class Customer{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "customer_id")
private Long customerId;
private String firstName;
}
class Bike{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "bike_id")
private long bikeId;
private String model;
}
So I just want to be able to create/modify the bike|customer records and create a contract with the already created customer(s) and bike(s).
As of now, the contract request will create the customer and bike records apart from records created separately using /customer, /bike endpoints - which I do not want.
Is the association mapping I have used correct for this requirement? What should ideally be the relationship between these entities given the below contraints?
One customer can have many contracts but one contract will have only one customer
One bike can have many contracts but one contract will have only one bike
How can I make use of the existing customer/bike records while creating a contract?
CodePudding user response:
Do you want to have the ability to change a
Bike
model when you create a newContract
? Obviously no!cascade = CascadeType.ALL
is about such changes. So throw it away.Do you want to load all the
Contract
data, like aBike
, when you load aContract
? It is not so obvious, but my advice don't do this! Let's changefetch=FetchType.EAGER
tofetch=FetchType.LAZY
.You use two strategies to generate an
id
—strategy= GenerationType.SEQUENCE
,strategy = GenerationType.IDENTITY
. Use only one.Don't use a primitive type for an
id
. Changelong bikeId
toLong bikeId
.@JoinColumn
specifies a database column name, not a class property. Change@JoinColumn(name="customerId")
to@JoinColumn(name="CUSTOMER_ID")
. Use uppercase for column names.
class Contract {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CUSTOMER_ID")
private Customer customer;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "BIKE_ID")
private Bike bike;
}
Assign existing Bike
and Customer
to the Contract
, using getReferenceById()
method. It doesn't hit a database at all.
Customer customer = customerRepository.getReferenceById(customerId);
contract.setCustomer(customer);
Another way is to use a transient customer.
Customer customer = new Customer();
customer.setId(customerId);
contract.setCustomer(customer);
But standard repository save()
method uses merge()
. So it will be an additional SQL request to load a customer. You can use custom repository with pure save()
method to avoid this
https://vladmihalcea.com/best-spring-data-jparepository/