Pageable로 Limit 걸기


JPA, JPQL 을 사용하다가 Limit이 필요한 경우가 생겼었습니다.


JPQL에 limit을 사용하니 에러가 발생하여 찾아보니 JPA, JPQL 에서 limit의 기능을 사용하려면 다른 방법을 사용해야 합니다.



Pageable limitOneHundred = new PageRequest(startPosition, 100);
List<SelectAll> tempList = requestRepository.findAllByDate(startDate, targetDate, limitOneHundred);

@Query(value = "SELECT n " +
"FROM LogRequest n " +
"WHERE DATE(n.regDate) >= :date " +
"AND DATE(n.regDate) = :targetDate " +
"ORDER BY n.no")
public List<SelectAllNaver> findAllByDate(@Param("date") Date date,
@Param("targetDate") Date targetDate,
Pageable limitOneHundred);



Pageable 을 사용하면 limit을 사용하는 것처럼 범위를 지정하여 페이징을 할 수 있습니다.

혹은 아래와 같이 Query annotation의 nativeQuery 옵션을 true로 하여 limit을 사용하는 방법도 있습니다.



@Query(nativeQuery = true, value = "select * from SLSNotification s where s.userId = :userId ORDER BY snumber desc LIMIT 20")
List<SLSNotification> getUserIdforManage(@Param("userId") String userId);




참조.

http://www.baeldung.com/spring-data-jpa-query

https://stackoverflow.com/questions/47616482/how-to-limit-result-in-query-used-in-spring-data-repository



JPQL이란?


JPQL(Java Persistence Query Language) 는 JPA (Java Persistence API) 의 일부로 정의된 플랫폼 독립적인 객체지향 쿼리 언어 입니다.


JPQL 은 관계형 데이터베이스의 엔티티에 대한 쿼리를 만드는데 사용됩니다.


SQL 에 크게 영향을 받아 SQL 문과 비슷하지만 데이터베이스의 테이블에 직접 연결되는 것이 아니라 JPA 엔티티에 대해 동작합니다.


그래서 JPQL 의 쿼리에는 테이블이 아닌 엔티티에서 표현하고 있는 컬럼의 이름을 써줘야 합니다.


아래의 Spring을 통한 예시에 나와있습니다.



@Entity
public class UserStatistics {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String host;

private String href;

private String pathName;

private String referrerHost;

private String referrerPath;

private Long visitCount;

private LocalDate date;
}



@Query(value = "SELECT u.pathName as pathName, DATE(u.date) as date, u.visitCount as visitCount " +
"FROM UserStatistics u GROUP BY DATE(u.date), u.pathName, u.visitCount")
List<UserStatisticsDateCount> findVisitCountGroupByDate();




참조

https://en.wikipedia.org/wiki/Java_Persistence_Query_Language

http://www.baeldung.com/spring-data-jpa-query







[직관적인 클린 코드를 위해] IF - ELSE IF 문 줄이기 2



이번 블로그 글은 저번에 이은 '[직관적인 클린 코드를 위해] IF - ELSE IF 문 줄이기 2' 입니다.


저번에는 동시에 확인해야 하는 조건이 하나일 경우에 IF, ELSE IF 문을 줄이는 방법을 봤습니다.


하지만 만약 동시에 확인해야 할 조건이 2개면 어떨까요?



public static void main(String[] args) {
Map<String, Alphabet> typeMap = new HashMap<>();
typeMap.put("A", new A());
typeMap.put("B", new B());
typeMap.put("C", new C());

String type = "B";

if(첫번째 조건) {
typeMap.get(type).test();
}
else if(첫번째 조건) {
typeMap.get(type).test();
}
else if(첫번째 조건) {
typeMap.get(type).test();
}
}



어쩔 수 없이 IF, ELSE IF 문을 사용해야 하는 것일까요?


예를 들어 상황을 가정하겠습니다.

DB의 여러 테이블에서 데이터를 받는데 이를 테이블 명과 Insert, Update, Delete 로 동시에 구분해서 수행해야 한다고 가정하겠습니다.


공통적인 부분으로 묶어보겠습니다.


  • 각각의 테이블은 Insert, Update, Delete가 일어납니다.

interface Insert {
public void insert();
}

class A implements Insert{
@Override
public void insert() {

}
}

class B implements Insert{
@Override
public void insert() {

}
}

class C implements Insert{
@Override
public void insert() {

}
}



Insert 패키지를 만들고 insert 추상매서드를 가진 인터페이스를 생성했습니다.

그리고 이 인터페이스를 implements 하는 3개의 클래스를 각각 만들었습니다.

Update와 Delete도 똑같이 했습니다.


하지만 이렇게 해도 1번 글과 같이 Map에 넣어 한번에 알아서 분류되고 원하는 value가 나오게 하려면 Insert, Update, Delete 인터페이스 타입을 value로 가진 각각의 Map이 필요합니다.

다시 공통적인 요소를 생각해보면 Insert, Update, Delete는 DB의 Crud 중 하나입니다.



interface Crud {
public void execute();
}

interface Insert extends Crud{
public void insert();
}

class A implements Insert{
@Override
public void execute() {
insert();
}

@Override
public void insert() {

}
}



Crud 인터페이스를 만들어 이를 Insert, Update 인터페이스에 상속 시키고 A 클래스에서는 이를 구현합니다.

그리고 두가지 조건을 처리하기 위해 Enum 클래스를 만들어 사용했습니다.


enum CommandEnum {
INSERT_A("A", "insert"),
UPDATE_A("A", "update"),
INSERT_B("B", "insert"),
UPDATE_B("B", "update"),
INSERT_C("C", "insert"),
UPDATE_C("C", "update");

private String tableName;
private String queryType;

CommandEnum(String tableName, String queryType) {
this.tableName = tableName;
this.queryType = queryType;
}

public String getTableName() {
return this.tableName;
}

public String getQueryType() {
return this.queryType;
}
}



그리고 이런 Enum을 사용하기 위해 따로 Mapper 클래스를 만들었습니다.

Mapper 클래스에서는 EnumMap을 생성하고 이를 for - each 문으로 값을 두가지 조건에 합당하는 Crud 객체를 리턴합니다.



class QueryTypeMapper {
private EnumMap<CommandEnum, Crud> commandEnumMap = new EnumMap<CommandEnum, Crud>(CommandEnum.class);

QueryTypeMapper() {
commandEnumMap.put(CommandEnum.INSERT_A, new InsertA());
commandEnumMap.put(CommandEnum.UPDATE_A, new UpdateA());
commandEnumMap.put(CommandEnum.INSERT_B, new InsertB());
commandEnumMap.put(CommandEnum.UPDATE_B, new UpdateB());
commandEnumMap.put(CommandEnum.INSERT_C, new InsertC());
commandEnumMap.put(CommandEnum.UPDATE_C, new UpdateC());
}

public Crud getCommand(String tableName, String type) {
for(CommandEnum commandEnum : commandEnumMap.keySet()) {
if(commandEnum.getTableName().equals(tableName) && commandEnum.getQueryType().equals(type)) {
return commandEnumMap.get(commandEnum);
}
}
throw new IllegalStateException("Classify command not found. ");
}
}



이렇게 만들어진 QueryTypeMapper 클래스와 CommandEnum enum클래스를 이용해 IF - ELSE IF 문으로 어지러워 질뻔한 코드가 단 두줄로 변하게 됩니다.


아래에 전체 코드가 있습니다. InsertA 클래스만 있지만 나머지 클래스들도 같은 방식이라 넣지 않았습니다.


간략하게 짜고 바쁘게 하다보니 클래스와 매서드, 변수의 네이밍에 신경 못쓴점은 양해 부탁드립니다.



public class CleanCode {

public static void main(String[] args) {
String tableName = "B";
String type = "insert";

QueryTypeMapper queryTypeMapper = new QueryTypeMapper();
queryTypeMapper.getCommand(tableName, type).execute();
}
}



class QueryTypeMapper {
private EnumMap<CommandEnum, Crud> commandEnumMap = new EnumMap<CommandEnum, Crud>(CommandEnum.class);

QueryTypeMapper() {
commandEnumMap.put(CommandEnum.INSERT_A, new InsertA());
commandEnumMap.put(CommandEnum.UPDATE_A, new UpdateA());
commandEnumMap.put(CommandEnum.INSERT_B, new InsertB());
commandEnumMap.put(CommandEnum.UPDATE_B, new UpdateB());
commandEnumMap.put(CommandEnum.INSERT_C, new InsertC());
commandEnumMap.put(CommandEnum.UPDATE_C, new UpdateC());
}

public Crud getCommand(String tableName, String type) {
for(CommandEnum commandEnum : commandEnumMap.keySet()) {
if(commandEnum.getTableName().equals(tableName) && commandEnum.getQueryType().equals(type)) {
return commandEnumMap.get(commandEnum);
}
}
throw new IllegalStateException("Classify command not found. ");
}
}



enum CommandEnum {
INSERT_A("A", "insert"),
UPDATE_A("A", "update"),
INSERT_B("B", "insert"),
UPDATE_B("B", "update"),
INSERT_C("C", "insert"),
UPDATE_C("C", "update");

private String tableName;
private String queryType;

CommandEnum(String tableName, String queryType) {
this.tableName = tableName;
this.queryType = queryType;
}

public String getTableName() {
return this.tableName;
}

public String getQueryType() {
return this.queryType;
}
}



interface Crud {
public void execute();
}



interface Insert extends Crud{
public void insert();
}



class InsertA implements Insert{
@Override
public void execute() {
insert();
}

@Override
public void insert() {
System.out.println("Class A insert method!");
}
}



제가 준비한 내용은 여기까지 입니다.

부족함이 많은 글임에도 끝까지 읽어주셔서 감사합니다.

다음에도 이와 같이 기본적인 내용이지만 괜찮다고 생각드는 것이 있으면 잘 정리해서 공유드리겠습니다.

감사합니다.


+ Recent posts