MyBatis와 Spring Boot (Annotation)

🗓️

MariaDB Docker 설치

docker pull mariadb
docker run --name mariadb-1 -e MYSQL_ROOT_PASSWORD=PASSWORD -d -p 3306:3306 mariadb
docker stop mariadb
docker start mariadb

DBeaver 설치

DB 생성

create database mybatis_db default character set utf8mb4 default collate utf8mb4_general_ci;
create user 'mybatis'@'%' identified by 'thatkxkd123';
grant all on mybatis_db.* to 'mybatis'@'%';
use mybatis_db;
create table company (
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    company_name VARCHAR(128),
    company_address VARCHAR(128),
    INDEX (company_name)
)

boot 프로젝트 생성

mariadb driver setup

  • application.properties
spring.datasource.url=jdbc:mariadb://localhost:3306/mybatis_db
spring.datasource.username=mybatis
spring.datasource.password=thatkxkd123

insert하는 컨트롤러 작성

@Data
public class Company {
    private int id;
    private String name;
    private String address;

}

@Mapper
public interface CompanyMapper {

    @Insert("INSERT INTO company(company_name, company_address) VALUES(#{company.name}, #{company.address})")
    int insert(@Param("company") Company company);
    // 반환되는 결과는 입력된 갯수가 반환된다. 실패할 경우 0 반환.
}


@RestController
@RequestMapping("/company")
public class CompanyController {

    @Autowired
    private CompanyMapper companyMapper;

    @PostMapping("")
    public int post(@RequestBody Company company){
        return companyMapper.insert(company);
    }
}

POST 테스트

Select기능 만들기

Mapper

@Select("SELECT * FROM company")
@Results({
    @Result(property="name", column="company_name"),
    @Result(property="address", column="company_address")
}) // id의 경우는 자동으로 매핑이 된다.
List<Company> getAll();

Controller

@GetMapping("")
public List<Company> getAll(){
    return companyMapper.getAll();
}

GET 테스트

Insert 개선

  • 보통 insert작업을 하면 0이나 1로 리턴값을 받기 보다는 전체의 값을 반환 받는다.
  • 그러나 id의 경우 DB에서 auto increment기 때문에 별도의 작업이 필요하다.
//Controller
@PostMapping("")
public Company post(@RequestBody Company company){
    companyMapper.insert(company);
    return company;
}
  • 객체를 그대로 반환하면 입력값 그대로 반환되지만 id는 값이 설정되지 않는다.
  • property를 설정하기 위해 mapper에서 옵션을 지정해줘야한다
@Insert("INSERT INTO company(company_name, company_address) VALUES(#{company.name}, #{company.address})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(@Param("company") Company company);
  • @Options를 통해 생성된 키를 가지고 keyProperty를 설정하면, 자동으로 생성된 id값이 객체의 id변수에 설정된 상태로 반환된다.

id값을 가지고 Select 해보기

    동일한 매핑 관계 @Results를 복사 할 수도 있지만 id값을 부여해 재사용 할 수 있다.
    @Results(id="CompanyMap", value={
    @Result(property = "name", column = "company_name"),
    @Result(property = "address", column = "company_address")
})
  • id를 가지고 select하는 mapper
@Select("SELECT * FROM company WHERE id=#{id}")
@ResultMap("CompanyMap")
Company getById(@Param("id") int id);
  • REST controller
@GetMapping("/{id}")
public Company getById(@PathVariable("id") int id){
    return companyMapper.getById(id);
}
  • @PathVariable : GetMapping의 변수와 매칭된다.

직원 테이블 만들기

create table employee (
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    company_id INTEGER,
    employee_name VARCHAR(128),
    employee_address VARCHAR(128),
    INDEX (employee_name),
    FOREIGN KEY (company_id) REFERENCES company(id)
)

DTO

@Data
public class Employee {

    private int id;
    private int companyId; //<<<< 추가됨
    private String name;
    private String address;

}

Mapper

@Mapper
public interface EmployeeMapper {
    @Insert("INSERT INTO employee(company_id, employee_name, employee_address) VALUES(#{employee.companyId}, #{employee.name}, #{employee.address})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(@Param("employee") Employee employee);
    // 반환되는 결과는 입력된 갯수가 반환된다. 실패할 경우 0 반환.

    @Select("SELECT * FROM employee")
    @Results(id="EmployeeMap", value={
        @Result(property = "name", column = "employee_name"),
        @Result(property = "address", column = "employee_address")
    })
        // id의 경우는 자동으로 매핑이 된다.
    List<Employee> getAll();

    @Select("SELECT * FROM employee WHERE id=#{id}")
    @ResultMap("EmployeeMap")
    Company getById(@Param("id") int id);
}

Controller

@RestController
@RequestMapping("/employee")
public class EmployeeController {
    @Autowired
    private EmployeeMapper employeeMapper;

    @PostMapping("")
    public Employee post(@RequestBody Employee employee){
        employeeMapper.insert(employee);
        return employee;
    }

    @GetMapping("")
    public List<Employee> getAll(){
        return employeeMapper.getAll();
    }

    @GetMapping("/{id}")
    public Company getById(@PathVariable("id") int id){
        return employeeMapper.getById(id);
    }
}

회사의 직원들의 목록도 조회를 하고싶다

Company DTO

@Data
public class Company {
    private int id;
    private String name;
    private String address;
    private List<Employee> employeeList;
}

Service layer

@Service
public class CompanyService {

    @Autowired
    private CompanyMapper companyMapper;

    @Autowired
    private EmployeeMapper employeeMapper;

    public List<Company> getAll() {
        List<Company> companyList = companyMapper.getAll();
        if (companyList != null && companyList.size() > 0) {
            for (Company company : companyList) {
                company.setEmployeeList(employeeMapper.getByCompanyId(company.getId()));
            }
        }
        return companyList;
    }
}
  • company목록이 1개 이상 조회된 경우에 employee목록을 set할것 – getByCompanyId라는 메서드에 companyId로 조회함으로써 모든 직원의 목록을 얻어오도록 할것이다.

Employee mapper

@Select("SELECT * FROM employee WHERE company_id=#{companyId}")
@ResultMap("EmployeeMap")
List<Employee> getByCompanyId(@Param("companyId") int companyId);

Company controller

@Autowired
private CompanyService companyService;

@GetMapping("/allemployees")
public List<Company> getAllEmployees() {
    return companyService.getAll();
}

Sub-query

  • 서브쿼리를 통해 원하는 mapper기능을 쿼리 안으로 넣을 수 있다.
// CompanyMapper
@Select("SELECT * FROM company")
@Results(id = "CompanyMap", value = {
    @Result(property = "name", column = "company_name"),
    @Result(property = "address", column = "company_address"),
    @Result(property = "employeeList", column = "id", many = @Many(select = "com.example.mybatis_01.EmployeeMapper.getByCompanyId"))
})
List<Company> getAll();

로깅

  • 기대값과 달리 제대로 실행되지 않을때는 로깅을 해봐야한다.
  • application.properties에 다음을 추가한다.
logging.level.com.example.mybatis_01.CompanyMapper=DEBUG
logging.level.com.example.mybatis_01.EmployeeMapper=DEBUG
logging.level.com.example.mybatis_01.CompanyMapper=TRACE
logging.level.com.example.mybatis_01.EmployeeMapper=TRACE

트랜잭션

@Transactional
public Company add(Company company) {
    companyMapper.insert(company);
    // add company into legacy system
    if (true) {
        throw new RuntimeException("LEGACY EXCEPTION");
    }
    return company;
}
  • Transactional 어노테이션으로 롤백을 지원한다.
  • 특정 예외에서 발생시키고 싶다면 rolebackFor라는 옵션으로 예외 선택이 가능하다.