Spring boot one to one with foreign key - spring-boot

I am want to store room details and room types in my db. I have created an entity for Room as follows.
#Data
#Entity
public class Room {
#Id
private String room_id;
private Boolean status;
private Double price;
private Long type;
private Long occupancy;
#OneToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "type_id", nullable = false)
private RoomType roomType;
}
I have create a RoomType entity as follows:
#Data
#Entity
public class RoomType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String type;
}
To save a Room Entity in my DB, the following is the request body format need to be given
{
"room_id": "string",
"status": true,
"price": 0,
"roomType": {
"id": 1,
"type": "string"
}
}
I want to have the request body as follows
{
"room_id": "string",
"status": true,
"price": 0,
"roomType": 1 # This the roomType foreign key
}
I want to have one to one relationship, so when retrieving the Room details, I will get the roomType values also in the following format.
{
"room_id": "string",
"status": true,
"price": 0,
"roomType": {
"id": 0,
"type": "string"
}
}
Or Is there any better way to handle this type of problem in spring boot??

I think there are two different concepts in here:
The inner representation of a Room in your service (The classes you use and their relations)
The outer representation (The one exposed by your API)
If you want those two representation to be the same, you could just serialize/deserialize data using the Room class directly (which I assume is what you are currently doing). For example:
#Controller
class MyController {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public Room createRoom(#RequestBody Room room) {
// Here room will be automatically deserialized from the request body
// Do stuff with the received room in here
// ...
// Here the returned room will be automatically serialized to
// the response body
return createdRoom;
}
}
However, if you don't want the inner and outer representations to be the same, you can use the DTO pattern (Data Transfer Object). Example in Spring here.
Now, you would have three different classes, Room and it's DTOs:
public class RoomRequestDTO {
private String room_id;
private Boolean status;
private Double price;
private Long type;
private Long occupancy;
private Integer roomType; // This is now an Integer
// Setters and Getters ...
}
public class RoomResponseDTO {
private String room_id;
private Boolean status;
private Double price;
private Long type;
private Long occupancy;
private RoomType roomType; // This is a RoomType instance
// Setters and Getters ...
}
Now, your controller would receive RoomRequestDTOs and send RoomResponseDTOs:
#Controller
class MyController {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public RoomResponseDTO createRoom(#RequestBody RoomRequestDTO roomReqDTO) {
Room room = ... // Convert from DTO to Room
// Do stuff with the received room in here
RoomResponseDTO roomRespDTO = ... // Convert to the DTO
return roomRespDTO;
}
}
This approach also has the advantage that you can now change the inner representaion of Room without afecting the API. This is, you could decide to merge the Room and RoomType classes in one, without affecting the outer representation.
Note: In the example I have linked, they use the ModelMapper library. If you do not desire to introduce that dependecy, you could simple add a contructor and a method to the DTOs as such
public RoomResponseDTO(Room room) {
// Manually asign the desired fields in here
this.id = room.getId();
// ...
}
public Room toRoom() {
Room room = new Room();
// Manually asing the desired field in here
room.setId(this.id);
// ...
return room;
}
However, this approach would make you have to change these methods whenever you want to change the representations of Room. The ModeMapper library does this for you.

Related

Spring Boot Java map Entity to DTO: array literal (strings) INSTEAD of array of objects

sample get request: http://localhost:3000/contact/1
What I got:
{
"id": 1,
"firstname": "First Name",
"lastname": "Last Name",
"emailaddresses": [
{
"emailaddress": "email#gmail.com"
},
{
"emailaddress": "email#g.c"
}
]
}
What I want:
{
"id": 1,
"firstname": "First Name",
"lastname": "Last Name",
"emailaddresses": ["email#gmail.com","email#g.c"]
}
The code below:
PersonDto
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<EmailAddressDto> emailaddresses;
//getters setters
}
EmailAddressDto
public class EmailAddressDto {
private String emailaddress;
//getters and setters
}
the Service class
public PersonDto getPerson(Long personId) { //this is the method inside the class
Optional<PersonEntity> p = peopleRepository.findById(personId);
var dto = modelMapper.map(p.get(), PersonDto.class);
return dto;
}
I also have a PersonEntity class mapped one-to-many to an EmailAddressesEntity class.
I'm really new to spring/java - I couldn't figure out how to get the JSON structure I want.
You can just annotate emailaddress field of EmailAddressDto with #JsonValue and leave everything as is.
public class EmailAddressDto {
#JsonValue
private String emailaddress;
//getters and setters
}
Using the above the output of a sample:
PersonDto personDto = new PersonDto();
personDto.setId(1L);
personDto.setFirstname("John");
personDto.setLastname("Doe");
personDto.setEmailaddresses(Arrays.asList(new EmailAddressDto("john#doe.com"), new EmailAddressDto("foo#bar.com")));
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(personDto);
System.out.println(json);
is:
{"id":1,"firstname":"John","lastname":"Doe","emailaddresses":["john#doe.com","foo#bar.com"]}
I'd suggest that you use a List of Strings instead of a List of EmailAddressDto's.
Following reasons:
Since you only have one attribute in your Dto, you can easily just directly use a List of Strings instead.
You get the second JSON-Layout as a response to your GET-Request.
When using variant number 1 (with the List of EmailAddressDto), you will achieve a JSON-Response with multiple objects for your different E-Mail addresses.
Otherwise when you use variant number 2 (with the List of String), you will achieve a JSON-Response which looks like what you want to have.
So don't forget to change your entities aswell.
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<String> emailAddresses;
//getters setters
}
If you can change your PersonDto that would be the easiest and cleanest way to do it.
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<String> emailaddresses;
//getters setters
}
While mapping your entities you would need to map EmailAddressesEntity to a String representing it (emailaddress).
If this is not possible you will need a custom converter for EmailAddressDto as follows:
public class ListEmailAddressDtoConverter extends StdConverter<List<EmailAddressDto>, List<String>> {
#Override
public List<String> convert(List<EmailAddressDto> emailAddresses) {
return emailAddresses.stream().map(EmailAddressDto::getEmailaddress).collect(Collectors.toList());
}
}
Then you need to tell Jackson to use it:
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
#JsonSerialize(converter = ListEmailAddressDtoConverter.class)
private List<EmailAddressDto> emailaddresses;
//getters setters
}

How to add new fields / values to an existing entity in Spring Data JPA

How can I add new fields or values or properties to an existing entity and store them in the same table?
Customer entity already exists and have fields as
- id
- name
- lastName
Want to add contactNumber (as seen in the api structure below) to this existing Customer entity. Don't want a new entity for contactNumber
The expected request body is as below:
{
"id": "int auto generated",
"name": "String",
"lasName": "String",
"contactNumber":
{
"mobile":"long",
"office":"long",
"home":"long"
}
}
How can this be achieved ? Checked some blogs related to mapstruct but not getting proper solution.
You can use #Embeddable :
#Embeddable
public class ContactNumber {
private Long mobile;
private Long office;
private Long home;
// getters, setters...
}
Customer Entity:
#Entity
public class Customer {
#Id
private Long id;
private String name;
private String lastName;
#Embedded
private ContactNumber contactNumber;
// getters, setters...
}
With this mapping three columns(mobile, office, home) will be added to the Customer table.
You can simply save the Customer with the request body in the question using (#RequestBody Customer customer) parameter:
#PostMapping(value="/customers")
public void saveCustomers(#RequestBody Customer customer) {
customerRepository.save(customer);
}
For more information take a look at here.

Passing parent id reference when creating child object through REST api

I am using spring boot (version - 2.1.1). I have a one to many database model exposed for CRUD operations through rest api's. The model looks as below. How do I configure the POST /departments api (that creates a department object) to accept just the organization id in the input json body?
#PostMapping
public Long createDepartment(#RequestBody Department Department) {
Department d = departmentService.save(Department);
return d.getId();
}
Note - I do not want to allow creating organization object when creating a department.
Model object mapping
#Entity
#Table(name="ORGANIZATIONS")
public class Organization{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
#OneToMany(mappedBy = "organization", fetch = FetchType.EAGER)
private List<Department> departments;
}
#Entity
#Table(name="DEPARTMENTS")
Public class Department{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Organization organization;
}
Thanks!
The easiest and most sane way in my opinion is to utilize the DTO (Data Transfer Object) pattern.
Create a class that represent the model you want to get as your input:
public class CreateDepartmentRequest {
private long id;
// getters and setters
}
Then use it in your controller:
#PostMapping
public Long createDepartment(#RequestBody CreateDepartmentRequest request) {
Department d = new Department();
d.setId(request.getId());
Department d = departmentService.save(d);
return d.getId();
}
Side note, its better to ALWAYS return JSON through REST API (unless you use some other format across your APIs) so you can also utilize the same pattern as I mentioned above to return a proper model as a result of the POST operation or a simple Map if you don't want to create to many models.

Lazy attribute is null inside transaction after creation

I have a small example with some get/post mappings and JpaRepository calls in Spring Boot.
Firstly I have two entity Classes:
#Entity
#Table(name = "stock")
public class Stock extends BaseEntity
{
#Column(name = "value")
public String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
#Column(name = "stock_id")
public Long stockId;
#Column(name = "value")
public String value;
}
I have a many-to-one association from StockItem to Stock.
I insert a Stock and have a controller as below:
#Autowired
public Controller(StockItemRepository stockItemRepository) {
this.stockItemRepository = stockItemRepository;
}
#RequestMapping("/")
#Transactional(readOnly = true)
public String get() {
List<StockItem> stockItemList = stockItemRepository.getItemsById(1L);
System.out.println("TX MANAGER: " + TransactionSynchronizationManager.isActualTransactionActive());
for (StockItem stockItem : stockItemList) {
System.out.println(stockItem.getStock().getValue());
}
return "get";
}
#RequestMapping("/fromSave")
#Transactional
public String post() {
StockItem stockItem = new StockItem();
stockItem.setStockId(1L);
stockItemRepository.saveAndFlush(stockItem);
System.out.println("saveCalled");
return get();
}
and getItemsById in the repository is defined as follows:
#Query("FROM StockItem si " +
"JOIN FETCH si.stock stk " +
"WHERE si.stockId = :id")
List<StockItem> getItemsById(#Param("id") Long id);
From my understanding, when I call the post method:
it creates a new item
sets the id of the associated attribute
saves and ends the transaction
Heres where things get strange...
I call get after the post and make the above repository call, which has a join fetch and when I call stockitem.getStock().getValue() I get a null pointer when I expect a LazyInitializationException.
If I call the get() from the mapping, outside the class, it successfully loads the associated object.
I have even removed the #Transaction annotation from the get, as well as
the join-fetch from my query and again, if I call from outside of the class it works and from the post, it crashes with a NullPointerException.
I have put the get inside of a TransactionTemplate.execute() and I still get a NullPointerException when calling from inside the class.
So the main questions are:
Why am I getting a NullPointerException instead of LazyInitializationException?
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
The problem here is that you are misusing JPA. As you are seemingly aware judging from the comments on the other answer you have mapped the stock_id column twice. Once as a many-to-one relationship
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
and once as a simple column
#Column(name = "stock_id")
public Long stockId;
When you set the simple column and flush the changes as in your post() method the following happens:
the value gets set in the simple column. The reference is still null.
the value gets stored in the database. The reference is still null.
The repository call will find the id of the StockItemin the Persistence Context and return that instance, i.e. the exact same used in the post method, with the reference still null.
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
No magic involved here. fetch specifications are only used for object traversal. JPQL queries don't honor these.
The unasked question remains: how to fix the situation?
The obvious fix is to lose the simple column and just use entity references as intended by JPA.
You don't want to do that in order to avoid DB access somewhere. But as long as you only access the id of the referenced Stock it shouldn't get initialized. So it seems that this should be possible with just Lazy Fetching.
Alternatively, I'd suggest removing the many-to-one relationship and creating a repository for Stock and manually loading it when required.
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false) //here is your problem
public Stock stock;
#Column(name = "stock_id")
public Long stockId; // why explicitly define a separate column for foreign key after mapping it above
#Column(name = "value")
public String value;
}
with insertable = false and updatable = false it won't insert in your DB and neither it will allow updation, so you are getting NullPointerException. You should atleast allow insertion in order to run the query based on the foreign key stock_id
UPDATE
Change your Entity class with property-based access:
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
private Stock stock; // variables should always be private since you have getters and setters
private String value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", updatable = false)
public Stock getStock() {
return stock;
}
public void setStock(Stock stock) {
this.stock = stock;
}
#Basic
#Column(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

Spring Data Rest #EmbeddedId cannot be constructed from Post Request

I have a JPA entity Person and an entity Team. Both are joined by an entity PersonToTeam. This joining entity holds a many-to-one relation to Person and one to Team. It has a multi-column key consisting of the ids of the Person and the Team, which is represented by an #EmbeddedId. To convert the embedded id back and forth to the request id I have a converter. All this follows the suggestion on Spring Data REST #Idclass not recognized
The code looks like this:
#Entity
public class PersonToTeam {
#EmbeddedId
#Getter
#Setter
private PersonToTeamId id = new PersonToTeamId();
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "person_id", insertable=false, updatable=false)
private Person person;
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "team_id", insertable=false, updatable=false)
private Team team;
#Getter
#Setter
#Enumerated(EnumType.STRING)
private RoleInTeam role;
public enum RoleInTeam {
ADMIN, MEMBER
}
}
#EqualsAndHashCode
#Embeddable
public class PersonToTeamId implements Serializable {
private static final long serialVersionUID = -8450195271351341722L;
#Getter
#Setter
#Column(name = "person_id")
private String personId;
#Getter
#Setter
#Column(name = "team_id")
private String teamId;
}
#Component
public class PersonToTeamIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return delimiter.equals(PersonToTeam.class);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
if (id != null) {
PersonToTeamId ptid = new PersonToTeamId();
String[] idParts = id.split("-");
ptid.setPersonId(idParts[0]);
ptid.setTeamId(idParts[1]);
return ptid;
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.fromRequestId(id, entityType);
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
if (id instanceof PersonToTeamId) {
PersonToTeamId ptid = (PersonToTeamId) id;
return String.format("%s-%s", ptid.getPersonId(), ptid.getTeamId());
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.toRequestId(id, entityType);
}
}
The problem with this converter is, that the fromRequestId method gets a null as id parameter, when a post request tries to create a new personToTeam association. But there is no other information about the payload of the post. So how should an id with foreign keys to the person and the team be created then? And as a more general question: What is the right approach for dealing many-to-many associations in spring data rest?
After running into the same issue I found a solution. Your code should be fine, except I return new PersonToTeamId() instead of the DefaultIdConverter if id is null in fromRequestId().
Assuming you are using JSON in your post request you have to wrap personId and teamId in an id object:
{
"id": {
"personId": "foo",
"teamId": "bar"
},
...
}
And in cases where a part of the #EmbeddedId is not a simple data type but a foreign key:
{
"id": {
"stringId": "foo",
"foreignKeyId": "http://localhost:8080/path/to/other/resource/1"
},
...
}

Resources