이전글 - [Spring Boot] - [Spring Boot] REST API 제작기 - 1.프로젝트 생성
#1. 데이터 소스 연동(Oracle, MS-SQL, H2)
멀티데이터소스 연동을 위해 application.properties 에 아래와 같이 각 db별로 접속정보를 작성한다.
# H2 DB 접속정보
spring.datasource.hikari.jdbc-url=jdbc:h2:~/test
spring.datasource.hikari.driver-class-name=org.h2.Driver
spring.datasource.username=sa
# Oracle DB 접속정보(itsvc)
spring.itsvc.datasource.hikari.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.itsvc.datasource.hikari.jdbc-url=jdbc:log4jdbc:oracle:thin:@xx.xx.xx.xx:xxxx:BMSD
spring.itsvc.datasource.hikari.pool-name=bmsd-cp
spring.itsvc.datasource.hikari.username=itsvcuser
spring.itsvc.datasource.hikari.password=pw
# Oracle DB 접속정보(core)
spring.core.datasource.hikari.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.core.datasource.hikari.jdbc-url=jdbc:log4jdbc:oracle:thin:@xx.xx.xx.xx:xxxx:CMMD
spring.core.datasource.hikari.pool-name=cmmd-cp
spring.core.datasource.hikari.username=cmmduser
spring.core.datasource.hikari.password=pw
# SqlServer DB 접속정보(asset)
spring.asset.datasource.hikari.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.asset.datasource.hikari.jdbc-url=jdbc:log4jdbc:sqlserver://xx.xx.xx.xx:xxxx;encrypt=true;trustServerCertificate=true
spring.asset.datasource.hikari.pool-name=asset-cp
spring.asset.datasource.hikari.username=assetuser
spring.asset.datasource.hikari.password=pw
다음은 데이터 소스 설정 파일(xxxDataSourceConfig.java) 을 생성한 후 아래와 같이 작성한다.
hikaricp 설정(connectionTimeout, poolSize 등등) 은 변경할 수도 있지만, 지금은 일단 기본값으로 설정 한다.
package com.mbti.intj.infra.datasource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@MapperScan(value = "com.mbti.intj.sample.ep", annotationClass = Mapper.class, sqlSessionFactoryRef = "coreSf")
@EnableTransactionManagement
public class CoreDataSourceConfig {
@Primary
@Bean
@ConfigurationProperties(prefix="spring.core.datasource.hikari")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
/**
* JDBC를 사용한 DataSource
* @return
*/
@Primary
@Bean(name="coreDs")
public DataSource epDataSource() {
return new HikariDataSource(hikariConfig());
}
/**
* SqlFactoryBean에 dataSource와 mapper 경로 등록
* @param dataSource
* @return
* @throws Exception
*/
@Primary
@Bean(name="coreSf")
public SqlSessionFactory sqlSessionFactory(@Autowired @Qualifier("coreDs") DataSource dataSource) throws Exception{
//RefreshableSqlSessionFactoryBean sqlFactoryBean = new RefreshableSqlSessionFactoryBean();
SqlSessionFactoryBean sqlFactoryBean = new SqlSessionFactoryBean();
sqlFactoryBean.setDataSource(dataSource);
// Mapper file location
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlFactoryBean.setMapperLocations(resolver.getResources("/mapper/ep/**/**.xml"));
// ResultType vo package location
sqlFactoryBean.setTypeAliasesPackage("com.mbti.intj.**.vo");
SqlSessionFactory sqlSessionFactory = sqlFactoryBean.getObject();
sqlSessionFactory.getConfiguration().setMapUnderscoreToCamelCase(true);
sqlSessionFactory.getConfiguration().setLazyLoadingEnabled(true);
sqlSessionFactory.getConfiguration().setJdbcTypeForNull(JdbcType.NULL);
return sqlFactoryBean.getObject();
}
@Primary
@Bean(name="coreSt")
public SqlSessionTemplate sqlSessionTemplate(@Autowired @Qualifier("coreSf") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package com.mbti.intj.infra.datasource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
/**
* packageName : com.mbti.intj.infra.datasource
* className : AssetDataSourceConfig
* author : garve32
* date : 2022-03-25
* description : ASSET DB(SQL SERVER) 에 대한 datasouece 설정
* MapperScan value(interface)와 Mapper Location(xml) 설정에 따른다
* ========================================================================
* DATE AUTHOR NOTE
* ------------------------------------------------------------------------
* 2022-03-25 garve32 최초 생성
**/
@Configuration
@MapperScan(value = "com.mbti.intj.sample.asset", annotationClass = Mapper.class, sqlSessionFactoryRef = "assetSf")
@EnableTransactionManagement
public class AssetDataSourceConfig {
@Autowired
private ApplicationContext ctx;
@Bean
@ConfigurationProperties(prefix="spring.asset.datasource.hikari")
public HikariConfig assetHikariConfig() {
return new HikariConfig();
}
/**
* JDBC를 사용한 DataSource
* @return
*/
@Bean(name="assetDs")
public DataSource assetDataSource() {
return new HikariDataSource(assetHikariConfig());
}
/**
* SqlFactoryBean에 dataSource와 mapper 경로 등록
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name="assetSf")
public SqlSessionFactory sqlSessionFactory(@Autowired @Qualifier("assetDs") DataSource dataSource) throws Exception{
SqlSessionFactoryBean sqlFactoryBean = new SqlSessionFactoryBean();
sqlFactoryBean.setDataSource(dataSource);
// Mapper file location
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlFactoryBean.setMapperLocations(resolver.getResources("/mapper/asset/**/**.xml"));
// ResultType vo package location
sqlFactoryBean.setTypeAliasesPackage("com.mbti.intj.**.vo");
SqlSessionFactory sqlSessionFactory = sqlFactoryBean.getObject();
sqlSessionFactory.getConfiguration().setMapUnderscoreToCamelCase(true);
sqlSessionFactory.getConfiguration().setLazyLoadingEnabled(true);
sqlSessionFactory.getConfiguration().setJdbcTypeForNull(JdbcType.NULL);
return sqlFactoryBean.getObject();
}
@Bean(name="assetSt")
public SqlSessionTemplate sqlSessionTemplate(@Autowired @Qualifier("assetSf") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
여기서 눈여겨 보아야 할 구문은 다음과 같다.
@MapperScan의 value값은 Mybatis Mapper(Interface) 파일을 찾을 수 있는 최상단 경로로 지정한다.
@ConfigurationProperties 의 prefix는 application.properties에 작성했던 db property 정보의 prefix값이다.
sqlFactoryBean.setMapperLocations에는 Mybatis 쿼리파일이 담긴 xml 경로를 지정해준다(/resources 하위)
sqlFactofyBean.setTypeAliasesPackage에는 Mybatis 리턴데이터가 담길 dto혹은 vo파일들의 package경로를 설정해 주어서 추후 xml 파일 작성시 일일히 전체패키지 경로를 작성하는 수고를 줄여준다.
또한, 반드시 메인으로 사용할 데이터소스 작성시 @Bean에 @Primary 어노테이션을 걸어준다.
datasourceconfig 파일들과 mapper 파일의 경로는 아래와 같다.(MapperScan 설정값 참고)
#2. Mapper 인터페이스와 mybatis xml 파일 생성
이제 Mapper 인터페이스와 쿼리구문이 담길 xml 파일을 작성해보자.
Mapper 인터페이스의 메소드 명이 반드시 쿼리파일의 id와 동일해야 정상적으로 호출할 수 있다.
단건 조회를 위한 메소드, 페이징 처리를 위한 카운트 쿼리와 리스트쿼리를 작성해주자.
Mapper 인터페이스에는 @Mapper 어노테이션을 붙이는 것을 잊지 말자.
package com.mbti.intj.sample.asset.mapper;
import com.mbti.intj.sample.asset.vo.AssetUserVo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface AssetSampleMapper {
AssetUserVo getUser(Object req) throws Exception;
List<AssetUserVo> getUserList(Object req) throws Exception;
int getUserListCnt(Object req) throws Exception;
}
Mybatis 문법의 쿼리는 아래와 같이 작성한다.
Mapper Interface 파일의 풀 경로를 namespace에 작성하고, id값을 Mapper 인터페이스의 메소드 명과 일치시킨다.
resultType에는 원래 com.mbti.intj.sample.asset.vo.AssetUserVo 로 작성해야 하지만
데이터소스 설정파일에서 sqlFactofyBean.setTypeAliasesPackage 에 **.vo 까지 미리 alias를 설정해주었기 때문에 AssetUserVo 처럼 줄여서 사용할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- Mapper interface의 패키지 풀네임 -->
<mapper namespace="com.mbti.intj.sample.asset.mapper.AssetSampleMapper">
<select id="getUser" resultType="AssetUserVo">
/* AssetSampleMapper.getUser */
SELECT EMPNO
, NAME
, ENAME1
, EPID
FROM EMPMAST
WHERE EPID = #{epid}
</select>
<select id="getUserListCnt" resultType="int">
/* AssetSampleMapper.getUserListCnt */
SELECT COUNT(*)
FROM EMPMAST
</select>
<select id="getUserList" resultType="AssetUserVo">
/* AssetSampleMapper.getUserList */
SELECT EMPNO
, NAME
, ENAME1
, EPID
FROM EMPMAST
ORDER BY EMPNO
OFFSET #{offset} ROWS
FETCH NEXT #{pageSize} ROWS ONLY
</select>
</mapper>
#3. 컨트롤러에서 호출테스트(API 호출)
해당 데이터소스를 사용한 쿼리를 호출하는 API를 작성해보자.
AssetUserDto - Api 입력으로 받을 dto(frontend에서 넘어오는 데이터)
package com.mbti.intj.sample.asset.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Getter
@Setter
@ToString
@NoArgsConstructor
public class AssetUserDto {
@NotNull
private String epid;
}
AssetUserVo - Service 레이어에서 리턴할 vo(frontend로 바로 넘겨줄 예정)
package com.mbti.intj.sample.asset.vo;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class AssetUserVo {
private final String empno;
private final String name;
private final String ename1;
private final String epid;
}
AssetSampleController - RestController로 작성하고 GetMapping으로 api url을 매핑한다.
Paging 부분은 나중에 다루도록 하겠다.
package com.mbti.intj.sample.asset.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mbti.intj.sample.asset.dto.AssetUserDto;
import com.mbti.intj.sample.asset.dto.AssetUserListReqDto;
import com.mbti.intj.sample.asset.service.AssetSampleService;
import com.mbti.intj.sample.asset.vo.AssetUserVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* packageName : com.mbti.intj.sample.asset.controller
* className : AssetSampleController
* author : garve32
* date : 2022-03-25
* description : 자산 정보 가져오는 컨트롤러
* ========================================================================
* DATE AUTHOR NOTE
* ------------------------------------------------------------------------
* 2022-03-25 garve32 최초 생성
**/
@Slf4j
@RestController
@RequiredArgsConstructor
@Transactional
@RequestMapping("/asset")
public class AssetSampleController {
private final AssetSampleService assetSampleService;
@GetMapping("/user")
public AssetUserVo getAssetUser(@Validated AssetUserDto dto) throws Exception {
return assetSampleService.getUser(dto);
}
@GetMapping("/users")
public Page<AssetUserVo> getUsers(AssetUserListReqDto dto, @PageableDefault(value = 20) Pageable page) throws Exception {
// Input DTO의 가공이 필요한 경우 신규 Object를 만들고 가공한 후 Mapper Interface의 argument로 적용
ObjectMapper om = new ObjectMapper();
Map<String, Object> map = om.convertValue(dto, Map.class);
Page<AssetUserVo> result = assetSampleService.getUserList(map, page);
return result;
}
}
AssetSampleService - AssetSampleMapper 를 생성자 주입받고 쿼리를 호출한다.
마찬가지로 페이징 부분은 나중에 자세히 다루도록 하겠다.
package com.mbti.intj.sample.asset.service;
import com.mbti.intj.sample.asset.mapper.AssetSampleMapper;
import com.mbti.intj.sample.asset.vo.AssetUserVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class AssetSampleService {
private final AssetSampleMapper assetSampleMapper;
public Page<AssetUserVo> getUserList(Map<String, Object> req, Pageable page) throws Exception {
req.put("offset", page.getOffset());
req.put("pageSize", page.getPageSize());
int cnt = assetSampleMapper.getUserListCnt(req);
List<AssetUserVo> result = assetSampleMapper.getUserList(req);
return new PageImpl<>(result, page, cnt);
//return assetSampleMapper.getUserList(req);
}
public AssetUserVo getUser(Object req) throws Exception {
return assetSampleMapper.getUser(req);
}
}
이제 내장 Tomcat을 기동시키고 /asset/user api를 호출해보면 원하는 데이터 소스의 쿼리가 잘 실행됐음을 확인할 수 있다.
Spring boot - Mybatis - Database 설정은 이것으로 모두 완료됐다.
'Spring Boot' 카테고리의 다른 글
javax.inject.Provider 를 사용할 때 UnsatisfiedDependencyException 발생 (0) | 2023.03.12 |
---|---|
[Spring Boot] REST API 제작기 - 1.프로젝트 생성 (0) | 2022.02.17 |
[Spring Boot] Spring Rest Doc 설정(gradle) (0) | 2021.06.03 |
댓글