Spring Boot Rest with postgres and Docker Compose

josemmarneca

josemmarneca

Posted on August 10, 2023

Spring Boot Rest with postgres and Docker Compose

Docker Compose helps us setup the system more easily and efficiently than with only Docker, we can stand up multiples application. Lets see:

In this example we will create two docker instances, one will be our application and the other will be our database in postgres. The images will be in the same network so they can communicate internally in this network.

Image description

Pre-requirements:

  • Docker (version 20.10.16)
  • Docker-compose (version 1.29.2)
  • Java (version 17.0.2)
  • Maven (version 3.8.1)

Create our Application

First, let’s create our application in Spring-boot for this we will go to https://start.spring.io/ to download spring-boot and all the dependencies we need.
In this example, we will create a simple Rest application that returns and allows us to create employees.

Image description

https://start.spring.io/ download spring app
After we download, we will start to code.

First, we will create our entity with the many-to-many relationship for rules and we will specify our columns and table names.

@Entity
@Table(name="employees")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "email")
    private String email;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "users_roles",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();

}
Enter fullscreen mode Exit fullscreen mode

Next, we will create our service which will be our middleware between our REST endpoints and our database

@Service
public class EmployeeServiceImpl  implements EmployeeService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final EmployeeRepository employeeRepository;

    public EmployeeServiceImpl(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @Override
    public EmployeeDto create(EmployeeDto employeeDto) {
        if(!Objects.isNull(employeeDto.getId())){
            throw new ResourceInvalidException("Not accept Id in create");
        }

        Employee employee = EmployeeMapper.INSTANCE.convertDtoToEntity(employeeDto);
        employee = this.employeeRepository.save(employee);
        return EmployeeMapper.INSTANCE.convertEntityToDto(employee);

    }

    @Override
    public List<EmployeeDto> getAll() {
        List<Employee> employeeList = this.employeeRepository.findAll();
        return employeeList.stream().map(EmployeeMapper.INSTANCE::convertEntityToDto)
                .collect(Collectors.toList());

    }

    @Override
    public EmployeeDto getById(long id) {
        Employee employee = this.employeeRepository.findById(id).
                orElseThrow(() -> new ResourceNotFoundException("Employee", "id", id));
        return EmployeeMapper.INSTANCE.convertEntityToDto(employee);
    }

    @Override
    public void deleteById(long id) {
        logger.debug("Delete Employee by id" + id);
        this.employeeRepository.findById(id).
                orElseThrow(() -> new ResourceNotFoundException("Employee", "id", id));
        logger.debug("Employee exist delete them");
        employeeRepository.deleteById(id);
    }

    @Override
    public EmployeeDto update(EmployeeDto employeeDto) {
        logger.debug("Update Employee by id" + employeeDto.getId());
        Employee employee = this.employeeRepository.findById(employeeDto.getId()).
                orElseThrow(() -> new ResourceNotFoundException("Employee", "id", employeeDto.getId()));
        employee.setEmail(employeeDto.getEmail());
        employee.setName(employee.getName());
        logger.debug("Employee exist update them");
        employee = this.employeeRepository.save(employee);
        return EmployeeMapper.INSTANCE.convertEntityToDto(employee);
    }
}
Enter fullscreen mode Exit fullscreen mode

Then we create our REST controller which allows us to have our endpoint and create, get, update and delete employees

@RestController
@RequestMapping(value = "/api/employee")
public class EmployeesController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final EmployeeServiceImpl employeeService;

    public EmployeesController(EmployeeServiceImpl employeeService) {
        this.employeeService = employeeService;
    }

    @PostMapping(value = "/create")
    public ResponseEntity<HttpStatus> create(@RequestBody EmployeeDto employee) {
        logger.info("START Employee created ");

        EmployeeDto employeeDto = employeeService.create(employee);
        logger.info("Employee created id: " + employeeDto.getId());
        return ResponseEntity.ok(HttpStatus.CREATED);
    }

    @GetMapping(value ="/{id}")
    public ResponseEntity<EmployeeDto> getUserById(@PathVariable("id") Long employeeId){
        EmployeeDto employeeDto = employeeService.getById(employeeId);
        return new ResponseEntity<>(employeeDto, HttpStatus.OK);
    }

    @GetMapping(value ="/all")
    public ResponseEntity<List<EmployeeDto>> getAll(){
        logger.info("START Employee get All ");
        List<EmployeeDto> employeeDtoList = employeeService.getAll();
        return new ResponseEntity<>(employeeDtoList, HttpStatus.OK);
    }

    @PutMapping(value ="/{id}")
    public ResponseEntity<EmployeeDto> updateUser(@PathVariable("id") Long employeeId,
                                              @RequestBody @Valid EmployeeDto employeeDto){
        employeeDto.setId(employeeId);
        EmployeeDto updatedEmployee = employeeService.update(employeeDto);
        return new ResponseEntity<>(updatedEmployee, HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<String> deleteUser(@PathVariable("id") Long employeeId){
        employeeService.deleteById(employeeId);
        return new ResponseEntity<>("Employee successfully deleted!", HttpStatus.OK);
    }

}
Enter fullscreen mode Exit fullscreen mode

Once we have created our application, we need to create our docker image. This docker image will run our containerized application:

# Use an appropriate base image for your application
FROM eclipse-temurin:17

# Set the working directory inside the container
WORKDIR /app

# Copy the Spring Boot JAR file into the container
COPY target/app-0.0.1-SNAPSHOT.jar app.jar

# Expose the port that your Spring Boot application will run on
EXPOSE 8080

# Define the command to run your Spring Boot application
CMD ["java", "-jar", "app.jar"]
Enter fullscreen mode Exit fullscreen mode

We can check the version of java we need in Docker at the following link:

Docker java version

After creating our dockerized application we will create our database. For this we will use a postgres database dockerized (we can check the version of our postgres at https://hub.docker.com/_/postgres).

Create our Database
version: '3.8'
services:
  postgres:
    #image: postgres version we can use the latest "postgres:latest"
    image: postgres:${POSTGRES_SERVER_VERSION}
    #container_name: docker container name
    container_name: docker-compose-spring-boot-postgres
    #restart: is used to define the policy for restarting a service in case of failure or shutdown (no, always, on-failure, unless-stopped) 
    restart: unless-stopped
    #env_file: is used to load environment variables from an external file for a specific service  
    env_file: ./.env
    #environment: POSTGRES_DB - Our database name
    #environment: POSTGRES_USER - Our database user
    #environment: POSTGRES_PASSWORD - Our database password
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    #volumes: Allows to have data in the container on our local machine
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    #ports: Allows access between the host (machine where Docker is running) and service containers
    ports:
      - ${POSTGRES_EXPOSE_PORT}:${POSTGRES_SERVER_PORT}
    #networks: Define and configure custom networks
    networks:
      - app-network:
networks:
  app-network:
Enter fullscreen mode Exit fullscreen mode

Set up our environment (.env)

####################################################################################
# Postgres Server
####################################################################################
POSTGRES_SERVER_VERSION=15.1-alpine3.17
POSTGRES_HOSTNAME=db
POSTGRES_DB=appdb
POSTGRES_USER=appuser
POSTGRES_PASSWORD=apppass
POSTGRES_SERVER_PORT=5432
POSTGRES_EXPOSE_PORT=5432
####################################################################################
# Application Server properties
####################################################################################
APP_SERVER_PORT=8082
APP_EXPOSE_PORT=8082
# Inside docker postgresql://{SERVICE_NAME}:${POSTGRES_SERVER_PORT}  Outisde postgresql://{DOCKER_IP}:${POSTGRES_EXPOSE_PORT} 
APP_DATASOURCE_URL=jdbc:postgresql://postgres:5432/appdb
APP_DATASOURCE_USER=appuser
APP_DATASOURCE_PASS=apppass
You can see all the code in the github repository:
Enter fullscreen mode Exit fullscreen mode

You can see all the code Docker Compose with Postgres and Spring Boot with Rest, lombok, mapstruct, ExceptionHandler, mapper in the github repository: Code

💖 💪 🙅 🚩
josemmarneca
josemmarneca

Posted on August 10, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related