I had never touched an OR Mapper other than Doma, so I suddenly thought about it and touched it with half interest.
How to install in Spring Boot application-I would like to write about the comparison with Doma that I felt by creating a simple API.
IDE VSCode
Java 11.0.6
Spring Boot 2.3.1
PostgreSQL 11.6
I would like to create a simple API and touch it in various ways. The API to be created is as follows.
end point | Http Method | Overview | Remarks |
---|---|---|---|
/api/employee/{employeeId} |
GET | Get employee information that matches the employee ID. | |
/api/employee |
GET | Get employee information. | We will also narrow down by search conditions. |
/api/employee |
POST | Register employee information. | |
/api/employee/{employeeId} |
PUT | Update employee information. | |
/api/employee/{employeeId} |
DELETE | Delete employee information. |
Create a template for your application using the VSCode plugin called Spring Initializer Java Support.
This plugin itself is included in the Spring Boot Extension Pack, so the [Spring Boot Extension Pack]( It is enough to have https://marketplace.visualstudio.com/items?itemName=Pivotal.vscode-boot-dev-pack) installed.
Create a template interactively. From the command palette, select "Spring Initializr: Generate a Gradle Project".
Select Java.
Enter the package name. This time, leave it as com.example
by default.
Enter the project name. Please give us your favorite name. (I chose employee-api.)
Select the Spring Boot version. (Select 2.3.1)
Select a dependent library. I just wanted to create a simple API, so I chose the following library.
Spring Boot DevTools / Lombok / Spring Web / Spring Data JPA / PostgreSQL Driver
spring.jpa.database=postgresql
spring.datasource.platform=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/sample
spring.datasource.username=postgres
spring.datasource.password=postgres
ʻInsert_date and ʻupdate_date
are defined as common items in the table.
CommonEntity.java
/**
*This class defines common items in the table.</br>
*All Entity classes are created by inheriting this class.
*/
@MappedSuperclass
@Getter
@Setter
public class CommonEntity {
/**Data registration date and time*/
@Column(name = "insert_date")
@Temporal(TemporalType.DATE)
private Date insertdate;
/**Data update date and time*/
@Column(name = "update_date")
@Temporal(TemporalType.DATE)
private Date updateDate;
/**
*Methods commonly executed before data registration
*/
@PrePersist
public void preInsert() {
Date date = new Date();
setInsertdate(date);
setUpdateDate(date);
}
/**
*Commonly executed methods before updating data
*/
@PreUpdate
public void preUpdate() {
setUpdateDate(new Date());
}
}
Inherit CommonEntity
that defines table common items and create Entity class for business.
EmployeeEntity.java
@Entity
@Table(name = "employee")
@Getter
@Setter
public class EmployeeEntity extends CommonEntity {
/**Employee ID*/
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer employeeId;
/**Employee name*/
@Column(name = "name")
private String employeeName;
/**age*/
@Column(name = "age")
private Integer age;
/**Job title ID*/
@Column(name = "position_id")
private String positionId;
/**Department ID*/
@Column(name = "department_id")
private String departmentId;
}
ʻDefine an interface that inherits from org.springframework.data.jpa.repository.JpaRepository`.
EmployeeRepository.java
package com.example.employeeapi.employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EmployeeRepository extends JpaRepository<EmployeeEntity, Integer> {
}
JpaRepository
There are methods that can handle basic CRUD operations. In the interface that inherits this interface (in this example, ʻEmployeeRepository`), you can define your own method when the method prepared in advance in the business specifications etc. is not enough. (Join to get records, etc.)
JpaRepository.java
/*
* Copyright 2008-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository;
import java.util.List;
import javax.persistence.EntityManager;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;
/**
* JPA specific extension of {@link org.springframework.data.repository.Repository}.
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
@Override
List<T> findAll();
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
*/
@Override
List<T> findAll(Sort sort);
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
*/
@Override
List<T> findAllById(Iterable<ID> ids);
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
@Override
<S extends T> List<S> saveAll(Iterable<S> entities);
/**
* Flushes all pending changes to the database.
*/
void flush();
/**
* Saves an entity and flushes changes instantly.
*
* @param entity
* @return the saved entity
*/
<S extends T> S saveAndFlush(S entity);
/**
* Deletes the given entities in a batch which means it will create a single {@link Query}. Assume that we will clear
* the {@link javax.persistence.EntityManager} after the call.
*
* @param entities
*/
void deleteInBatch(Iterable<T> entities);
/**
* Deletes all entities in a batch call.
*/
void deleteAllInBatch();
/**
* Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is
* implemented this is very likely to always return an instance and throw an
* {@link javax.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers
* immediately.
*
* @param id must not be {@literal null}.
* @return a reference to the entity with the given identifier.
* @see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
*/
T getOne(ID id);
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
*/
@Override
<S extends T> List<S> findAll(Example<S> example);
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
*/
@Override
<S extends T> List<S> findAll(Example<S> example, Sort sort);
}
Define a Service class that uses the ʻEmployee Repository` defined earlier and a Controller class that calls it.
EmployeeService.java
package com.example.employeeapi.employee;
import java.util.ArrayList;
import java.util.List;
import javax.transaction.Transactional;
import com.example.employeeapi.employee.dto.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Transactional
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public Employee getEmployeeById(String employeeId) {
EmployeeEntity entity = employeeRepository.findById(Integer.parseInt(employeeId)).get();
Employee employee = new Employee();
copyEntityToBean(entity, employee);
return employee;
}
public List<Employee> getEmployeeList() {
List<Employee> employees = new ArrayList<>();
List<EmployeeEntity> employeeEntityList = employeeRepository.findAll();
employeeEntityList.forEach(entity -> {
Employee employee = new Employee();
copyEntityToBean(entity, employee);
employees.add(employee);
});
return employees;
}
public Employee createEmployee(Employee employee) {
EmployeeEntity entity = new EmployeeEntity();
copyBeanToEntityForInsert(employee, entity);
EmployeeEntity createdEntity = employeeRepository.save(entity);
Employee newEmployee = new Employee();
copyEntityToBean(createdEntity, newEmployee);
return newEmployee;
}
public Employee updateEmployee(Employee employee) {
EmployeeEntity entity = new EmployeeEntity();
copyBeanToEntityForUpdate(employee, entity);
EmployeeEntity updatedEntity = employeeRepository.save(entity);
Employee updatedEmployee = new Employee();
copyEntityToBean(updatedEntity, updatedEmployee);
return updatedEmployee;
}
public boolean deleteEmployeeById(String employeeId) {
employeeRepository.deleteById(Integer.parseInt(employeeId));
return true;
}
private void copyEntityToBean(EmployeeEntity entity, Employee employee) {
//For the sample, make a simple copy.
//If you want to do it cleanly, BeanUtils#Use copyProperties etc.
employee.setId(String.valueOf(entity.getEmployeeId()));
employee.setName(entity.getEmployeeName());
employee.setAge(String.valueOf(entity.getAge()));
employee.setPositionId(entity.getPositionId());
employee.setDepartmentId(entity.getDepartmentId());
employee.setInsertDate(String.valueOf(entity.getInsertdate()));
employee.setUpdateDate(String.valueOf(entity.getUpdateDate()));
}
private void copyBeanToEntityForInsert(Employee employee, EmployeeEntity entity) {
//For the sample, make a simple copy.
//If you want to do it cleanly, BeanUtils#Use copyProperties etc.
if (!"".equals(employee.getName())) {
entity.setEmployeeName(employee.getName());
}
if (!"".equals(employee.getAge())) {
entity.setAge(Integer.parseInt(employee.getAge()));
}
if (!"".equals(employee.getPositionId())) {
entity.setPositionId(employee.getPositionId());
}
if (!"".equals(employee.getDepartmentId())) {
entity.setDepartmentId(employee.getDepartmentId());
}
}
private void copyBeanToEntityForUpdate(Employee employee, EmployeeEntity entity) {
//For the sample, make a simple copy.
//If you want to do it cleanly, BeanUtils#Use copyProperties etc.
entity.setEmployeeId(Integer.parseInt(employee.getId()));
copyBeanToEntityForInsert(employee, entity);
}
}
EmployeeController.java
package com.example.employeeapi.employee;
import java.util.List;
import com.example.employeeapi.common.dto.HttpResponseDto;
import com.example.employeeapi.employee.dto.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping(path = "/api/employee/{employeeId}")
public HttpResponseDto getEmployeeById(@PathVariable("employeeId") String employeeId) {
HttpResponseDto httpResponseDto = new HttpResponseDto();
Employee employee = employeeService.getEmployeeById(employeeId);
httpResponseDto.setHttpStatus(HttpStatus.OK);
httpResponseDto.setResponseData(employee);
return httpResponseDto;
}
@GetMapping(path = "/api/employee")
public HttpResponseDto getEmployeeList() {
HttpResponseDto httpResponseDto = new HttpResponseDto();
List<Employee> employees = employeeService.getEmployeeList();
httpResponseDto.setHttpStatus(HttpStatus.OK);
httpResponseDto.setResponseData(employees);
return httpResponseDto;
}
@PostMapping(path = "/api/employee")
public HttpResponseDto createEmployee(@RequestBody Employee employee) {
HttpResponseDto httpResponseDto = new HttpResponseDto();
Employee newEmployee = employeeService.createEmployee(employee);
httpResponseDto.setHttpStatus(HttpStatus.CREATED);
httpResponseDto.setResponseData(newEmployee);
return httpResponseDto;
}
@PutMapping(path = "/api/employee/{employeeId}")
public HttpResponseDto updateEmployee(@PathVariable("employeeId") String emplyeeId, @RequestBody Employee employee) {
HttpResponseDto httpResponseDto = new HttpResponseDto();
employee.setId(emplyeeId);
Employee updatedEmployee = employeeService.updateEmployee(employee);
httpResponseDto.setHttpStatus(HttpStatus.CREATED);
httpResponseDto.setResponseData(updatedEmployee);
return httpResponseDto;
}
@DeleteMapping(path = "/api/employee/{employeeId}")
public HttpResponseDto deleteEmployee(@PathVariable("employeeId") String employeeId) {
HttpResponseDto httpResponseDto = new HttpResponseDto();
if (employeeService.deleteEmployeeById(employeeId)) {
httpResponseDto.setHttpStatus(HttpStatus.OK);
httpResponseDto.setMessage("delete success.");
} else {
// do something
}
return httpResponseDto;
}
}
I wrote it as a comparison with other OR Mappers, but it is a comparison with Doma.
――What I felt was good
--Simple CRUD operations can be achieved simply by using the provided API.
--Doma also provides APIs other than search (SELECT), but in the case of search processing, it is necessary to create an SQL file even for a simple query.
--If it's about the API provided by JpaRepository
, Doma Gen seems to be quite so, but ,
--The feeling of use is similar to the OR Mapper for TypeScript called TypeORM that I often use. (I wonder if TypeORM was made with JPA in mind, please let me know if you are familiar with it.)
――What I felt was not good
――Since I made only simple CRUD operations, I have no particular complaints at the moment.
――However, since it was originally JPA, I have a feeling that it will hurt if I do not adopt it after studying well.
The DB for verification is built based on Docker. It's a pain to make a DB because it works with copy and paste! Please use it.
$ tree
.
├── docker-compose.yml
└── init-script
├── 01_create_table.sql
└── 02_insert_data.sql
docker-compose.yml
version: '3'
volumes:
db_data:
services:
database:
image: postgres:11.6
container_name: postgres
ports:
- 5432:5432
volumes:
- db_data:/var/lib/postgresql/data
- ./init-script:/docker-entrypoint-initdb.d
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: sample
01_create_table.sql
create table department (
--Department code
id varchar(3) primary key,
--Department name
name varchar(50),
--Data input date
insert_date date,
--Data update date
update_date date
);
create table "position" (
--Job title ID
id varchar(2) primary key,
--Job title
name varchar(20),
--Data input date
insert_date date,
--Data update date
update_date date
);
--table generation
create table "employee" (
--employee number
id serial primary key,
--Employee name
name varchar(50),
--age
age integer,
--Position
position_id varchar(2) references position(id),
--Affiliation department id
department_id varchar(3) references department(id),
--Data input date
insert_date date,
--Data update date
update_date date
);
02_insert_data.sql
insert into department (id, name, insert_date, update_date)
values ('001', 'Human Resources Department', '2020-06-17', '2020-06-17');
insert into department (id, name, insert_date, update_date)
values ('002', 'General Affairs Department', '2020-06-17', '2020-06-17');
insert into department (id, name, insert_date, update_date)
values ('003', 'Development department', '2020-06-17', '2020-06-17');
insert into department (id, name, insert_date, update_date)
values ('004', 'Public relations department', '2020-06-17', '2020-06-17');
insert into position (id, name, insert_date, update_date)
values ('01', 'Director', '2020-06-17', '2020-06-17');
insert into position (id, name, insert_date, update_date)
values ('02', 'Manager', '2020-06-17', '2020-06-17');
insert into position (id, name, insert_date, update_date)
values ('03', 'General', '2020-06-17', '2020-06-17');
insert into employee (
name,
age,
position_id,
department_id,
insert_date,
update_date
)
values (
'Shacho-san',
50,
'01',
'001',
'2020-06-17',
'2020-06-17'
);
insert into employee (
name,
age,
position_id,
department_id,
insert_date,
update_date
)
values (
'Butcho-san',
46,
'02',
'001',
'2020-06-17',
'2020-06-17'
);
insert into employee (
name,
age,
position_id,
department_id,
insert_date,
update_date
)
values (
'Kaccho',
30,
'03',
'001',
'2020-06-17',
'2020-06-17'
);
insert into employee (
name,
age,
position_id,
department_id,
insert_date,
update_date
)
values (
'Mr. Pampee',
30,
'03',
'002',
'2020-06-17',
'2020-06-17'
);
I would like to imagine an actual use case and create a more practical API.
-Connect to database with SpringBoot + Spring JPA
-Connect to database with spring boot + spring jpa and CRUD operation
-Points for selecting Java OR mapper
Recommended Posts