These notes will cover a couple of suggestions I have for you for the final project. The first suggestion is aimed to head off a technical problem with posts, while the second is a suggestion to make your development process faster and easier.
The JavaScript code that the 106 folks are using to handle POST requests has one peculiarity you need to be aware of. If your server responds to a POST request with a 200 or 201 status code but fails to send a body back in the response, their software will treat the request as a failure despite the fact that you returned a status code that indicates success.
The fix for this is to make sure that you always return something in the body of every POST request you serve.
One way to return something from a post is to return a string in the body of the response. Even this approach is slightly problematic, because JavaScript is actually very strict with its JSON format rules. One obscure rule in JSON is that all strings have to be quoted. This means that if your POST method does, say
return "OK";
this is not good enough. This will result in the body of the response looking like
OK
which is not valid JSON because it lacks quote marks. The correct thing to do is
return "\"OK\"";
which results in a body that looks like
"OK"
In the real world, most programmers follow a widely used convention with POST requests that makes it easy to get things right. The convention is to always echo back the object the client sent in the post in the body of the response.
Here is an example. I have modified the auction example to always take this approach to posts. One simple example is the controller method for posting a new auction:
@PostMapping public ResponseEntity<AuctionDTO> save(Authentication authentication,@RequestBody AuctionDTO auction) { AuctionUserDetails details = (AuctionUserDetails) authentication.getPrincipal(); auction.setSeller(details.getUsername()); String id; try { id = auctionService.save(auction); auction.setAuctionid(id); } catch(WrongUserException ex) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(auction); } return ResponseEntity.status(HttpStatus.CREATED).body(auction); }
This code receives an AuctionDTO from the client, stores the auction in the database, and then gets back a UUID for the auction as a side effect of saving. The code then puts the UUID back into the AuctionDTO and then sends that object back in the body of the response.
Another example has to do with posting users. My previous code for posting a user sent back a JWT in the body of the response. In the new version I started by adding a new member variable to the UserDTO class that I could use to store a token:
public class UserDTO { private String name; private String password; private String token; public UserDTO() { token = ""; } // Getters and Setters not shown }
With this change I could now modify the method for posting a new user to this form:
@PostMapping public ResponseEntity<UserDTO> save(@RequestBody UserDTO user) { if (user.getName().isBlank() || user.getPassword().isBlank()) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(user); } String key; try { key = us.save(user); } catch(DuplicateException ex){ return ResponseEntity.status(HttpStatus.CONFLICT).body(user); } String token = jwtService.makeJwt(key); user.setToken(token); return ResponseEntity.status(HttpStatus.CREATED).body(user); }
This code stores the user in the database. The service method that does this returns a UUID for the new user in its response. We then build a JWT based on this UUID, put the JWT back into the UserDTO object and then return that object in the body of the response.
The second technical suggestion I have for you is to prefer Spring Data and Hibernate over JDBC for new projects. When I first introduced Spring Data and Hibernate in class these topics looked very scary and technical. A big part of the complexity we saw in the Auction application came from the fact that I wanted to demonstrate what it looked like to build a Hibernate application on top of an existing database.
One advantage that you are going to have is that you won't have an existing database to work with. With no existing database you can use Hibernate in its simplest form. All you have to do is to set up your entity classes and then tell Hibernate to generate the database for you automatically.
To demonstrate how simple this is I have constructed yet another version of the auction application. You can access the full project for this example by clicking the button at the top of these notes. In this version of the Auction application I threw away the existing database and just wrote simple entity classes for everything. Since these entity classes do not have to conform to an existing database they can be pretty simple. For example, here is the new version of the Auction entity class:
@Entity public class Auction { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID auctionid; @ManyToOne private User seller; private String item; private String description; private String imageurl; private int reserve; private LocalDate opens; private LocalDate closes; private boolean completed; @OneToMany(mappedBy="auction") List<Tag> tags; @OneToMany(mappedBy="auction") List<Bid> bids; public Auction() {} // Getters and setters left out }
We still need to go through the exercise of setting up relationships between entity classes, but everything else here is pretty simple to set up.
If you want to operate Hibernate in the mode in which it generates the database tables for you automatically there is one small additional step you will need to take. In your application.properties file you need to add the line
spring.jpa.hibernate.ddl-auto = create
to explicitly tell Hibernate to make the database table for you automatically. Before you run your project you will need to use MySQL to create new empty schema for any databases you are working with. The first time you run your application or one of your unit tests Hibernate will now generate all of the tables in those databases for you.
One last small thing you may need to contend with has to do with storing UUIDs in a database. By default Hibernate chooses the most efficient datatype for storing UUIDs, the BINARY(16) MySQL datatype. This is a binary formal, which means that when you look at these fields in the database you will see a BLOB box which indicates that the data is binary and is not displyed in the workbench. If you need to convert those BLOBs to text for any purpose in the workbench you can run this query in the workbench:
SELECT BIN_TO_UUID(userid) FROM user;
The BIN_TO_UUID()
function can convert these UUIDs from binary to text form for you.