How can I simply add a link to a Spring Data REST Entity

后端 未结 3 1920
你的背包
你的背包 2021-01-02 07:20

I have my Entities with Spring Data JPA, but to generate stats about them, I use jOOQ in a Spring @Repository.

Since my methods return either a Li

相关标签:
3条回答
  • 2021-01-02 07:36

    For manually create links see spring-hateoas-examples. Simplest wai is via new Resource if no DTO and extends ResourceSupport for DTO.

    Links to spring-data-rest managed entity I've customized similar to links to root resource:

    MyController implements ResourceProcessor<Resource<ManagedEntity>> {
    
       @Override
       public Resource<Restaurant> process(Resource<ManagedEntity> resource) {
           resource.add(linkTo(methodOn(MyController.class)
               .myMethod(resource.getContent().getId(), ...)).withRel("..."));
           return resource;
    }
    

    And for paged resourses

    MyController implements ResourceProcessor<PagedResources<Resource<ManagedEntity>>>
    

    The problem is when you need both, as yor cannot extends both this interface due to generic type erasure. As a hack I've created dummy ResourceController

    0 讨论(0)
  • 2021-01-02 07:48

    The best way to add links is to consider Spring-HATEOAS, which makes code look even cleaner.

    One word of advice: Always use org.springframework.http.ResponseEntity for returning response to clients as it allows easy customisation of response.

    So as your requirement is to send links in the response, then for this best practice suggested is to use a type of ResourceSupport(org.springframework.hateoas.ResourceSupport) and ResourceAssemblerSupport(org.springframework.hateoas.mvc.ResourceAssemblerSupport) to create resources which needs to be sent to the client.

    For Example: If you have a model object like Account then there must be few fields which you would not like the client to know about or to be included in the responce so to exclude those attributes from response we can use ResourceAssemblerSupport class'

    public TResource toResource(T t);

    method to generate the resource from a model object which needs to be sent as response.

    For instance we have a an Account Class like (Can be directly used for all server side interaction and operations)

    @Document(collection = "Accounts_Details")
    
    public class Account {
    
        @Id
        private String id;
    
        private String username;
        private String password;
        private String firstName;
        private String lastName;
        private String emailAddress;
        private String role;
        private boolean accountNonExpired;
        private boolean accountNonLocked;
        private boolean credentialsNonExpired;
        private boolean enabled;
        private long accountNonLockedCounter;
        private Date lastPasswordResetDate;
        private Address address;
        private long activationCode;
    
        public Account() {
        }
    
        //getters and setters
    }
    

    Now from This POJO we will create a Resource object which will be sent to the client with selected attributes.

    For this We will create a Account resource which will include only the necessary fields which are viewable to client. and do that we create another class.

    @XmlRootElement
    
    public class AccountResource extends ResourceSupport {
    
        @XmlAttribute
        private String username;
        @XmlAttribute
        private String firstName;
        @XmlAttribute
        private String lastName;
        @XmlAttribute
        private String emailAddress;
        @XmlAttribute
        private Address address;
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
        public String getEmailAddress() {
            return emailAddress;
        }
        public void setEmailAddress(String emailAddress) {
            this.emailAddress = emailAddress;
        }
        public Address getAddress() {
            return address;
        }
        public void setAddress(Address address) {
            this.address = address;
        }
    
    
    }
    

    So now this resource is what the client will see or have to work with.

    After creating the blueprint of the AccountResource we need a way to convert our Model POJO to this resource and for that the suggested best practice is to create a ResourceAssemblerSupport Class and override the toResource(T t) method.

    import org.springframework.hateoas.mvc.ControllerLinkBuilder;
    import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
    import org.springframework.stereotype.Component;
    
    import com.brx.gld.www.api.controller.RegistrationController;
    import com.brx.gld.www.api.model.Account;
    
    @Component
    public class AccountResourceAssembler extends ResourceAssemblerSupport<Account, AccountResource> {
    
        public AccountResourceAssembler(Class<RegistrationController> controllerClass,
                Class<AccountResource> resourceType) {
            super(controllerClass, resourceType);
        }
    
        public AccountResourceAssembler() {
            this(RegistrationController.class, AccountResource.class);
        }
    
        @Override
        public AccountResource toResource(Account account) {
            AccountResource accountResource =  instantiateResource(account); //or createResourceWithId(id, entity) canbe used which will automatically create a link to itself.
            accountResource.setAddress(account.getAddress());
            accountResource.setFirstName(account.getFirstName());
            accountResource.setLastName(account.getLastName());
            accountResource.setEmailAddress(account.getEmailAddress());
            accountResource.setUsername(account.getUsername());
            accountResource.removeLinks();
            accountResource.add(ControllerLinkBuilder.linkTo(RegistrationController.class).slash(account.getId()).withSelfRel());
            return accountResource;
        }
    
    }
    

    In the toReource Method instead of using instanriateReource(..) we must use createdResourceWithId(id, entity) and then add the custum links to the resorce, which infact is again a best practice to consider, but for the sake of demonstration i have used instantiateResource(..)

    Now to use this in Controller :

    @Controller
    @RequestMapping("/api/public/accounts")
    public class RegistrationController {
    
        @Autowired
        private AccountService accountService;
    
        @Autowired
        private AccountResourceAssembler accountResourceAssembler;
    
        @RequestMapping(method = RequestMethod.GET)
        public ResponseEntity<List<AccountResource>> getAllRegisteredUsers() {
            List<AccountResource> accountResList = new ArrayList<AccountResource>();
            for (Account acnt : accountService.findAllAccounts())
                accountResList.add(this.accountResourceAssembler.toResource(acnt));
            return new ResponseEntity<List<AccountResource>>(accountResList, HttpStatus.OK);
        }
    
    /*Use the below method only if you have enabled spring data web Support or otherwise instead of using Account in @PathVariable usr String id or int id depending on what type to id you have in you db*/
    
        @RequestMapping(value = "{userID}", method = RequestMethod.GET)
        public ResponseEntity<AccountResource>  getAccountForID(@PathVariable("userID") Account fetchedAccountForId) {
            return new ResponseEntity<AccountResource>(
                    this.accountResourceAssembler.toResource(fetchedAccountForId), HttpStatus.OK);
        }
    

    To enable Spring Data Web support which adds few more funcationality to yo code like automatically fetching model data from DB based on the id passed like we used in the previous method.

    Now returning to the toResource(Account account) method: in this first the resource object is initialised and then the desired props are set and then the links are added to the AccountResorce by using the static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo(..) method and then the controller class is passed in from which it picks teh base url and and after that the url is built using slash(..) and so on so forth. After the complete path is specified we use the rel method to specify the relation(like here we used withSelfRel() to specify the relation to be it self. For others relations we can use withRel(String relation) to be more descriptive. So in our code in the toResource method we used something like accountResource.add(ControllerLinkBuilder.linkTo(RegistrationController.class).slash(account.getId()).withSelfRel());

    which will build the URL as /api/public/accounts/{userID}

    Now in postman if we use a get on this url http://localhost:8080/api/public/accounts

    {
        "username": "Arif4",
        "firstName": "xyz",
        "lastName": "Arif",
        "emailAddress": "xyz@outlook.com",
        "address": {
          "addressLine1": "xyz",
          "addressLine2": "xyz",
          "addressLine3": "xyz",
          "city": "xyz",
          "state": "xyz",
          "zipcode": "xyz",
          "country": "India"
        },
        "links": [
          {
            "rel": "self",
            "href": "http://localhost:8080/api/public/accounts/5628b95306bf022f33f0c4f7"
          }
        ]
      },
      {
        "username": "Arif5",
        "firstName": "xyz",
        "lastName": "Arif",
        "emailAddress": "xyz@gmail.com",
        "address": {
          "addressLine1": "xyz",
          "addressLine2": "xyz",
          "addressLine3": "xyz",
          "city": "xyz",
          "state": "xyz",
          "zipcode": "xyz",
          "country": "India"
        },
        "links": [
          {
            "rel": "self",
            "href": "http://localhost:8080/api/public/accounts/5628c04406bf23ea911facc0"
          }
        ]
      }
    

    click on any of the link and send the get request the response will be http://localhost:8080/api/public/accounts/5628c04406bf23ea911facc0

    {
        "username": "Arif5",
        "firstName": "xyz",
        "lastName": "Arif",
        "emailAddress": "xyz@gmail.com",
        "address": {
          "addressLine1": "xyz",
          "addressLine2": "xyz",
          "addressLine3": "xyz",
          "city": "xyz",
          "state": "xyz",
          "zipcode": "xyz",
          "country": "India"
        },
        "links": [
          {
            "rel": "self",
            "href": "http://localhost:8080/api/public/accounts/5628c04406bf23ea911facc0"
          }
        ]
      }
    
    0 讨论(0)
  • 2021-01-02 07:50

    See this from docs

    @Bean
    public ResourceProcessor<Resource<Person>> personProcessor() {
    
       return new ResourceProcessor<Resource<Person>>() {
    
         @Override
         public Resource<Person> process(Resource<Person> resource) {
    
          resource.add(new Link("http://localhost:8080/people", "added-link"));
          return resource;
         }
       };
    }
    
    0 讨论(0)
提交回复
热议问题