Reputation: 995
If I have a entity that contains an object of an another class, for example a Book
entity that has within it a Publisher
entity that is associated as follows:
@ManyToOne
@JoinColumn(name="PUB_CODE", referencedColumnName = "PUB_CODE")
private Publisher pub;
Is this a secure/correct (I saw the correct data in the DB in this example, but not 100% sure if it would work in all cases) approach to post an object that has foreign key association in the database? I don't know if this is safe to do in terms of transaction atomicity or in terms of threading, or if it is efficient. Relevant code below:
Book.java
package app.domain;
/*imports*/
@Entity
public class Book implements Serializable{
/**
*
*/
private static final long serialVersionUID = -6902184723423514234L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(nullable = false, unique=true)
private String bookName;
@Column(nullable = false)
private int pageCount;
@ManyToOne
@JoinColumn(name="PUB_CODE", referencedColumnName="PUB_CODE")
private Publisher pub;
/*public getters and setters*/
}
Publisher.java
package app.domain;
/*imports*/
@Entity
public class Publisher implements Serializable {
private static final long serialVersionUID = 4750079787174869458L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(name="PUB_CODE",nullable = false, unique = true)
private String publisherCode;
@Column(nullable = false)
private String publisherName;
/*public getters and setters*/
}
BookRepo.java
package app.service;
/*imports*/
public interface BookRepo extends JpaRepository<Book, Long>{
@Query("SELECT pb FROM Publisher pb WHERE pb.publisherCode = TRIM(UPPER(:pubCode))")
public Publisher findPublisherByPubCode(@Param("pubCode")String pubCode);
}
BookController.java
package app.controller;
/*imports*/
@RestController
@RequestMapping(value = "/books")
public class BookController {
private BookRepo bookRepo;
@Autowired
public BookController(BookRepo bookRepo) {
this.bookRepo = bookRepo;
}
//The ApiPathParam is for JSONDOC purposes
@RequestMapping(value = "/create", method = RequestMethod.POST)
public List<Book> create(@ApiPathParam(name = "book") @RequestBody Book book, @ApiPathParam(name = "pubCode") @RequestParam("pubCode") String pubCode) {
// Assume exception handling
Publisher pbToAttachToThisBook = bookRepo.findPublisherByPubCode(pubCode);
book.setPub(pbToAttachToThisBook);
bookRepo.save(book);
return bookRepo.findAll();
}
}
Post object body (input into a POST tool):
{
"bookName": "goosebumps",
"id": 0,
"pageCount": 332,
"pub": {
"id": 0,
"publisherCode": "",
"publisherName": "",
"serialVersionUID": 0
},
"serialVersionUID": 0
}
pubCode parameter input provided, also into the POST tool, in the same call as above: 'SC'
After the above code was executed, in the Book table, there was an entry for the book above, with its PUB_CODE
foreign key column filled in with 'SC'
, and the returned List<Book>
of the POST controller method that was called showed that the newly added book included the Publisher
entity information (such as the full name "Scholastic") for publisher with PUB_CODE='SC'
that was already existing in the database.
Thank you.
Upvotes: 8
Views: 6991
Reputation: 17867
The technique you posted originally (passing the FK ID, retrieving it manually in your controller, and setting it on the entity explicitly) is valid and secure.
I don't know of a cleaner approach unless you move to HATEOAS principals, which allows for resource link handling: http://projects.spring.io/spring-hateoas/
Upvotes: 2
Reputation: 3073
Sounds like you need to separate/decouple
your data layer's domain model
from your Rest Resources/ API specs
, as they could evolve at a different pace. Also your choice of JPA
should not influence the API specs
.
Should this feel like something you want to pursue there lots of resources out there including Best Practices for Better RESTful API
Upvotes: 1