-
mybatis로 jpa 흉내내기개발/java 2024. 9. 3. 13:18
mybatis의 경우 jdbc를 통해 코드와 db를 혼합하여 작성하는 방식에서 둘을 분리하게 구현함으로써 유지 보수 및 개발을 함에 있어 간편하고 쉽게 만들었다
그 이후 JPA가 도입됨에 따라 DB table에 따라 구현하는 방식이 서비스를 구현하는 것이 아닌 실 서비스를 세분화하여 ORM에 좀 더 다가갈 수 있게 되었다
그러나 특정 이유로 JPA를 사용하지 못하는 경우 mybatis에서 제공하는 new SQL() 과 ProviderMethodResolver를 통해서 별도 쿼리 작성 없이 동적으로 만들 수 있는 방법이 있다.
import org.apache.ibatis.builder.annotation.ProviderMethodResolver; //mybatis 인터페이스 ProviderMethodResolver를 implements할 경우 new SQL을 통해 동적으로 쿼리를 만들 수 있다. //아래를 풀이하면 select * from table id ='id' order by id;가 될 것이다 class MyBatisProvider implements ProviderMethodResolver { public static String findById(String id) { return new SQL(){{ SELECT("*"); FROM("table"); WHERE("id = #{id}"); ORDER_BY("id"); }}.toString(); } }
쿼리는 항상 앨리어스를 통해 전체를 출력하는 것도 아니고 조건도 id가 아닌 주문번호 , 회원번호 , 주문접수일 등으로 조회를 하는게 일반적일 것이다.
mybatis에서 제공하는 ProviderContext의 경우 조회를 하는 대상 클래스의 정보를 추론 할 수 있다
아래와 같이 orderInfoRdbDao를 Mybatis를 통해 조회하게 되면 ProviderContext를 통해 OrderInfo class의 정보가 무엇인지 알 수 있다. 예를 들어 OrderInfo에는 3개의 컬럼이 있고 그 컬럼에는 orderNumber , orderSequence , orderName이 있다와 같은 정보를 알 수 있다.
OrderInfo orderInfo = orderInfoRdbDao.getOrderInfoInfo(orderNumber); @RdbTable("orderDetail") public class OrderInfo{ @columnName("odNo") private String orderNumber; @columnName("odSeq") private String orderSequence; @columnName("odNm") private String orderName; }
좀 더 OrderInfo를 살펴보자
@RdbTable을 통해 table 명은 orderDetail 이고 @columnName을 통해 각각을 컬럼이 RDS에는 어떤 컬럼명으로 되는지 또한 알 수 있다. 이제 이를 통해서 동적으로 테이블과 컬럼을 뽑는 예제를 살펴보자
public static String findById(ProviderContext context,String id) { return new SQL(){{ SELECT(this.column(context)) .FROM(this.table(context)) .WHERE("id=#{id}") .ORDER_BY("id"); }}.toString(); } //Class에 있는 @ColumnName name 정보를 반환한다 , 반환 시 각 컬럼을 Array로 반환해야한다 private String[] column(ProviderContext context){ return Arrays.stream(domainType(context).getDeclaredFields()) .filter(t -> t.isAnnotationPresent(ColumnName.class)) .collect(Collectors.collectingAndThen(Collectors.toList(), Optional::of)) .toArray(String[]::new); } //Class에 있는 @RdbTable을 name 정보를 반환한다 private String table(ProviderContext context){ return classInfo(context).getAnnotation(RdbTable.class).map(RdbTable::name); } //Context를 통해 해당 Class 정보를 가져온다 private Class<?> classInfo(ProviderContext context){ return Arrays.stream(Arrays.stream(context.getMapperType().getGenericInterfaces()) .filter(ParameterizedType.class::isInstance) .map(ParameterizedType.class::cast) .findFirst().map(ParameterizedType::getActualTypeArguments) .get()).findFirst().filter(Class.class::isInstance) .map(Class.class::cast) .orElseThrow(NoSuchElementException::new) }
남은건 이제 where 조건이다
기본적으로는 id라는 Primary Key로 조회하는게 일반적이겠지만 여러가지 index가 있을 수 있다는것을 고려하여 조건에는 한도가 없다
그리고 조건은 같다 , 크다 , 작다와 같이 여러 조건이 될 수 있다, 또한 크다 , 작다의 경우 쌍으로 될 수도 있고 개별로만 쓰일 수도 있다
예를 들어 주문접수일이 2024년 9월 1일 부터 2024년 9월 3일까지의 정보를 조회할 수도 있고 주문금액 100원 이상을 구매한 고객의 정보를 추출 할 수도 있다
public class Condition<T> { private String condition; //등호 조건 , = , > , < private T value; // 조건값 private String columnName; //컬럼명 private Condition(Builder<T> builder) { if(builder.condition == null) builder.condition = "="; this.condition = builder.condition; this.value = builder.value; this.columnName = builder.columnName; }
public class RequestModel implements Serializable { private Condition<String> odNo;
where 조건에 이를 넣어보자
//condition의 컬럼명(odNo) condition(=)의 상태 field의 name(odNo) private String where(RequestModel id){ return Arrays.stream(id.getClass().getDeclaredFields()) .map(field -> { Condition condition = field.get(id); return condition.getColumnName() .concat(" ") .concat(condition.getCondition()) .concat("#{") .concat(field.getName()) .concat(".value}"); }) .filter(StringUtils::isNotEmpty).toArray(String[]::new); }
이제 해당 Mybatis를 통해 RDB를 구축할때는 annotation기반으로 구축이 가능하며 Condition을 통해 where 조건을 추론할 수 있다.
@Autowired private OrderInfoMapper orderInfoMapper; public List<OrderInfo> getOrderInfoList(String odNo){ RequestModel build = OrderRequestModel.builder() .odNo(Condition.<String>builder().value(odNo).condition("=").build()) .build(); List<OrderInfo> byId = orderInfoMapper.findById(build); } @Dao public interface OrderInfoMapper extends MybatisMapper<OrderInfo, RequestModel> { } public interface MybatisMapper<T, ID extends Serializable> { @SelectProvider(type = MyBatisSqlProvider.class) List<T> findById(ID id); } public class MyBatisSqlProvider<T,ID extends Serializable> implement ProviderMethodResolver { public String findById(ProviderContext context,ID id) { return new SQL().SELECT(this.column(context)) .FROM(this.table(context)) .WHERE(this.where(id)) .toString(); } }
@Autowired 개인 OrderInfoMapper orderInfoMapper; 공개 목록<OrderInfo> getOrderInfoList(String odNo){ RequestModel 빌드 = OrderRequestModel.builder() .odNo(Condition.<String>builder().value(odNo).condition("=").build()) .build( ); List<OrderInfo> byId = orderInfoMapper.findById(build); } @Dao 공용 인터페이스 OrderInfoMapper는 MybatisMapper<OrderInfo, RequestModel>을 확장합니다. { } 공용 인터페이스 MybatisMapper<T, ID는 직렬화 가능>을 확장합니다. { @SelectProvider(type = MyBatisSqlProvider.class) List<T> findById(ID id); } 공용 클래스 MyBatisSqlProvider<T,ID 확장 직렬화 가능> 구현 ProviderMethodResolver { 공용 문자열 findById(ProviderContext 컨텍스트,ID id) { 반환 새 SQL().SELECT(this.column(context)) .FROM(this.table(context)) .WHERE(this.where(id)) .toString(); } }'개발 > java' 카테고리의 다른 글
CompletableFuture (6) 2024.09.05 java stream Util 만들기 (2) 2024.09.04 싱글턴 문제점과 해결책 (0) 2024.09.02 api retry하기 (0) 2024.08.28 Exception 분리 (0) 2024.08.19