|
|
9 anni fa | |
|---|---|---|
| src | 9 anni fa | |
| .gitignore | 9 anni fa | |
| README.md | 9 anni fa | |
| pom.xml | 9 anni fa |
io.zipcoder.tc_spring_poll_application named domain.OptionOption class in the domain sub-package.Option class signature is annotated with @EntityOption has an id instance variable of type Long
id should be annotated with
@Id
@GeneratedValue
column(field)@Column(name = "OPTION_ID")
Option has a value instance variable of type String
value should be annotated with
@Column(name = "OPTION_VALUE")Create a getter and setter for each of the respective instance variables.
PollPoll class in the domain sub-package.Poll class signature is annotated with @EntityPoll has an id instance variable of type Long
id should be annotated with
@Id@GeneratedValueColumn(name = "POLL_ID")Poll has a question instance variable of type String
question should be annotated with
@Column(name = "QUESTION")Poll has an options instance variable of type Set of Option
options should be annotated with
@OneToMany(cascade = CascadeType.ALL)@JoinColumn(name = "POLL_ID")@OrderByCreate a getter and setter for each of the respective instance variables.
VoteVote class in the domain sub-package.Vote class signature is annotated with @EntityVote has an id instance variable of type Long
id should be annotated with
@Id@GeneratedValueColumn(name = "VOTE_ID")Vote has a option instance variable of type Option
option should be annotated with
@ManyToOne@JoinColumn(name = "OPTION_ID")Create a getter and setter for each of the respective instance variables.
findById, findAll, for retrieving data, and methods to persist and delete data.Repository per domain object.io.zipcoder.tc_spring_poll_application named repositories.OptionRepositoryOptionRepository interface in the repositories subpackage.OptionRepository extends CrudRepository<Option, Long>PollRepositoryPollRepository interface in the repositories subpackage.PollRepository extends CrudRepository<Poll, Long>VoteRepositoryVoteRepository interface in the repositories subpackage.VoteRepository extends CrudRepository<Vote, Long>io.zipcoder.tc_spring_poll_application named controller.PollControllerCreate a PollController class in the controller sub package.
PollController signature should be annotated with @RestControllerPollController has a pollRepository instance variable of type PollRepository
pollRepository should be annotated with @InjectGET request methodGET request on the /polls endpoint which provides a collection of all of the polls available in the QuickPolls application. Copy and paste this into your PollController class.@RequestMapping(value="/polls", method= RequestMethod.GET)
public ResponseEntity<Iterable<Poll>> getAllPolls() {
Iterable<Poll> allPolls = pollRepository.findAll();
return new ResponseEntity<>(allPolls, HttpStatus.OK);
}
PollRepository.ResponseEntity and pass in Poll data and the HttpStatus.OK status value.Poll data becomes part of the response body and OK (code 200) becomes the response status code.start-class tag in your pom.xml encapsulates io.zipcoder.springdemo.QuickPollApplicationmvn spring-boot:runhttp://localhost:8080/polls and hit Send.POST request methodPollController by implementing the POST verb functionality in a createPoll method:@RequestMapping(value="/polls", method=RequestMethod.POST)
public ResponseEntity<?> createPoll(@RequestBody Poll poll) {
poll = pollRepository.save(poll);
return new ResponseEntity<>(null, HttpStatus.CREATED);
}
@RequestBody Poll poll
@RequestBody tells Spring that the entire request body needs to be converted to an instance of PollPoll persistence to PollRepository’s save method
poll = pollRepository.save(poll);createPollServletUriComponentsBuilder utility class. This will ensure that the client has some way of knowing the URI of the newly created Poll.URI newPollUri = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(poll.getId())
.toUri();
createPoll method so that it returns a ResponseEntity which takes an argument of a new HttpHeaders() whose location has been set to the above newPollUri via the setLocation method.GET request method@RequestMapping takes a URI template /polls/{pollId}.{pollId} along with @PathVarible annotation allows Spring to examine the request URI path and extract the pollId parameter value.PollRepository’s findOne finder method to read the poll and pass it as part of a ResponseEntity.@RequestMapping(value="/polls/{pollId}", method=RequestMethod.GET)
public ResponseEntity<?> getPoll(@PathVariable Long pollId) {
Poll p = pollRepository.findOne(pollId);
return new ResponseEntity<> (p, HttpStatus.OK);
}
UPDATE request methodRequestMapping(value="/polls/{pollId}", method=RequestMethod.PUT)
public ResponseEntity<?> updatePoll(@RequestBody Poll poll, @PathVariable Long pollId) {
// Save the entity
Poll p = pollRepository.save(poll);
return new ResponseEntity<>(HttpStatus.OK);
}
DELETE request method.@RequestMapping(value="/polls/{pollId}", method=RequestMethod.DELETE)
public ResponseEntity<?> deletePoll(@PathVariable Long pollId) {
pollRepository.delete(pollId);
return new ResponseEntity<>(HttpStatus.OK);
}
POST to http://localhost:8080/polls/ whose request body is the JSON object below.Body tab, selecting the raw radio button, and selecting the JSON option from the text format dropdown.{
"id": 1,
"question": "What's the best netflix original?",
"options": [
{ "value": "Black Mirror" },
{ "value": "Stranger Things" },
{ "value": "Orange is the New Black"},
{ "value": "The Get Down" }
]
}
VoteControllerPollController, we implement the VoteController class.VoteController class along with the functionality to create a vote.VoteController uses an injected instance of VoteRepository to perform CRUD operations on Vote instances.@RestController
public class VoteController {
@Inject
private VoteRepository voteRepository;
@RequestMapping(value = "/polls/{pollId}/votes", method = RequestMethod.POST)
public ResponseEntity<?> createVote(@PathVariable Long pollId, @RequestBody Vote
vote) {
vote = voteRepository.save(vote);
// Set the headers for the newly created resource
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(ServletUriComponentsBuilder.
fromCurrentRequest().path("/{id}").buildAndExpand(vote.getId()).toUri());
return new ResponseEntity<>(null, responseHeaders, HttpStatus.CREATED);
}
}
VoteControllerPOST a new Vote to the /polls/1/votes endpoint with the option object expressed in JSON below.{
"option": { "id": 1, "value": "Black Mirror" }
}
VoteRepositoryfindAll in the VoteRepository retrieves all votes in a Database rather than a given poll.VoteRepository.public interface VoteRepository extends CrudRepository<Vote, Long> {
@Query(value = "SELECT v.* " +
"FROM Option o, Vote v " +
"WHERE o.POLL_ID = ?1 " +
"AND v.OPTION_ID = o.OPTION_ID", nativeQuery = true)
public Iterable<Vote> findVotesByPoll(Long pollId);
}
findVotesByPoll takes the ID of the Poll as its parameter.@Query annotation on this method takes a native SQL query along with the nativeQuery flag set to true.?1 placeholder with the passed-in pollId parameter value.VoteControllergetAllVotes method in the VoteController@RequestMapping(value="/polls/{pollId}/votes", method=RequestMethod.GET)
public Iterable<Vote> getAllVotes(@PathVariable Long pollId) {
return voteRepository. findByPoll(pollId);
}
java named dtosOptionCountOptionCount DTO contains the ID of the option and a count of votes casted for that option.public class OptionCount {
private Long optionId;
private int count;
public Long getOptionId() {
return optionId;
}
public void setOptionId(Long optionId) {
this.optionId = optionId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
VoteResultVoteResult DTO contains the total votes cast and a collection of OptionCount instances.import java.util.Collection;
public class VoteResult {
private int totalVotes;
private Collection<OptionCount> results;
public int getTotalVotes() {
return totalVotes;
}
public void setTotalVotes(int totalVotes) {
this.totalVotes = totalVotes;
}
public Collection<OptionCount> getResults() {
return results;
}
public void setResults(Collection<OptionCount> results) {
this.results = results;
}
}
ComputeResultControllerPollController and VoteController, we create a new ComputeResultController class@RestController
public class ComputeResultController {
@Inject
private VoteRepository voteRepository;
@RequestMapping(value = "/computeresult", method = RequestMethod.GET)
public ResponseEntity<?> computeResult(@RequestParam Long pollId) {
VoteResult voteResult = new VoteResult();
Iterable<Vote> allVotes = voteRepository.findVotesByPoll(pollId);
//TODO: Implement algorithm to count votes
return new ResponseEntity<VoteResult>(voteResult, HttpStatus.OK);
}
VoteRepository into the controller, which is used to retrieve votes for a given poll.computeResult method takes pollId as its parameter.@RequestParam annotation instructs Spring to retrieve the pollId value from a HTTP query parameter.ResponseEntity.QuickPoll application.status of 200 is returned by executing a GET request of http://localhost:8080/computeresults?pollId=1 via PostmanResourceNotFoundExceptionexception package inside of io.zipcoder.springdemo.QuickPollApplicationResourceNotFoundException class that extends RuntimeException. We'll use this to signal when a requested resource is not found.ResourceNotFoundException class with @ResponseStatus(HttpStatus.NOT_FOUND). This informs Spring that any request mapping that throws a ResourceNotFoundException should result in a 404 NOT FOUND http status.String message and passes it to the superclass constructorString message and Throwable cause and passes both to the superclass constructorCreate a void method in PollController called verifyPoll that checks if a specific poll id exists and throws a ResourceNotFoundException if not. Use this in any method that searches for or updates an existing poll (eg: Get, Put, and Delete methods).
Note: This means that trying to submit a PUT request for a resource that doesn't exist will not implicitly create it; it should throw a 404 instead.
Spring provides some built-in exception handling and error response, but we'll customize it a bit here. Create an ErrorDetail class in a new io.zipcoder.tc_spring_poll_application.dto.error package to hold relevant information any time an error occurs.
Fields (Don't forget to provide getters and setters):
String title: a brief title of the error condition, eg: "Validation Failure" or "Internal Server Error"int status: the HTTP status code for the current request; redundant but useful for client-side error handlingString detail: A short, human-readable description of the error that may be presented to a userlong timeStamp: the time in milliseconds when the error occurredString developerMessage: detailed information such as exception class name or a stack trace useful for developers to debug@ControllerAdviceIn this section we add custom handling for the exceptions we created before. A @ControllerAdvice is an AOP feature that wraps a controller and adds some functionality when needed. In this case we are adding functionality only when an exception is thrown.
@ControllerAdviceNOT_FOUND status
new Date().getTime() for the timestampResourceNotFoundException@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> handleResourceNotFoundException(ResourceNotFoundException rnfe, HttpServletRequest request) {...}
Now it's time to make sure that all objects persisted to the database actually contain valid values. Use the org.hibernate.validator.constraints.NotEmpty and javax.validation.constraints.Size and javax.validation.Valid annotations for validation.
Poll class:
options should be @Size(min=2, max = 6)question should be @NotEmpty@Valid annotations to Poll objects in RequestMapping-annotated controller methods (there should be 2)In order to customize validation errors we'll need a class for error information. Create a ValidationError class in io.zipcoder.tc_spring_poll_application.dto.error with the following fields and appropriate getters and setters:
String codeString messageWe also need a new field in the ErrorDetail class to hold errors. There may be multiple validation errors associated with a request, sometimes more than one of the same type, so this field will be a collection, specifically a Map<String, List<ValidationError>> errors field.
RestExceptionHandler@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?>
handleValidationError( MethodArgumentNotValidException manve,
HttpServletRequest request){...}
In this handler we need to do the following:
ResponseEntity containing the error detail and the appropriate HTTP status code (400 Bad Request)List<FieldError> fieldErrors = manve.getBindingResult().getFieldErrors();
for(FieldError fe : fieldErrors) {
List<ValidationError> validationErrorList = errorDetail.getErrors().get(fe.getField());
if(validationErrorList == null) {
validationErrorList = new ArrayList<>();
errorDetail.getErrors().put(fe.getField(), validationErrorList);
}
ValidationError validationError = new ValidationError();
validationError.setCode(fe.getCode());
validationError.setMessage(messageSource.getMessage(fe, null));
validationErrorList.add(validationError);
}
Commonly used strings in your Java program can be removed from the source code and placed in a separate file. This is called externalizing, and is useful for allowing changes to text displayed without impacting actual program logic. One example of where this is done is in internationalization, the practice of providing multilingual support in an application, allowing users to use an application in their native language.
There are two steps needed here to externalize and standardize the validation error messages:
messages.properties file in the src/main/resources directory with the given properties below
messages.properties is a key-value file stored in plain text. Your IDE may have a table-based view or show the contents as text.properties files are a common idiom in Java applications; they contain additional information the application uses that doesn't impact the actual source code.MessageSource object in the RestExceptionHandler to set the message on ValidationError objects (ie: setMessage(messageSource.getMessage(fe,null)); )
CRUDRepository instances are.messages.properties content:
NotEmpty.poll.question=Question is a required field
Size.poll.options=Options must be greater than {2} and less than {1}
GET method resembling the following:
http://blog.example.com/posts?page=3http://blog.example.com/posts?page=3&size=20JSON object below.{
"data": [
... Blog Data
],
"totalPages": 9,
"currentPageNumber": 2,
"pageSize": 10,
"totalRecords": 90
}
Create a src/main/resource/import.sql file with DML statements for populating the database upon bootstrap. The import.sql should insert at least 15 polls, each with 3 or more options.
Below is an example of SQL statements for creating a single poll with only one option.
Poll Creation
insert into poll (poll_id, question) values (1, 'What is your favorite color?');
Option Creation
insert into option (option_id, option_value, poll_id) values (1, 'Red', 1);
Restart your application.
Use Postman to ensure database is populated by import.sql.
org.springframework.data.repository.PagingAndSortingRepository.Controller methods to handle Pageable arguments.GET request to http://localhost:8080/polls?page=0&size=2 via Postman.JSON object with pagination-specific information.