'Spring MVC 3'에 해당되는 글 5건

  1. [Spring MVC 3] Spring PetClinic 리팩토링 소식
  2. [JavaScript] Kendo UI Web Grid; Server Side Paging/Sorting/Filtering 구현
  3. [Spring MVC 3] HTTP Request Parameter 다루기
  4. [Spring MVC 3] Spring MVC 3 with Twitter Bootstrap 2 Showcase
  5. [Spring MVC 3] RequestContextHolder 클래스를 이용하여 세션 접근하기

[Spring MVC 3] Spring PetClinic 리팩토링 소식

* 오랜만에 SpringSource Team Blog를 눈팅하다가 Spring MVC의 공식 샘플 애플리케이션인 Spring PetClinic이 리팩토링되었다는 소식을 접했다. 코딩 실력이 느는 가장 빠른 방법은 역시 책을 정독하는 것보다는 고수들의  소스 코드를 해독하는 작업이 아닌가 싶다.


SPRING PETCLINIC IS ON GITHUB! (by Michael Isvy)

http://blog.springsource.org/2013/03/21/spring-petclinic-is-on-github/


* PetClinic 애플리케이션은 아래 링크로 직접 실행해볼 수 있다.

http://spring-petclinic.cloudfoundry.com/


* 전체 소스 코드는 아래 링크로 확인해볼 수 있다.

https://github.com/SpringSource/spring-petclinic/


* 애플리케이션에 대한 간단한 소개는 아래 링크에서 확인해볼 수 있다.

https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application


저작자 표시 비영리 동일 조건 변경 허락
신고

[JavaScript] Kendo UI Web Grid; Server Side Paging/Sorting/Filtering 구현

Kendo Web UI Grid에 사용되는 kendo.data.DataSource 오브젝트는 그 자체만으로 매우 편리하고 강력한 기능을 가지고 있다. 자체적으로 제공되는 페이징, 소팅, 필터링 기능을 통해 서버 사이드를 통한 데이터베이스로부터의 데이터 획득 기능이 상당히 편리해진다.

Spring MVC 3 기반의 내부 프로젝트에 사용할 목적으로 아래와 같이 kendo.data.DataSource의 요청에 대응할 수 있는 코드를 구현해봤다.


<Model: KendoUiParam.java>

import java.util.List;
import com.google.common.base.Objects;
import com.google.common.base.Strings;

public class KendoUiParam {
    private int page;
    private int pageSize;
    private int skip;
    private int take;
    private List<Sort> sort;
    private Filter filter;

    private String setValue(Object value) {
        if (value.toString().length() < 10) {
            return "'" + value + "'";
        }
        if (Objects.equal(value.toString().charAt(4), '-') && Objects.equal(value.toString().charAt(7), '-') && Objects.equal(value.toString().length(), 10)) {
            return "TO_DATE('" + value + "', 'YYYY-MM-DD')";
        }
        return "'" + value + "'";
    }

    public boolean hasWhere() {
        if (!Objects.equal(this.filter, null)) {
            return true;
        }
        return false;
    }

    public String getWhere() {
        if (Objects.equal(this.filter, null)) {
            return null;
        }
        StringBuilder where = new StringBuilder();
        where.append("(");
        for (int i = 0; i < this.filter.getFilters().size(); i++) {
            if (Strings.isNullOrEmpty(this.filter.getFilters().get(i).getValue())) {
                continue;
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "eq")) {
                where.append(this.filter.getFilters().get(i).getField() + " = " + setValue(this.filter.getFilters().get(i).getValue()));
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "neq")) {
                where.append(this.filter.getFilters().get(i).getField() + " != " + setValue(this.filter.getFilters().get(i).getValue()));
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "lt")) {
                where.append(this.filter.getFilters().get(i).getField() + " < " + setValue(this.filter.getFilters().get(i).getValue()));
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "lte")) {
                where.append(this.filter.getFilters().get(i).getField() + " <= " + setValue(this.filter.getFilters().get(i).getValue()));
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "gt")) {
                where.append(this.filter.getFilters().get(i).getField() + " > " + setValue(this.filter.getFilters().get(i).getValue()));
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "gte")) {
                where.append(this.filter.getFilters().get(i).getField() + " >= " + setValue(this.filter.getFilters().get(i).getValue()));
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "startswith")) {
                where.append(this.filter.getFilters().get(i).getField() + " LIKE '%" + this.filter.getFilters().get(i).getValue() + "'");
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "endswith")) {
                where.append(this.filter.getFilters().get(i).getField() + " LIKE '" + this.filter.getFilters().get(i).getValue() + "%'");
            } else if (Objects.equal(this.filter.getFilters().get(i).getOperator(), "contains")) {
                where.append(this.filter.getFilters().get(i).getField() + " LIKE '%" + this.filter.getFilters().get(i).getValue() + "%'");
            }
            where.append(" " + this.filter.getLogic() + " ");
        }
        where.append("1=1)");
        return where.toString();
    }

    public boolean hasOrderBy() {
        if (!Objects.equal(this.sort, null)) {
            if (!Objects.equal(this.sort.size(), 0)) {
                return true;
            }
        }
        return false;
    }

    public String getOrderBy() {
        if (Objects.equal(this.sort, null)) {
            return null;
        }
        StringBuilder orderBy = new StringBuilder();
        for (int i = 0; i < this.sort.size(); i++) {
            orderBy.append(this.sort.get(i).getField() + " " + sort.get(i).getDir());
            if (!Objects.equal(i, this.sort.size() - 1)) {
                orderBy.append(", ");
            }
        }
        return orderBy.toString();
    }

    public boolean hasPaging() {
        if (!Objects.equal(this.page, 0) || !Objects.equal(this.pageSize, 0)) {
            return true;
        }
        return false;
    }

    public String getPagingSql(String sql) {
        if (!hasPaging()) {
            return sql;
        }
        String prefix = "SELECT * FROM (SELECT ROWNUM AS ROW_NUMBER, COUNT(*) OVER() TOTAL_ROW_COUNT, A.* FROM ( ";
        String postfix = " ) A ) WHERE ROWNUM <= :PAGE_SIZE AND ROW_NUMBER > (:PAGE_NUMBER-1) * :PAGE_SIZE";
        return prefix + sql + postfix;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getSkip() {
        return skip;
    }

    public void setSkip(int skip) {
        this.skip = skip;
    }

    public int getTake() {
        return take;
    }

    public void setTake(int take) {
        this.take = take;
    }

    public List<Sort> getSort() {
        return sort;
    }

    public void setSort(List<Sort> sort) {
        this.sort = sort;
    }

    public Filter getFilter() {
        return filter;
    }

    public void setFilter(Filter filter) {
        this.filter = filter;
    }

    @Override
    public String toString() {
        return "KendoUiParam [page=" + page + ", pageSize=" + pageSize + ", skip=" + skip + ", take=" + take + ", sort=" + sort + ", filter=" + filter + "]";
    }
}

<Model: Sort.java>

public class Sort {
    private String field;
    private String dir;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public String getDir() {
        return dir;
    }

    public void setDir(String dir) {
        this.dir = dir;
    }

    @Override
    public String toString() {
        return "Sort [field=" + field + ", dir=" + dir + "]";
    }
}


<Model: Filter.java>

import java.util.List;

public class Filter {
    private List<Filters> filters;
    private String logic;

    public List<Filters> getFilters() {
        return filters;
    }

    public void setFilters(List<Filters> filters) {
        this.filters = filters;
    }

    public String getLogic() {
        return logic;
    }

    public void setLogic(String logic) {
        this.logic = logic;
    }

    @Override
    public String toString() {
        return "Filter [filters=" + filters + ", logic=" + logic + "]";
    }
}


<Model: Filters.java>

public class Filters {
    private String field;
    private String operator;
    private String value;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public String getOperator() {
        return operator;
    }

    public void setOperator(String operator) {
        this.operator = operator;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Filters [field=" + field + ", operator=" + operator + ", value=" + value + "]";
    }
}


<Controller>
    @RequestMapping(value = "/pr/ajax/readStkMtlMaster", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
    @ResponseBody
    public Map<String, Object> readStkMtlMaster(@RequestBody KendoUiParam param) {
        Map<String, Object> response = new HashMap<String, Object>();
        try {
            logger.info("==================== [MM-PUR] readStkMtlMaster() | Parameter='" + param.toString() + "'");
            List<Map<String, Object>> result = mmService.readStkMtlMaster(param);
            response.put("data", result);
            if (Objects.equal(result.size(), 0)) {
                response.put("total", 0);
            } else {
                response.put("total", result.get(0).get("TOTAL_ROW_COUNT"));
            }
            logger.info("==================== [MM-PUR] readStkMtlMaster() | Parameter='" + response.toString() + "'");
            return response;
        } catch (Exception ex) {
            Map<String, Object> errorResponse = JsonUtil.createErrorResponse(ex);
            return errorResponse;
        }
    }


<Service>

    public List<Map<String, Object>> readStkMtlMaster(KendoUiParam param) {
        logger.info("==================== [MM-PUR] readStkMtlMaster() | Parameter='" + param.toString() + "'");
        List<Map<String, Object>> result = mmDAO.readStkMtlMaster(param);
        logger.info("==================== [MM-PUR] readStkMtlMaster() | Return='" + result.toString() + "'");

        return result;
    }


<DAO>

    public List<Map<String, Object>> readStkMtlMaster(KendoUiParam param) {
        logger.info("==================== [MM-PUR] readStkMtlMaster() | Parameter='" + param.toString() + "'");
        MapSqlParameterSource paramSource = new MapSqlParameterSource();
        SqlBuilder q = new SqlBuilder();
        q.SELECT("PURCHASETYPE, FACTORYNAME, CONSUMABLESPECNAME, CONSUMABLESPECVERSION, DESCRIPTION, CONSUMABLETYPE, CONSUMEUNIT, IQAAQL, IQADCSPECNAME, CREATEUSER, CREATETIME");
        q.FROM("CONSUMABLESPEC");
        if (param.hasWhere()) {
            q.WHERE(param.getWhere());
        }
        if (param.hasOrderBy()) {
            q.ORDER_BY(param.getOrderBy());
        }
        if (param.hasPaging()) {
            paramSource.addValue("PAGE_NUMBER", param.getPage());
            paramSource.addValue("PAGE_SIZE", param.getPageSize());
        }
        List<Map<String, Object>> result = namedParameterJdbcTemplate.queryForList(param.getPagingSql(q.toString()), paramSource);
        logger.info("==================== [MM-PUR] readStkMtlMaster() | Return='" + result.toString() + "'");

        return result;
    }


<View: HTML>

<div>
  <blockquote>
    <h2>원부자재 PR 생성</h2>
  </blockquote>
</div>

<div class="alert alert-info">
  <strong>원부자재 기준정보 조회 조건 설정</strong>
</div>

<table class="table table-bordered table-hover">
  <tbody>
    <tr>
      <td style="width: 30%">PURCHASETYPE:</td>
      <td style="width: 70%"><a class="searchCriteria" data-type="text" id="purchaseType" name="purchaseType"></a></td>
    </tr>
    <tr>
      <td>CONSUMALBLESPECNAME:</td>
      <td><a class="searchCriteria" data-type="text" id="consumableSpecName" name="consumableSpecName"></a></td>
    </tr>
    <tr>
      <td>DESCRIPTION:</td>
      <td><a class="searchCriteria" data-type="text" id="description" name="description"></a></td>
    </tr>
    <tr>
      <td>CREATETIME:</td>
      <td><a class="searchCriteria" data-type="date" id="createTimeStart" name="createTimeStart"></a> ~ <a class="searchCriteria" data-type="date" id="createTimeEnd" name="createTimeEnd"></a></td>
    </tr>
  </tbody>
</table>

<table class="table">
  <tbody>
    <tr>
      <td style="width: 30%"></td>
      <td style="width: 70%"><button class="btn btn-primary input-medium" id="searchButton">조회</button>&nbsp;&nbsp; <button class=
      "btn btn-success input-medium" id="resetButton">초기화</button></td>
    </tr>
  </tbody>
</table>

<div class="alert alert-info">
  <strong>조회 결과</strong>
</div>

<div class="k-content" style="overflow: auto; width: 100%">
    <div id="stkMtlTable"></div>
</div>

<p></p>


<View: JavaScript>

var setSearchCriteria = function() {
    $('#purchaseType').editable({
      emptytext: '미입력'
    });
    $('#consumableSpecName').editable({
      emptytext: '미입력'
    });
    $('#description').editable({
      emptytext: '미입력'
    });
    $('#createTimeStart').editable({
      emptytext: '미입력',
      format: 'yyyy-mm-dd'
    });
    $('#createTimeEnd').editable({
      emptytext: '미입력',
      format: 'yyyy-mm-dd'
    });
    $('.searchCriteria').editable('setValue', '');
  };
setSearchCriteria();

var dataSource = new kendo.data.DataSource({
  serverPaging: true,
  serverSorting: true,
  serverFiltering: true,
  pageSize: 10,
  filter: {
    field: '1',
    operator: 'neq',
    value: '1'
  },
  transport: {
    read: {
      type: 'post',
      dataType: 'json',
      contentType: 'application/json;charset=UTF-8',
      url: '/pr/ajax/readStkMtlMaster'
    },
    parameterMap: function(data, operation) {
      return JSON.stringify(data);
    }
  },
  schema: {
    data: function(response) {
      return response.data;
    },
    total: function(response) {
      return response.total;
    }
  }
});

$('#stkMtlTable').kendoGrid({
  dataSource: dataSource,
  sortable: {
    mode: "multiple",
    allowUnsort: true
  },
  selectable: true,
  navigatable: false,
  pageable: {
    refresh: true,
    pageSizes: true
  },
  scrollable: true,
  columnMenu: false,
  autoBind: false,
  columns: [{
    field: 'PURCHASETYPE',
    title: 'PURCHASETYPE',
    width: 110
  }, {
    field: 'FACTORYNAME',
    title: 'FACTORYNAME',
    width: 110
  }, {
    field: 'CONSUMABLESPECNAME',
    title: 'CONSUMABLESPECNAME',
    width: 175
  }, {
    field: 'CONSUMABLESPECVERSION',
    title: 'CONSUMABLESPECVERSION',
    width: 185
  }, {
    field: 'DESCRIPTION',
    title: 'DESCRIPTION',
    width: 600
  }, {
    field: 'CONSUMABLETYPE',
    title: 'CONSUMABLETYPE',
    width: 140
  }, {
    field: 'CONSUMEUNIT',
    title: 'CONSUMEUNIT',
    width: 130
  }, {
    field: 'IQAAQL',
    title: 'IQAAQL',
    width: 90
  }, {
    field: 'IQADCSPECNAME',
    title: 'IQADCSPECNAME',
    width: 130
  }, {
    field: 'CREATEUSER',
    title: 'CREATEUSER',
    width: 120
  }, {
    field: 'CREATETIME',
    title: 'CREATETIME',
    width: 120,
    template: '#=Date.create(CREATETIME).format("{yyyy}-{MM}-{dd}")#'
  }]
});

$('#searchButton').click(function() {
  var searchCriteria = $('.searchCriteria').editable('getValue');
  var query = {
    page: 1,
    pageSize: 10,
    sort: {
      field: 'CREATETIME',
      dir: 'desc'
    },
    filter: [{
      field: 'PURCHASETYPE',
      operator: 'eq',
      value: searchCriteria.purchaseType
    }, {
      field: 'CONSUMABLESPECNAME',
      operator: 'contains',
      value: searchCriteria.consumableSpecName
    }, {
      field: 'DESCRIPTION',
      operator: 'contains',
      value: searchCriteria.description
    }, {
      field: 'CREATETIME',
      operator: 'gte',
      value: searchCriteria.createTimeStart
    }, {
      field: 'CREATETIME',
      operator: 'lte',
      value: searchCriteria.createTimeEnd
    }]
  };
  dataSource.query(query);
});

$('#resetButton').click(function() {
  $('.searchCriteria').editable('setValue', '');
  var query = {
    filter: {
      field: '1',
      operator: 'neq',
      value: '1'
    }
  };
  dataSource.query(query);
});


<View: 실행화면>


저작자 표시 비영리 동일 조건 변경 허락
신고

[Spring MVC 3] HTTP Request Parameter 다루기

클라이언트로부터 요청받은 파라메터가 아래와 같다고 가정

page=1
pageSize=10
sort[0][field]="FACTORYNAME"
sort[0][dir]="desc"


Controller에서 파라메터를 받을 Model 클래스를 작성

public class SomeModel {
  private int page;
  private int pageSize;
  private String sortField;
  private String sortDir;
  // Getters & Setters 메써드 작성
  // toString() 메써드 작성
}


Controller 메써드 작성

@Controller
public class someController {
  @RequestMapping(value = "/doSomething", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
  @ResponseBody
  public Map<String, Object> doSomething(@ModelAttribute SomeModel someModel, @RequestParam(value = "sort[0][field]", required = false) String sortField, @RequestParam(value = "sort[0][dir]", required = false) String sortDir) {
    Map<String, Object> response = new HashMap<String, Object>();
    someModel.setSortField(sortField);
    if (Objects.equal(sortDir, null)) {
      someModel.setSortDir(sortDir);
    } else {
      someModel.setSortDir(sortDir.toUpperCase());
    }
    List<Map<String, Object>> data = someService.doSomething(someModel);
    response.put("data", data);
    if (Objects.equal(data.size(), 0)) {
      response.put("total", 0);
    } else {
      response.put("total", data.get(0).get("TOTAL_ROW_COUNT"));
    }
    return response;
  }


예제 설명

* Spring MVC 3에서 컨트롤러는 각각의 HTTP 요청 파라메터를 @RequestParam을 통해 Java 기본 타입 변수에 담을 수 있음
* 보다 편리한 방법으로 여러 파라메터들을 @ModelAttribute를 통해 미리 정의된 모델 클래스에 한꺼번에 담을 수 있음
* @RequestParam의 속성 required = false로 정의하면 파라메터가 전달되지 않아도 변수에 null로 담겨짐
* 모델 클래스에 정의되어 있지만 전달되지 않은 파라메터는 변수에 null로 담겨짐
* sort[0][field]와 같이 List<Object> 형태의 파라메터는 @ModelAttribute를 통해 담을 수 없음(.NET에서는 가능)
* sort[0].field와 같이 전달되어야 @ModelAttribute로 담는 것이 가능
* 따라서 @RequestParam을 통해 개별 변수에 담은 후 모델 클래스에 대입하는 번거로운 방법 사용
* 위 예제에 사용된 Objects 클래스는 Google Guava 라이브러리가 제공하는 유틸리티 클래스


참고자료


저작자 표시 비영리 동일 조건 변경 허락
신고

[Spring MVC 3] Spring MVC 3 with Twitter Bootstrap 2 Showcase

본 글은 크리에이티브 커먼즈 라이센스(CC BY-NC-SA)를 준수합니다.

 



대부분의 규모있는 프로젝트는 다수의 인력으로 체계화된 분업을 통해 진행되지만 나같이 촉박한 일정 속에 작은 규모의 인트라넷 웹 애플리케이션 개발 프로젝트를 혼자서 진행하는 경우도 있다.


데이터 모델링부터 비즈니스 로직 설계, UX까지 혼자서 해내려면 검증된 수많은 프레임워크 및 라이브러리의 조합으로 극한의 생산성을 끌어내는 수 밖에 없는데 결국 구글링과의 싸움이고 계속되는 학습의 고통이 뒤따른다.


최근에 서버 사이드는 Spring MVC 3, Sitemesh를 사용하고 클라이언트 사이드는 jQuery, Knockout, Twitter Bootstrap 2를 이용한 극한의 생산성을 추구한 1인 프로젝트를 진행하고 있는데 각 기술을 적절히 조합하기 위해 수많은 시행착오와 학습의 고통을 겪는 와중에 꽤 괜찮은 해외 블로거의 Showcase를 찾아서 공유하고자 한다. 웹 애플리케이션 개발에서 가장 많이 쓰이는 기능들을 쇼케이쇼 형식으로 보여준다.


[소개글] Spring MVC 3.1 with Twitter Bootstrap 2.1 Showcase / Priyatam


[쇼케이스] Spring MVC 3.1 with Twitter Bootstrap 2.1 Showcase / Priyatam


[소스코드] Spring MVC 3.1 with Twitter Bootstrap 2.1 Showcase / Priyatam


저작자 표시 비영리 동일 조건 변경 허락
신고

[Spring MVC 3] RequestContextHolder 클래스를 이용하여 세션 접근하기

Spring MVC 3에서 세션에 저장된 로그인 유저 정보 객체에 접근할 때 Controller 메써드의 파라메터로 HttpSession 객체를 명시하여 처리하였으나 중복된 코드가 발생함에 따라 아래와 같은 방법으로 개선하였다.


1. UserService에 아래와 같은 세션 접근 메써드를 작성

    public LoginUser getLoginUser() {
        ServletRequestAttributes servletRequestAttribute = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpSession httpSession = servletRequestAttribute.getRequest().getSession(true);
        LoginUser loginUser = (LoginUser) httpSession.getAttribute("loginUser");
        Preconditions.checkNotNull(loginUser, "로그인 상태가 아닙니다. 로그인부터 하세요.");

        return loginUser;
    }


2. 작성한 메써드를 통해 Controller 메써드에서는 아래와 같이 간단하게 접근하여 세션에 저장된 로그인 유저 정보를 개체를 획득

LoginUser loginUser = userService.getLoginUser();


저작자 표시 비영리 동일 조건 변경 허락
신고