In Part 1, we used Spring Initializr to create a skeleton Spring Boot project and added a few details to make it work with MySQL and Flyway.
Next, we are going to start a simple API with functionality to POST a new User.
Audience
Anyone who wants to learn how to create and run an API using Spring Boot with MySQL and Flyway.
Experience with Java programming and relational databases is helpful but not required.
Requirements
- Code from Part 1
- Java IDE (e.g. IntelliJ)
- Maven
- MySQL Community Server
- REST client (e.g. Postman)
Create the database model
This demo uses Hibernate as an ORM (Object Relational Mapper) tool to convert database rows in the USER
table to Java UserModel
objects. Hibernate is a popular implementation of JPA (Java Persistence API).
The first thing to do is create the UserModel
class to mirror the table structure:
@Entity
@Table(name="USER")
public class UserModel {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column
private Long id;
@Column
private String firstName;
@Column
private String lastName;
@Column
private Date addTs;
public UserModel() {
}...
}
- The
@
signs are annotations that tell the Spring Boot framework how to interpret the model @Entity
and@Table
indicate this class represents a database table- All JPA model classes have an
@Id
to be able to refer to a specific row in the table - Each field in the table needs a corresponding variable with the
@Column
annotation - Since the
USER
table hasAUTO_INCREMENT
property on theID
column, JPA matches it withGenerationType.IDENTITY
strategy to let MySQL keep creating the IDs ...
stands for the standard getters, setters,equals()
, andhashcode()
methods common in POJO (Plain Old Java Object) classes. With IntelliJ (and most other IDEs), you can generate these methods by right clicking and selecting “Generate…”
Create the request object
To add a new User, the REST client will need to send details about the User to our resource. However, there are often cases where the details sent by the client are different than what is stored in the database. For example, UserModel
has a timestamp that is generated by the service. Also, the ID is generated by the database. Therefore, the only two values that are needed for a request are First Name and Last Name.
public class UserRequest { private String firstName;
private String lastName; public UserRequest() {
} ...
}
UserRequest
does NOT have any JPA annotations. Since it is only a partial representation of the table, we need to write some code later to convertbetween the database model and request object.
Create the response object
After the User is saved, the details are typically returned to the client as confirmation. It is advantageous to have a third POJO with formatted values for the client to use. For example, instead of making the client convert the Add timestamp, the service can do it and pass along the formatted date string to the client.
public class User { private Long id;
private String firstName;
private String lastName;
private String addDate; public User() {
}
...
}
Create the converter
In order to translate between the client and database representations of a User, we can set up an interface with generic method signatures, and then implement the interface specifically for User.
public interface ModelConverter<Q, M, S> { M requestToModel(Q request); S modelToResponse (M model);}
The interface declares two methods:
requestToModel
to take the request from the client and translate into the database model (in preparation for saving to the database)modelToResponse
to take an object from the database and translate into the client format (in preparation for returning confirmation to the client)
Next, implement ModelConverter
for Users:
@Component
public class UserConverter implements ModelConverter<UserRequest, UserModel, User> { @Override
public UserModel requestToModel(UserRequest request) {
UserModel model = new UserModel();
model.setFirstName(request.getFirstName());
model.setLastName(request.getLastName());
return model;
} @Override
public User modelToResponse(UserModel model) {
User response = new User();
response.setId(model.getId());
response.setFirstName(model.getFirstName());
response.setLastName(model.getLastName());
response.setAddDate(new SimpleDateFormat("MMM d, yyyy").format(model.getAddTs()));
return response;
}}
- A new JPA annotation,
@Component
, indicates Spring Boot should manage this class and make it available to other classes - The interface parameters
<Q, M, S>
are replaced with User-specific objects<UserRequest, UserModel, User>
requestToModel
fills in the details it knows forUserModel
and leaves the rest nullmodelToResponse
fills in the required details forUser
. It also formats the timestamp, showing how the converter can have some of its own logic instead of always passing the exact database values to the client
How do I save a User?
We have set up the dependent classes for our API, and now we can implement the actual repository and service methods.
First, start with UserRepository
:
@Repository
public interface UserRepository extends JpaRepository<UserModel, Long> {}
- All of the standard repository methods our API needs are inherited from
JpaRepository
@Repository
indicates Spring Boot should also manage this class
What calls the repository method?
For the client to access the repository method, we need to create the UserService
class:
@Service
@Transactional
public class UserService { @Autowired
UserConverter userConverter; @Autowired
UserRepository userRepository; public User save(final UserRequest userRequest) {
UserModel userModel = userConverter.requestToModel(userRequest); userModel.setAddTs(new Date()); return userConverter.modelToResponse(userRepository.save(userModel));
}}
@Service
indicates Spring Boot should manage this service layer component.@Service
is functionally similar to@Component
, but convention encourages using the most specific annotation for code readability and compatibility with future releases (see Spring documentation for a more complete explanation)@Transactional
directs Spring Boot to use a single session to interact with the database. Transactions are commonly started at the service layer and then used by the repository methods@Autowired
is a special annotation that allows the current Spring Boot managed class to use an instance of other managed classes. For example, sinceUserRepository
was annotated with@Repository
, Spring Boot can find it and there is no need to explicitly declare an instance ofUserRepository
- The
save
method takes aUserRequest
from the client and returns aUser
response to the client
How does the client call the API?
The client needs to know several things in order to call the API:
- URL
- HTTP action verb
- Request data
All of these can be described by a Jersey resource class. Let’s create one called UserResource
:
@Path("/users")
@Component
public class UserResource { @Autowired
UserService userService; @POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public User save(final UserRequest userRequest) {
return userService.save(userRequest);
}}
- The
@Path
annotation specifies the relative root of the the URL UserService
is autowired so service methods can be mapped to the URLs@POST
indicates thesave
method requires data from the client to create the new User@Consumes
and@Produces
specify the type of the incoming data from the client and outgoing data to the client, respectively. Many APIs, including this one, send data in JSON (JavaScript Object Notation) format
A specific API path is often called an endpoint. Also, many REST APIs have a common path prefix of /api
for all endpoints. One way to use a path prefix is by adding this line to application.properties
:
# Path properties
server.servlet.contextPath=/api
Including the prefix, the endpoint for a new User becomes /api/users
However, if you try to use the endpoint now, it will give a 404 Not Found error. For Spring Boot and Jersey to work properly, one more step is required. You must register the resource class in a new class that extends ResourceConfig
. We can call it JerseyConfiguration
:
@Component
public class JerseyConfiguration extends ResourceConfig { public JerseyConfiguration() {
register(UserResource.class);
}}
POST a User
The API is ready to add the first User!
With a REST client like Postman, choose POST and enter URL as http://localhost:8080/api/users
The request body is required in the following format:
{
"firstName": "Adam",
"lastName": "Zink"
}
Then, click Send to submit the request:
The User object is returned as confirmation. It includes both the ID generated by the database and the date formatted by the service.
Congratulations! Your Spring Boot REST endpoint is now operational.
The Code
https://github.com/AdamZink/spring-boot-mysql-demo/tree/part-2-r1
Next
Part 3 completes the User services by creating more endpoints to get, update, and delete Users.