ITWorker
ITWorker

Reputation: 995

In spring boot JPA, how to properly POST an object whose entity representation has a foreign key association to a different entity?

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

Answers (2)

kaliatech
kaliatech

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

iamiddy
iamiddy

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

Related Questions