79일차(1)/Android App(42) : Http 요청으로 Oracle DB 출력하기
- 안드로이드 앱에서 DB에 있는 Todo 리스트 가져오기
- 안드로이드 앱에서 DB 테이블에 데이터 추가, 삭제하기
새 모듈 생성
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<ListView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/listView"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="할일 입력..."
android:id="@+id/inputText"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="추가"
android:id="@+id/addBtn"/>
</LinearLayout>
</LinearLayout>
- sqLite 예제에서 사용한 layout 복사해옴
- 이전 예제에서는 내부 DB를 사용한 것이고,
이번 예제에서는 같은 기능을 SpringBoot의 oracle DB를 활용해서 해볼 것!
- step13에서 할일을 입력해서 ListView에 추가했던 것처럼 원격지 DB에 들어갈 수 있도록 코딩.
삭제- 오래 클릭시 들어오는 요청으로삭제
- 안드로이드 앱과 서버 간의 대화를 구현하기.
- SQLite에 UI를 다루는 기본동작은 있다. (이 경우 dbHelper는 필요없다)
- 원격지 서버를 쓴다는 것은 사용하는 모든 클라이언트가 같은 테이블을 공유한다는 것.
- 똑같이 동작하되 원격지 DB를 쓰도록 수정!
- 앱과 웹서버 사이의 소통 방식
- 출시된 어떤 안드로이드 앱이 있다고 가정하기
- 이 앱에 띄우는 정보는 원격지 웹서버의 어떤 DB에 있는 정보이다.
- 이 멀리 떨어진 DB에 어떻게 접근할 것인가? 바로 화면에 띄울 수는 없다.
- 그래서 웹서버 어플리케이션이있다. 이 서버를 spring Boot로 만들어준다.
- 안드로이드 앱에서 필요한 정보가 있다면 이 웹서버 앱에 요청해준다. 그러면 서버에서 응답을 준다.
- 웹브라우저가 아니기 때문에 응답을 줄때 html, css등은 의미가 없다.
대신 json, xml 형식 문자열로 응답한다!
- 앱에서 이것을 파싱한다. 파싱은 문자열 안에서 원하는 내용만 쏙쏙 빼내는 것을 의미한다.
- 앱과 웹서버 사이의 대화방식을 학습!
- 오라클 Todo Table 을 웹서버 연동
- 안드로이드 앱에서 리퀘스트를 보내는 핵심객체는 HttpURLConnection 객체이다.
(브라우저가 아니므로 요청해주는 객체가 필요하다)
- 응답되는 객체를 추출해서 사용한다.
- jsp페이지를 포워드 이동해서 응답하는게 아니라 JSON형식의 문자열을 응답한다는 것 빼고는
웹서버 입장에서는 앱에서 요청하든 브라우저에서 요청하든 다를 것이 없다.
- get/post 와 같은 전송 방식이 중요한 것이지, 어떤 객체를 사용해서 요청하는지 등을 구별할 수는 없다.
- HttpURLConnection 객체를 사용하면 웹브라우저에서 폼 전송하거나/링크를 누른 효과를 똑같이 낼 수 있다.
- Util 클래스 안에 들어있는 메소드
- 키보드 숨기는 메소드 (Util.hideKeyboard 로 this 전달하면 사용가능)
- 포커스 해제하는 메소드(releaseFocus) 등
- 이렇게 자주 사용하는 기능을 Util로 만들어두면 앱을 금방 만들 수 있다.
- get, post방식 요청 메소드도 Util 안에 들어있다.
- 요청 id, 요청 url, 파라미터 값, 리스너를 전달하면 요청할 수 있다.
- doInBackground 메소드 안에서 get 방식 파라미터를 구성하고, 값을 누적시켜 저장
- num, name, addr 정보를 전달할 때,
이것을 Map에 담아서 ?num=1&name=kim&addr=seoul 이 되도록 하기
- 이 메소드의 while문 부분은 이런 문자열을 만들어내는 코드이다.
- http://localhost:9000/member 뒤에 저 내용을 붙여준다.
- GET 방식 요청 파라미터. map에 담긴 내용을 변화시켜서 뒤에 갖다붙이는 효과(링크를 누르는 효과와 같다)
- 요청하는 방법은? httpURLConnection 사용
- 서버가 문자열에 응답하면서(반복문을 돌면서) 출력하는 문자열을
하나하나 연결연산자로 연결하는것은 비효율적이므로, StringBuilder 에 누적시킨 후 한번에 가지고 온다.
- 여기서 가져오는 문자열을 json으로 활용하면 편하다!
- utility에서 "data"라는 키값으로 Map에 담고, 성공이라면 onSuccess() 를 호출하면서 map 넣어주고,
실패하면 onFail() 호출하면서 map 넣어주기
- 담은 문자열을 어떻게 꺼내는가?
- 서버가 응답하는 문자열의 형식
@ResponseBody
public Map xxx() 이 있다고 하면,
컨트롤러에서 Map 또는 Dto를 리턴하면 아래와 같은 형식의 문자열이 응답한다.
{"key":value, "key2":value2", ...}
- App에서 위 형식의 문자열을 활용하려면 JSONObject 객체를 사용하면 된다.
생성자의 인자에 저 내용을 넣어주어야 한다.
- 파란색 박스: 서버에서 사용하는 내용 (응답시키는 방법)
- 분홍색 박스: App에서 사용하는 내용 (어떤객체로 가져와서 파싱하는가)
( parsing : 특정 키 값으로 저장된 데이터를 빼내는 것)
- 이들의 연결고리가 되는 것은 JSON문자열이다!
- List도 사용할 수 있다. 컨트롤러에서 List<String> 을 리턴하면 아래와 같은 형식의 문자열이 응답된다.
@ResponseBody
public List<String> xxx()
→ [ "data1", "data2", data3", .... ]
- App에서 위와 같은 형식의 문자열을 활용하려면 JSONArray 객체를 사용하면 된다.
- List<> 제너릭이 Dto, map 인 경우
@ResponseBody
public List<Dto> xxx()
→ [ {"key" : valure }, {}, {}, ... ]
- 컨트롤러에서 List<Dto> or List<Map> 을 리턴하면 위와 같은 형식의 문자열이 응답된다.
- JsonArray의 0,1,2번 방 안에있는것이 JSONObject 이다.
- 출력되는 형태에 따라서 적절한 JSON 메소드 사용하기
- 이후에는 로그인, 로그아웃기능 구현 예정
- 어떤 인증(로그인)을 받아야만 사용할 수 있는 서버의 데이터도 있다.
- 브라우저도 없는데 어떻게 로그인 기능을 사용할까?
- httpSession 객체를 사용. session을 활용하는 방법으로 로그인해볼 것!
- 안드로이드에서 DB에 있는 Todo 리스트 가져오기
Android Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!--
1. 개인정보
2. 위치정보
3. 비용이 발생할 가능성이 있는 작업
4. 배터리를 많이 소모하는 등등의 작업
위의 작업을 app에서 한다면 허가를 얻어내야 한다.
인터넷 자원을 사용하겠다는 permission
-->
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.MyAndroid"
android:usesCleartextTraffic="true"> <!-- https가 아닌 http 요청도 가능하게 하는 설정 -->
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
- Android Manifest.xml 에 인터넷 사용 permission 추가
- 앱 설치시 개인정보 사용, 위치정보 사용, 저장소 사용, 배터리 소모, .. 등등의 정보를 요청하는 것은
여기에 permission으로 명시해놓은 내용만 가능하다! 이 내용만 접근할 수 있다.
- usesCleartextTraffic : https가 아닌 http 요청도 가능하게 하는 설정
TodoAdapter
- 이전에는 코틀린으로 만들었던 것을 java로 만들어보기
- listview_cell 의 레이아웃을 가지고 있다.
listview_cell.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/text_content"
android:textSize="20sp"
android:text="내용입니다..."
android:layout_margin="10dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/text_regdate"
android:textColor="#999999"
android:gravity="right"
android:text="등록일입니다..."
android:layout_margin="10dp"/>
</LinearLayout>
TodoDto
package com.example.step17example;
public class TodoDto {
//필드
private int num;
private String content;
private String regdate;
//디폴트 생성자
public TodoDto () {}
public TodoDto(int num, String content, String regdate) {
this.num = num;
this.content = content;
this.regdate = regdate;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRegdate() {
return regdate;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
}
TodoAdapter
package com.example.step17example;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
public class TodoAdapter extends BaseAdapter {
//필드
private Context context;
private int layoutRes;
private List<TodoDto> list;
//생성자
public TodoAdapter(Context context, int layoutRes, List<TodoDto> list) {
this.context = context; //액티비티의 참조값
this.layoutRes = layoutRes; //listView의 cell layout 리소스 아이디
this.list = list; //listView에 출력할 데이터를 가지고 있는 모델
}
//모델의 개수 리턴
@Override
public int getCount() {
return list.size();
}
//position에 해당하는 모델을 리턴
@Override
public Object getItem(int position) {
return list.get(position);
}
//position에 해당하는 아이템의 아이디(primary key)
@Override
public long getItemId(int position) {
//position에 해당하는 TodoDto의 번호를 리턴하는 것
return list.get(position).getNum();
}
//position에 해당하는 cell View를 리턴해주어야 한다. (listView가 호출할 예정인 메소드)
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//만일 view를 새로 만들어야 한다면
if(convertView == null){
//여기서 만들고 (res/layout/listView_cell.xml 문서를 전개해서)
//해당 문서의 리소스 아이디는 생성자의 인자로 전달되어서 필드에 저장해 놓은 상태
convertView=LayoutInflater.from(context).inflate(layoutRes, parent, false);
}
//View에 모델이 가지고 있는 데이터를 출력하고
TodoDto dto=list.get(position);
//listView_cell.xml 안에 있는 TextView의 참조값 얻어와서 문자열 출력
TextView text_content=convertView.findViewById(R.id.text_content);
TextView text_regdate=convertView.findViewById(R.id.text_regdate);
text_content.setText(dto.getContent());
text_regdate.setText(dto.getRegdate());
//View를 리턴해준다.
return convertView;
}
}
- BaseAdapter 상속해주고 메소드 4개 override
- 필드와 생성자 만들기
- 생성자에는 context(액티비티), 레이아웃 리소스의 int값, 모델(List<TodoDto>)를 전달해주어야 한다.
- getItem은 TodoDto type을 리턴하는데, 이는 object 타입도 될 수 있으므로
object를 리턴하는 메소드에서 리턴값이 될 수 있다.
- position의 primary key를 리턴해준다.
- position에 해당하는 num을 리턴하는 것. getNum()
- 원래는 long 타입을 리턴해야 하는데, int 타입을 리턴하는 것은 가능하다! long이 더 넓은 범위이므로
- 리스트뷰는 최소한의 cell 갯수만 만들고 나머지는 재활용하는 구조
- 처음에는 값이 null이다. 안에서 값을 만들어주어야 한다.
- View에 데이터를 출력하고, textView의 참조값을 얻어와서 문자열을 출력한다.
- 이 아답타를 listView에 연결한다. 그러면 ListView에서 cellview를 출력해줄 것!
- activity_main.xml 에 있는 UI의 참조값을 얻어와서 MainActivity에서 작업하기!
MainActivity (최종)
package com.example.step17example;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.example.step17example.databinding.ActivityMainBinding;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
view binding 사용하는 방법
1. build.gradle 파일에 아래 설정 추가
buildFeatures {
viewBinding = true
}
2. 우상단의 sync now 링크를 눌러서 설정이 적용되도록 한다.
3. layout xml 문서의 이름대로 클래스가 자동으로 만들어진다.
예를 들어 activity_main.xml 문서면 ActivityMainBinding 클래스
activity_sub.xml 문서면 ActivitySubBinding 클래스
*/
public class MainActivity extends AppCompatActivity implements Util.RequestListener, AdapterView.OnItemLongClickListener {
//자주 사용하는 문자열은 static final 상수로 만들어두고 사용하면 편리하다.
public static final String BASE_URL="http://192.168.0.34:9000/boot07/";
//필드
List<TodoDto> list;
TodoAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
//R.layout.activity_main.xml 문서를 전개해서 View를 만들기
ActivityMainBinding binding=ActivityMainBinding.inflate(getLayoutInflater());
//전개된 layout에서 root를 얻어내서 화면 구성을 한다.(여기서는 LinearLayout 이다.)
setContentView(binding.getRoot());
//아답타에 넣어줄 모델 목록
list=new ArrayList<>();
//listView에 연결할 아답타
adapter=new TodoAdapter(this, R.layout.listview_cell, list);
//listView에 아답타 연결하기
binding.listView.setAdapter(adapter);
//UI의 참조값 얻어오기
EditText inputText=findViewById(R.id.inputText);
//버튼의 참조값 얻어오기, 리스너 등록
binding.addBtn.setOnClickListener(v -> {
//EditText에 입력한 문자열을 읽어와서
String content = binding.inputText.getText().toString();
Map<String, String> map=new HashMap<>();
map.put("content", content);
//원격지 웹서버에 post 방식으로 전송하기
Util.sendPostRequest(AppConstants.REQUEST_TODO_INSERT,
AppConstants.BASE_URL+"/todo/insert",
map,
this);
});
//ListView를 길게 누르고 있을 때 리스너 등록
binding.listView.setOnItemLongClickListener(this);
}
@Override
protected void onStart() {
super.onStart();
//MainActivity가 활성화되는 시점에 원격지 서버의 데이터를 받아와서 listView에 출력하기
Util.sendGetRequest(AppConstants.REQUEST_TODO_LIST,
AppConstants.BASE_URL+"/todo/list",
null,
this);
}
@Override
public void onSuccess(int requestId, Map<String, Object> result) {
//응답된 json 문자열 읽어오기
String jsonStr=(String)result.get("data");
//만일 할일 추가 요청에 대한 응답이라면
if(requestId == AppConstants.REQUEST_TODO_INSERT){
//jsonStr 은 {"isSuccess":true} 형식의 json 문자열
Log.d("MainActivity onSuccess()", jsonStr);
//성공이면 목록을 다시 요청해서 UI가 업데이트되도록 한다.
Util.sendGetRequest(AppConstants.REQUEST_TODO_LIST,
AppConstants.BASE_URL+"/todo/list",
null,
this);
}else if(requestId == AppConstants.REQUEST_TODO_LIST){
//기존 목록은 일단 삭제
list.clear();
//jsonStr 은 [{"num":x, "content":"x", "regdate":"x"}, {},{},{},...] 형식의 문자열
try {
JSONArray arr=new JSONArray(jsonStr);
//JSONArray 객체의 방의 개수만큼 반복문 돌면서
for(int i=0; i<arr.length(); i++){
//JSONObject 객체를 하나씩 얻어낸다.
JSONObject tmp=arr.getJSONObject(i);
//JSONObject 에는 할일 하나가 들어있다. => TodoDto로 변경하면 된다.
TodoDto dto=new TodoDto();
dto.setNum(tmp.getInt("num"));
dto.setContent(tmp.getString("content"));
dto.setRegdate(tmp.getString("regdate"));
//TodoDto 객체를 list에 누적시킨다.
list.add(dto);
}
//모두 누적시켰으면 모델이 변경되었다고 아답타에 알려서 ListView가 업데이트 되도록 한다.
adapter.notifyDataSetChanged();
} catch (Exception e) {
Log.e("MainActivity onSuccess()", e.getMessage());
}
}else if(requestId == AppConstants.REQUEST_TODO_DELETE){
//성공이면 목록을 다시 요청해서 UI가 업데이트되도록 한다.
Util.sendGetRequest(AppConstants.REQUEST_TODO_LIST,
AppConstants.BASE_URL+"/todo/list",
null,
this);
}
}
@Override
public void onFail(int requestId, Map<String, Object> result) {
//만일 할일 추가 요청에 대한 실패라면
if(requestId==AppConstants.REQUEST_TODO_INSERT){
}
}
//listView의 cell을 오랫동안 클릭하면 실행되는 메소드
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
new AlertDialog.Builder(this).setTitle("알림")
.setMessage("삭제하시겠습니까?")
.setPositiveButton("네", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
/*
view는 클릭한 cell의 view
position은 클릭한 cell의 인덱스
id는 클릭한 cell 모델의 primary key 값
*/
//삭제할 할일의 primary key 값을 Map에 담고
Map<String, String> map = new HashMap<>();
map.put("num", Long.toString(id));
//Util을 사용해서 삭제
Util.sendPostRequest(AppConstants.REQUEST_TODO_DELETE,
AppConstants.BASE_URL+"/todo/delete",
map,
MainActivity.this);
}
})
.setNegativeButton("아니오", null)
.create()
.show();
return false;
}
}
- 생성자의 인자들을 각각의 자리에 잘 넣어준다.
- context 타입은 Activity 안에서는 activity를 사용하면 된다.
- 현재는 데이터가 없다. 이렇게 출력된다.
- 이 안드로이드 화면에서 값을 입력하면 읽어와서 서버에서 할일을 DB에 저장하도록 만들 것!
package com.example.step17example;
import android.os.Bundle;
import android.widget.ListView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//ListView 의 참조값 얻어오기
ListView listView=findViewById(R.id.listView);
//아답타에 넣어줄 모델 목록
List<TodoDto> list=new ArrayList<>();
//ListView 에 연결할 아답타 객체 생성
TodoAdapter adapter=new TodoAdapter(this, R.layout.listview_cell, list);
//ListView 에 아답타 연결하기
listView.setAdapter(adapter);
}
}
- 처음에 findViewById() 로 작성했던 것인데, 뷰바인딩 형식으로 바꿔볼 것!
- step08의 gradle.build에 가서 buildFeatures 설정을 복사해서 step17example에 갖다붙이고, 동기화해준다!
- 그러면 ViewBinding을 사용할 준비가 된 것이다!
- 다시 MainActivity 로 돌아가보면 ActivityMainBinding이라는 클래스가 하나 생겨있다.
[ view binding 사용하는 방법 ]
1. build.gradle 파일에 아래 설정 추가
buildFeatures {
viewBinding = true
}
2. 우상단의 sync now 링크를 눌러서 설정이 적용되도록 한다.
3. layout xml 문서의 이름대로 클래스가 자동으로 만들어진다.
예를 들어 activity_main.xml 문서면 ActivityMainBinding 클래스
activity_sub.xml 문서면 ActivitySubBinding 클래스
- binding.getRoot() 는 여기서는 리니어 레이아웃을 말한다.
- 이것이 root이다. 이 레이아웃을 전개했을때 최상위에 있는 요소!
- 참조값을 얻어오는 것은 이렇게 쓸 수 있다.
- View binding을 활용하면 아이디 값으로 바로 접근가능하다.
- 아답타를 연결하면서 참조값까지 알아낼 수 있다.
- add 버튼에 리스너 등록하기
- binding 객체를 사용하면 EditText, 버튼의 참조값도 간편하게 접근해서 가져올 수 있다.
- Util의 리스너를 액티비티에 직접 등록해준다.
- 상수로 만들어놓고 사용하면 편하다.
- 경로가 달라지고, 배포 시점이 달라지더라도 이렇게 상수값으로 설정해놓으면 이것만 수정하면 된다.
- 이렇게 상수들만 넣어두는 클래스를 별도로 만들어 관리하기도 한다.
- AppConstants 클래스 생성
package com.example.step17example;
public class AppConstants {
//자주 사용하는 문자열은 static final 상수로 만들어두고 사용하면 편리하다.
public static final String BASE_URL="http://192.168.0.34:9000/boot07/api";
public static final int REQUEST_TODO_INSERT=999;
public static final int REQUEST_TODO_LIST=1000;
public static final int REQUEST_TODO_DELETE=1001;
}
- INSERT 작업을 이렇게 만들어서 넣어준다.
- sendPostRequest() 에 999라고 넣어둔 코드도 상수화하여 넣어준다.
- 적당히 상수로 고쳐 작성하면 코드의 가독성이 좋아진다.
- 이렇게 리스너를 함수 형태로 만들면 이 안에서 this 가 MainActivity 를 가리킬 수 있다.
- 보통은 그냥 Activity.this 라고 써야한다.
- 만약 서버에 데이터가 있다면 onCreate되는 시점에 불러올 수도 있어야 한다!
- 이 불러오는 요청도 static final 상수로 만들어준다.
(이 상수는 관례상 대문자, snake case로만 작성한다.)
- _ 이렇게 언더바로 쭉 연결하는것을 snake case라고 한다.
- onStop에 잠자고 있다가 다시 활성화될 수 있으므로,
onCreate 대신 onStart에 이 작업을 넣어준다.
- 이 과정을 거쳐서 액티비티가 활성화된다.
이 각각의 단계에서 하고싶은 작업이 있으면 해당 단계의 메소드를 오버라이드 하면 된다.
- 홈, back버튼을 누르면 바로 onDestroy로 가는게 아니라 보통 onStop에 머물러 있는다.
- onStop 단계에 있을 때 앱을 재시작하면 onRestart를 거쳐서 다시 onStart로 들어간다.
- 어떤 작업을 처음 시작할 때, 재시작할 때 둘다 적용되게 하고 싶다면 onCreate가 아니라 onStart에서 해야 하는 이유!
- 부모의 메소드(super)를 호출해야 하는지 말아야 하는지는 그때그때 다르다...
- 원격지 서버에 get요청을 보내는 Util 안의 함수 사용하기!
- 이 MainActivity에서 http에 post방식, get방식 요청을 2개 하고 있다.
- 각각의 요청을 요청의 아이디를 통해서 구분할 수 있다.
- onSuccess, onFail 안에서 요청의 id를 사용해서 if~else로 분기해서 응답하기!
STS - Spring Boot 컨트롤러가 필요!
@RestController
- @ResponseBody를 생략할 수 있는 어노테이션이다.
- spring 5.0 부터 추가된 RestController 어노테이션을 붙이면,
json 문자열을 응답할때 @ResponseBody를 생략할 수 있다.
- responsebody가 기본으로 붙어있는 상태. 기본 응답방식이 JSON문자열이라고 생각하면 된다!
- 편하게 JSON으로 응답할 수 있도록 만들어짐
- json이나 xml로 바로 응답하겠다는 뜻이다. (jsp 페이지 포워드이동 X)
- 여기서 응답한 json 문자열을 안드로이드에서 활용한다.
TodoDto
package com.sy.boot07.api;
import org.apache.ibatis.type.Alias;
@Alias("todoDto")
public class TodoDto {
//필드
private int num;
private String content;
private String regdate;
public TodoDto () {}
public TodoDto(int num, String content, String regdate) {
super();
this.num = num;
this.content = content;
this.regdate = regdate;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRegdate() {
return regdate;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
}
TodoDao 인터페이스
package com.sy.boot07.api;
import java.util.List;
import com.sy.boot07.api.TodoDto;
public interface TodoDao {
//추가
public void insert(TodoDto dto);
//리스트
public List<TodoDto> getList();
//삭제
public void delete(int num);
}
TodoDaoImpl
package com.sy.boot07.api;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.sy.boot07.api.TodoDao;
import com.sy.boot07.api.TodoDto;
//dao에 붙여야 하는 어노테이션
@Repository
public class TodoDaoImpl implements TodoDao {
//의존객체 주입(DI) 받기
@Autowired
private SqlSession session;
@Override
public void insert(TodoDto dto) {
/*
* Mapper's namespace => todo
* sql's id => insert
* parameterType => TodoDto
*/
session.insert("todo.insert", dto);
}
@Override
public List<TodoDto> getList() {
/*
* Mapper's namespace => getList
* sql's id => getList
* parameterType => 없음
* resultType => TodoDto
*/
return session.selectList("todo.getList");
}
@Override
public void delete(int num) {
/*
* Mapper's namespace => todo
* sql's id => insert
* parameterType => int (번호에 의한 삭제)
*/
session.delete("todo.delete", num);
}
}
- List<> 제너릭이 바로 result type이 된다.
- mapper 작성하기
- 이렇게 Dto에 typeAlias 설정을 해두면 간단한 코딩이 가능하다.
- 이것을 인식시키기 위해서는 어떤 패키지의 하위에있는 것들을 스캔할 것인지에 대한 설정이 필요하다.
TodoMapper
<?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 namespace="todo">
<insert id="insert" parameterType="todoDto">
INSERT INTO todo
(num, content, regdate)
VALUES(todo_seq.NEXTVAL , #{content}, SYSDATE)
</insert>
<select id="getList" parameterType="todoDto" resultType="todoDto">
SELECT num, content, regdate
FROM todo
ORDER BY num ASC
</select>
<delete id="delete" parameterType="int">
DELETE FROM todo
WHERE num=#{num}
</delete>
</mapper>
- Mapper에서 지정한 이름을 Dao에서 호출하는 메소드에 들어가는 내용과 일치시켜 주어야 하고,
파라미터 타입도 일치시켜야한다.(TodoDto)
- SELECT문의 경우, select된 행을 어디에 저장할지를 정해야 한다.
- 이 select된 row를 어디에 담을지 정하는 것이 resultType 이다
- 이 sql문만 봐서는 지금 행이 몇개인지 알 수 없다.
- 사용자 입장에서 어떤 메소드로 어떻게 사용하느냐에 따라 결과가 달라질 수 있다.
- 메소드의 인자 String, Object 타입으로
- 할일을 content라는 파라미터 명으로 전달한다.
- Map을 content에서 리턴해주면 저런 문자열이 응답된다.
- 이것을 보고 출력될 JSON 문자열을 상상해보기!!
MainActivity 수정
- 이렇게 실행의 흐름이 들어온다.
- DB에서 목록을 받아와서 ListView에 뿌려야 한다.
- 첫번째요청은 List 요청이다
- 여기서 가져온 jsonStr이 json형식이 아닐 경우 exception이 발생한다.
→ try~catch문으로 묶어주기
- 출력되는 글자를 보고 어떤 메소드를 사용할지 정하면 된다.
- dto의 setter 메소드를 사용해서 담아준다.
- 이 dto에 담은 것을 위의 list에 넣어주면 된다.
→ 위의 list를 필드로 선언해야 한다!
- 이 시점에 리스트가 업데이트 된다.
- 아답타 필드도 업데이트해주기
- 이렇게 DB에 있는 목록이 안드로이드 기기에 출력된다.
- 우리가 사용하는 버스 도착정보앱 등도 이런 식으로 json 문자열로 날아온 값을 추출해서 보여주는 것이다.
- 이렇게 parsing으로 필요한 내용만 뽑아내서 UI에 출력하는 것!
- 입력하면 할일(content)을 DB 에 추가하기!
- 서버에 보내서 등록을 하고, 응답이 되면 목록을 새로 요청해야 한다.
- 이런 순서, 구조로 흐름이 진행된다!
- 목록 받아오기 요청을 다시 한다(4번)
- 그런데 그러면 5번 블럭에서 목록을 출력하면서, 기존 목록에 내용이 누적될 수 있다.
- 맨 처음에 기존 목록을 삭제하는 코드가 필요하다!
- else if 블럭 안에 list.clear(); 코드 추가
- 값을 입력하면 이렇게 추가되고, 목록도 갱신된다.
- listView의 셀을 길게 누르고 있을 때 삭제되도록 하는 기능 추가
- LongClickListener 를 구현 (AdapterView 패키지에 들어있다)
- cellView에 변경하고 싶은 것이 있다면 저 view를 사용해도 된다.
- 2번인 '쇼핑' cell을 길게 누른다고 하면, 메소드의 인자에 이렇게 값이 전달된다.
public static final int REQUEST_TODO_DELETE=1001;
- 요청의 종류가 늘어났으므로 새로운 상수를 추가했다.
- 숫자를 문자로 바꾸어서 처리! Map<String,String>이므로 문자로 바꾸어준다.
- 삭제하기 전에 알림을 띄우는 기능 추가
- Dialoginterface 인터페이스가 자동완성된다.
- RequestId 값에 따라 else if 로 분기해서 메소드 추가. 삭제 후 새로운 리스트를 불러올 수 있도록!
- Spring에 이 삭제 작업을 처리할 컨트롤러가 필요하다
- mapper 수정
- Dto를 가져왔을 때는 필드의 이름과 일치시켜야 하지만,
int와 같이 값 하나만 가지고 왔을 때는 #{num} 자리에 num이 아닌 어떤 값을 넣든 상관없다.
- 삭제여부를 묻는 창이 나타나고, 클릭하면 삭제되고 새로운 목록이 다시 나타난다.
'국비교육(22-23)' 카테고리의 다른 글
82일차(1)/Android App(44) : 로그아웃 기능 구현 (0) | 2023.02.06 |
---|---|
80일차(1)/Android App(43) : 로그인 기능 구현 (0) | 2023.02.03 |
78일차(1)/Android App(41) : Http POST방식 요청하기(4) - JSONArray 활용 (1) | 2023.01.30 |
77일차(4)/Android App(40) : Http POST방식 요청하기(3) (0) | 2023.01.30 |
77일차(3)/Android App(39) : Http POST방식 요청하기(2) (0) | 2023.01.30 |