안드로이드&IOS 앱 개발자 양성

안드로이드 MySQL&Hibernate 연동(76 ~ 78일차)

HRuler 2020. 7. 23. 18:20
더보기

** Oracle과 MySQL

** MyBatis와 Hibernate의 차이
1. MyBatis
2. Hibernate

** Spring Maven MVC Project
1. Maven
2. Gradle
3. Jenkins
4. MVC Project

** 작업
1. 테이블 및 샘플 데이터 생성

** Item 테이블 서버 작업을 위한 Spring MVC Project 생성
1. Spring MVC Project 생성
2. pom.xml 파일에서 기본
3. pom.xml 파일에 필요한 의존성 라이브러리를 설정
4. 롬복 설치
5. web.xml 파일에 파라미터 인코딩 설정 필터 추가
6. servlet-context.xml 파일에 파일 업로드와 Controller가 처리하지 못하는 요청은 WAS가 처리하도록 하는 설정 추가
7. Item 테이블과 연동할 DTO 클래스 생성 : 자료형을 정확하게 설정해야 한다.
8. DAO 패키지에 Item 클래스와 Item 테이블을 매핑하는 item.hbm.xml 파일을 생성하고 설정
9. ItemDAO 클래스를 생성하고 필요한 메소드를 생성
10. root-context.xml 파일에 연결할 데이터 베이스 설정과 하이버네이트 설정을 추가
11. servlet-context.xml 파일에 하이버네이트 트랜잭션 사용을 위한 설정을 추가
12. service 패키지에 ItemService 인터페이스를 생성하고 필요한 메소드를 작성
13. service 패키지에 ItemService 인터페이스를 Implements한 ItemServiceImpl 클래스를 만들고 구현
14. 기본 페이지에 Item 테이블의 데이터를 전달하는 Controller 클래스를 생성하고 메소드 작성
15. 서버를 실행하고 테스트

** Item 테이블의 데이터를 사용하는 Android App 생성
1. Android Application 생성
2. 레이아웃 작성
3. res/values에 values.xml 생성 - array.xml
4. MainActivity 작업
5. 안드로이드에서 네트워크 기능을 사용하기
6. MainActivity.java 파일의 onCreate에서 버튼을 클릭했을 때 호출할 이벤트 핸들러를 작성

** 안드로이드 상세보기

** 상세보기 구현
1. Server에 img 디렉토리가 있는지 확인하고 없으면 img 디렉토리를 생성하고 기본 이미지를 삽입
2. 실행가능한 Activity 생성 - itemActivity
3. 레이아웃 파일에 화면 디자인
4. ItemDetailActivity.java에 필요한 코드 작성

** Android에서 데이터 삽입
1. 실행 가능한 Activity 생성 - InsertActivity
2. 화면 디자인 - EditText 3개와 버튼 1개
3. res 디렉토리에 raw 디렉토리를 생성하고 ball.jpg 파일을 배치
4. InsertActivity.java 파일에 코드 작성
5. 서버와 안드로이드 앱을 모두 구동해서 테스트

** 회원관리를 위한 서버와 안드로이드 앱 만들기
1. 데이터를 설계 - 데이터베이스 작업
2. Server 작업
3. Client Application 작업 - Android

** 로그인 작업
1. 실행 가능한 Activity 생성(LoginActivity)
2. 화면 디자인
3. LoginActivity.java 파일에 소스 코드 작성

※ Hibernate 사용법
1. 테이블과 매핑할 DTO 클래스 필요
2. 테이블과 DTO를 매핑할 하이버네이트 설정 파일 필요
3. DataSource와 Hibernate 설정 - root-context.xml에서 설정
4. 트랜잭션 매니저를 설정 : servlet-context.xml
5. DAO 클래스에서 SessionFactory를 주입받아서 사용

※ MyBatis

※ Hibernate

※ DAO, Service, Controller

JSON Parsing

 안드로이드에서 서버와 연동할 때 주의할 점
1. INTERNET 권한 부여
2. http 서버와 연동할 때 별도의 설정 필요
3. Android는 네트워크 사용하는 코드는 반드시 스레드에 작성되어야 한다.
4. Android에서는 메인 스레드를 제외하고는 출력하는 코드를 작성할 수 없다.

HttpURLConnection을 이용한 post 전송

하이버네이트 연동 서비스 생성

 회원 가입

Open API(Application Programming Interface)

HTML Parsing

finance.naver.com의 링크의 문자열을 전부 가져와서 출력
1. Android Application 생성
2. build.gradle 파일에 jsoup 라이브러리 의존성을 추가
3. AndroidManifest.xml 파일에 인터넷 권한을 설정
4. http 프로토콜의 사이트에 접속할 때는 application 태그에 설정을 추가
5. 레이아웃 수정
6. Activity.java 파일 작성

** Oracle과 MySQL

- 공통점은 둘 다 관계형 데이터베이스(테이블의 집합으로 데이터를 표시)이다.

- 사용하는 기업의 규모가 다르다.

   MySQL은 아직까지 대기업, 관공서에서는 거의 사용하지 않고, 중견기업이나 중소기업 또는 호스팅 하는 경우에 많이 사용한다.

- Oracle은 인코딩할 필요가 없지만 MySQL은 인코딩을 하지 않으면 한글이 제대로 입력이 안된다.

 

** MyBatis와 Hibernate의 차이

1. MyBatis

- SQL Mapper Framework

- Java와 SQL 코드의 분리

- 배우기가 쉽기 때문에 SI 분야에서 주로 사용한다.

2. Hibernate
- ORM(Object Relation Mapper) Framework

- Java 객체와 테이블을 하나의 행으로 매핑한다.

- 배우기는 어렵지만 성능이 우수하기 때문에 솔루션 분야에서 주로 사용한다.

 

** Spring Maven MVC Project

1. Maven

- 프로젝트 관리 도구이면서 빌드 도구이다.

- pom.xml 파일을 이용해서 프로젝트를 관리하고 빌드를 수행한다.

2. Gradle

- 빌드 도구

- Android Studio의 빌드 도구가 Gradle이다.

- build.gradle이라는 파일을 이용해서 빌드를 수행한다.

3. Jenkins

- 빌드 도구

- 현업에서 가장 많이 사용하는 빌드 도구이다.

4. MVC Project

- 기본적인 웹 설정이 되어있는 프로젝트

 

** 작업

Item 테이블의 검색 및 페이징, 상세보기, 데이터 삽입

Member 테이블의 데이터 삽입, 로그인, 로그아웃, 정보 수정, 탈퇴

- 삽입, 로그인, 수정, 탈퇴는 POST 방식으로 처리

MySQL 접속 정보

192.168.0.76 - 3306

user00 ~ user20 : 데이터베이스 이름, 아이디, 비번

1. 테이블 및 샘플 데이터 생성

** Item 테이블 서버 작업을 위한 Spring MVC Project 생성

- 작업할 것 : 아이템 테이블에 검색어를 이용해서 검색한 결과를 출력하는 작업

                     아이템 테이블에 기본키를 이용해서 하나의 데이터를 출력하는 작업

                     아이템 테이블에 데이터를 삽입하는 작업

1. Spring MVC Project 생성

- MySQLHibernate(프로젝트 이름) : kakao.gjs5515.mysqlserver(패키지 이름)

- 회원은 가입, 로그인, 정보 수정, 탈퇴

2. pom.xml 파일에서 기본 라이브러리들의 버전 변경

- java, spring, junit

- 설정 후 프로젝트 선택하고 마우스 오른쪽을 클릭해서 [Maven] - [Updata Project] 선택

3. pom.xml 파일에 필요한 의존성 라이브러리를 설정

1) 오라클을 위한 repositories를 추가 - 오라클 서버 사용 시 설정

2) dependencies에 필요한 의존성 추가

3) pom.xml 파일을 저장하고 실행

4. 롬복 설치

- 터미널에 입력

   java -jar 파일 경로

5. web.xml 파일에 파라미터 인코딩 설정 필터 추가

- 저장하고 재실행

6. servlet-context.xml 파일에 파일 업로드와 Controller가 처리하지 못하는 요청은 WAS가 처리하도록 하는 설정 추가

7. Item 테이블과 연동할 DTO 클래스 생성 : 자료형을 정확하게 설정해야 함

- package : kakao.gjs5515.mysqlserver.domain;

8. DAO 패키지에 Item 클래스와 Item 테이블을 매핑하는 item.hbm.xml 파일을 생성하고 설정 

9. ItemDAO 클래스를 생성하고 필요한 메소드를 생성

10. root-context.xml 파일에 연결할 데이터 베이스 설정과 하이버네이트 설정을 추가

11. servlet-context.xml 파일에 하이버네이트 트랜잭션 사용을 위한 설정을 추가

- 하이버네이트는 트랜잭션 사용이 필수

- 트랜잭션의 적용은 Service

1) servlet-context.xml 파일에 tx 네임스페이스를 추가

2) bean을 설정

12. service 패키지에 ItemService 인터페이스를 생성하고 필요한 메소드를 작성

13. Service 패키지에 ItemService 인터페이스를 Implements한 ItemServiceImpl 클래스를 만들고 메소드 구현

14. 기본 페이지에 Item 테이블의 데이터를 전달하는 Controller 클래스를 생성하고 메소드 작성

15. 서버를 실행하고 테스트

 

** Item 테이블의 데이터를 사용하는 Android App 생성

1. Android Application을 생성

2. 레이아웃 작성

3. res/values에 values.xml 생성 - array.xml

4. MainActivity 작업

1) 필요한 인스턴스 변수 작업

 

2) onCreate에 View를 전부 찾아오기

3) 다운로드 받은 결과를 화면에 출력하기 위한 핸들러를 생성

4) 데이터를 다운로드 받아서 저장할 스레드 클래스 생성

5) Activity가 활성화될 때마다 호출되는 메소드를 재정의

5. 안드로이드에서 네트워크 기능을 사용하기

1) AndroidManifest.xml 파일에서 INTERNET 권한을 부여

2) 접속할 곳의 URL이 http이면 application 태그에 onCleartextTraffic="true"를 추가해야 한다.

6. MainActivity.java 파일의 onCreate에서 버튼을 클릭했을 때 호출할 이벤트 핸들러를 작성

** 안드로이드 상세보기

- itemid를 넘겨받아서 detail에 요청을 전송해서 상세보기를 구현(itemid를 1로 가정)

- image도 다운로드 받아서 구현

- 웹에서 텍스트와 이미지를 다운로드 받아서 출력하는 경우에 하나의 스레드에서 구현할 수 있지만 대부분의 경우는 일단 텍스트를 먼저 다운로드 받아서 출력하고 이미지는 별도의 스레드에서 다운로드 받아서 출력하는 것을 권장한다.

 

** 상세보기 구현

1. Server에 img 디렉토리가 있는지 확인하고 없으면 img 디렉토리를 생성하고 기본 이미지를 삽입

2. 실행가능한 Activity 생성 - itemActivity

3. 레이아웃 파일에 화면 디자인

- 상단에 itemname을 출력

   설명 : description을 출력

   가격 : price를 출력

   pictureurl에 해당하는 이미지를 출력

4. ItemDetailActivity.jave에 필요한 코드 작성

** Android에서 데이터 삽입

- itemname, description, price를 파라미터로 전송해야 하고 pictureurl이 이미지 파일이다.

1. 실행 가능한 Activity 생성 - InsertActivity

2. 화면 디자인 - EditText 3개와 버튼 1개

3. res 디렉토리에 raw 디렉토리를 생성하고 ball.jpg 파일을 배치

4. InsertActivity.java 파일에 코드 작성

5. 서버와 안드로이드 앱을 모두 구동해서 테스트

1) 안드로이드 화면에 삽입 성공이 출력되는지 확인

2) 데이터베이스에서 확인

3) 서버 프로젝트를 저장한 디렉토리에서 .metadata 디렉토리의 .plugins/org.eclipse.wst.server.core/tmp0(tmp 다른숫자일 수 있음)/wtpwepapps/프로젝트이름/업로드되는디렉토리이름/ 을 확인해서 파일이 업로드됐는지 확인

 

** 회원관리를 위한 서버와 안드로이드 앱 만들기

- 회원 정보

   email(primary key - 복호화가 가능한 암호화를 이용해서 저장)

   nickname(unique - 로그인 할 때 아이디로 사용)

   pw(not null - 복호화는 불가능하고 비교만 가능한 형태의 암호화를 이용해서 저장)   

   profile(이미지 파일의 경로를 저장 - 기본 이미지 이름은 default.jpg)

- 회원가입, 로그인, 회원정보 수정, 회원 탈퇴 4가지 작업 구현

1. 데이터를 설계 - 데이터베이스 작업

2. Server 작업

1) 암호화 작업을 위한 준비

- pom.xml 파일에 아래 dependency가 설정되었는지 확인

- 복호화가 가능한 암호화를 위한 클래스를 프로젝트에 삽입

2) webapp 디렉토리에 파일을 저장할 디렉토리를 생성

- profile 디렉토리를 생성

   서버에서 데이터를 제공하는 디렉토리를 만들 때는 텍스트 파일을 만들어서 마지막 업데이트한 날짜를 저장

3) 유저의 기본 이미지로 사용할 default.jpg  파일을 pofile 디렉토리에 복사

4) Member 테이블과 연동할 클래스를 domain 패키지에 생성

5) Member 테이블과 Member 클래스를 매핑하는 파일을 dao 패키지에 생성 :  member.hbm.xml

6) 매핑 파일을 등록 - root-context.xml 파일에 추가 : web.xml 파일의 listener 태그에 context-param 태그에 설정된 파일에 작성(웹 애플리케이션이 시작될 때 사용할 bean의 설정 파일 경로

7) MemberDAO 클래스 구현

8) MemberService 인터페이스를 만들고 필요한 메소드를 선언

- 파라미터 모양이나 리턴 타입이 여러가지 형태

   파라미터는 클라이언트로부터 전송받은 데이터를 읽기 위한 것

   리턴 타입은 클라이언트에게 작업의 결과를 넘겨주기 위한 것

   파라미터는 HttpServletRequest(파일 업로드가 없는 경우) MultipartHttpServletRequest(파일 업로드가 있는 경우)를 이용

   리턴 타입은 전부 void로 하고 데이터는 request attribute를 이용해서 저장

- 실무에서는 필요한 것만 파라미터로 받고 필요한 데이터만 리턴하는 형태로 작업합니다.

- 서비스의 메소드는 클라이언트의 요청 개수만큼 생성 

9) MemberServiceImpl 클래스를 만들고 메소드를 구현

package kakao.gjs5515.mysqlserver.service;



import java.io.FileOutputStream;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.UUID;



import javax.servlet.http.HttpServletRequest;



import org.mindrot.jbcrypt.BCrypt;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

import org.springframework.web.multipart.MultipartFile;

import org.springframework.web.multipart.MultipartHttpServletRequest;



import kakao.gjs5515.mysqlserver.dao.MemberDAO;

import kakao.gjs5515.mysqlserver.domain.Member;

import kakao.gjs5515.mysqlserver.util.CryptoUtil;



@Service

public class MemberServiceImpl implements MemberService {

@Autowired

private MemberDAO memberDao;



// 하이버네이트는 트랜잭션을 사용하지 않으면 예외가 발생

@Override

@Transactional

public void join(MultipartHttpServletRequest request) {

// 파라미터 읽기

String email = request.getParameter("email");

String nickname = request.getParameter("nicknam");

String pw = request.getParameter("pw");

MultipartFile image = request.getFile("profile");



System.out.println("email:" + email);



// 결과를 저장할 Map을 생성

Map<String, Object> map = new HashMap<String, Object>();

// result는 회원가입 성공여부

// emailcheck에는 이메일 중복 검사 통과 여부

// nicknamecheck에는 닉네임 중복 검사 통과 여

map.put("result", false);

map.put("emailcheck", true);

map.put("nicknamecheck", true);



String key = "itggangpae";

// email 중복 검사

// 복호화가 가능한 암호화로 되어 있음

List<String> emaillist = memberDao.emailcheck();

for (String temp : emaillist) {

try {

// 복호화 해서 비교

if (CryptoUtil.decryptAES256(temp, key).equals(email)) {

map.put("emailcheck", false);

request.setAttribute("result", map);

return;

}

} catch (Exception e) {

}

}



// nickname 중복검사

List<String> nicknamelist = memberDao.nicknamecheck(nickname);

if (nicknamelist != null && nicknamelist.size() > 0) {

map.put("nicknamecheck", false);

request.setAttribute("result", map);

return;

}



// 파일 업로드

String profile = "default.jpg";

// 이미지가 존재하면 업로드

if (image != null && image.isEmpty() == false) {

// 업로드할 디렉토리 경로를 생성

String filePath = request.getRealPath("/profile");

// 파일이름 생성

profile = UUID.randomUUID() + image.getOriginalFilename();

// 업로드할 파일 경로를 생성

filePath = filePath + "/" + profile;



// 파일 업로드

FileOutputStream fos = null;

try {

fos = new FileOutputStream(filePath);

fos.write(image.getBytes());

} catch (Exception e) {

System.out.println("파일 업로드 실패:" + e.getMessage());

}

}

// Dao의 파라미터 만들기

Member member = new Member();

try {

member.setEmail(CryptoUtil.encryptAES256(email, key));

} catch (Exception e) {

}

member.setNickname(nickname);

member.setPw(BCrypt.hashpw(pw, BCrypt.gensalt()));

member.setProfile(profile);



// 회원가입 메소드 호출

memberDao.join(member);

map.put("result", true);

request.setAttribute("result", map);

// map에서 result가 회원가입 성공여부

// emailcheck는 이메일 중복 검사 통과 여부

// nicknamecheck는 닉네임 중복 검사 통과 여부



}



@Override

@Transactional

public void login(HttpServletRequest request) {

//파라미터 읽기

String nickname = 

request.getParameter("nickname");

String pw = request.getParameter("pw");

//로그인 성공 여부를 저장 

Map<String, Object> map = 

new HashMap<String, Object>();

map.put("result", false);



//nickname을 가지고 데이터를 찾아오기

List<Member> list = memberDao.login(nickname);

//nickname에 해당하는 데이터가 없다면 더이상 진행 할 필요가 없음 

if(list == null || list.size() < 1) {

request.setAttribute("result", map);

return;

}



//찾아온 데이터와 비밀번호를 비교

for(Member member : list) {

if(BCrypt.checkpw(pw, member.getPw())) {

map.put("nickname", nickname);

map.put("profile", member.getProfile());

try {

map.put("email",

CryptoUtil.decryptAES256(

member.getEmail(),

"itggangpae"));

}catch(Exception e) {}

map.put("result", true);

request.setAttribute("result", map);

return;

}

}



}



@Override

@Transactional

//Hibernate는 기본키를 조건으로 해서 수정이나 삭제를 수행 

public void update(MultipartHttpServletRequest request) {

//필요한 파라미터 읽기

String nickname = request.getParameter("nickname");

String pw = request.getParameter("pw");

//이미지는 반드시 입력되는 항목이 아니라면 

//이전값을 파라미터로 보내고 새로운 값이 null이 아니라면

//새로운 값으로 대체 

String profile = 

request.getParameter("oldprofile");

//email 찾아오기 

List<Member>list = 

memberDao.login(nickname);

String email = list.get(0).getEmail();



//이미지 파일 업로드

MultipartFile image = 

request.getFile("profile");

if(image != null && image.isEmpty() == false) {

//업로드할 파일 경로 생성

String filePath = 

request.getRealPath("/profile");

profile = UUID.randomUUID() + 

image.getOriginalFilename();

filePath = filePath + "/" + profile;

//파일 업로드

try {

FileOutputStream fos =

new FileOutputStream(filePath);

fos.write(image.getBytes());

}catch(Exception e) {

System.out.println(

"파일 업로드 예외:" + e.getMessage());

}



}



Map<String, Object>map = 

new HashMap<String, Object>();

map.put("result", false);



//DAO 파라미터 만들기

Member member = new Member();

member.setEmail(email);

member.setPw(pw);

member.setNickname(nickname);

member.setProfile(profile);



memberDao.update(member);



map.put("result", true);



request.setAttribute("result", map);





}



@Override

@Transactional

public void delete(HttpServletRequest request) {

String nickname = 

request.getParameter("nickname");

List<Member>list = memberDao.login(nickname);

String email = list.get(0).getEmail();



Member member = new Member();

member.setEmail(email);



Map<String, Object> map = new HashMap<>();

map.put("result", false);

memberDao.delete(member);

map.put("result", true);

request.setAttribute("result", map);

}



}

10) MemberDataController를 만들고 메소드를 작성

package kakao.gjs5515.mysqlserver;



import java.util.Map;



import javax.servlet.http.HttpServletRequest;



import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.multipart.MultipartHttpServletRequest;



import kakao.gjs5515.mysqlserver.service.MemberService;



@RestController

public class MemberDataController {

@Autowired

private MemberService memberService;





@RequestMapping(value="join", 

method=RequestMethod.POST)

public Map<String, Object> join(

MultipartHttpServletRequest request){

memberService.join(request);

Map<String, Object> map = (Map<String, Object>)

request.getAttribute("result");

return map;

}



@RequestMapping(value="login", 

method=RequestMethod.POST)

public Map<String, Object> login(

HttpServletRequest request){

memberService.login(request);

Map<String, Object> map = (Map<String, Object>)

request.getAttribute("result");

return map;

}



@RequestMapping(value="update", 

method=RequestMethod.POST)

public Map<String, Object> update(

MultipartHttpServletRequest request){

memberService.update(request);

Map<String, Object> map = (Map<String, Object>)

request.getAttribute("result");

return map;

}



@RequestMapping(value="delete", 

method=RequestMethod.POST)

public Map<String, Object> delete(

HttpServletRequest request){

memberService.delete(request);

Map<String, Object> map = (Map<String, Object>)

request.getAttribute("result");

return map;

}





}

3. Client Application 작업 - Android

1) 회원 가입 작업

- 샘플 이미지 삽입 - ball.jpg

- 실행 가능한 Activity 추가 - MemberJoinActivity

- Activity.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=".LoginActivity"
    android:orientation="vertical">
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/nicknameInput"
        android:hint="닉네임을 입력하세요"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pwInput"
        android:inputType="textPassword"
        android:hint="비밀번호를 입력하세요"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="로그인"
            android:id="@+id/btnlogin"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="회원가입"
            android:id="@+id/btnjoin"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="메인으로"
            android:id="@+id/btnmain"/>
    </LinearLayout>
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/imgprofile"/>

</LinearLayout>

- Acitivity.java 파일에 코드 작성

package com.example.androidmysqlhibernate;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MemberJoinActivity extends AppCompatActivity {
    EditText emailInput, pwInput, nicknameInput;
    Button btnJoin, btnLogin, btnMain;

    class ThreadEx extends Thread{
        //다운로드 받은 문자열을 저장할 객체
        StringBuilder sb = new StringBuilder();
        @Override
        public void run() {
            //서버와 연동 작업
            try{
                URL url = new URL("http://192.168.0.84:8080/mysqlserver/join");
                HttpURLConnection con = (HttpURLConnection)url.openConnection();
                con.setRequestMethod("POST");
                con.setUseCaches(false);
                con.setConnectTimeout(30000);
                con.setDoOutput(true);
                con.setDoInput(true);
                //파일 전송을 위한 설정
                String boundary = UUID.randomUUID().toString();     //실행 시마다 다른 문자열을 주기 위해서
                con.setRequestProperty("ENCTYPE", "multipart/form-data");
                con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
                //파라미터 만들기
                String lineEnd = "\r\n";
                String [] data = {emailInput.getText().toString().trim(), nicknameInput.getText().toString().trim(), pwInput.getText().toString().trim()};
                String [] dataNames = {"email", "nickname", "pw"};
                //파라미터 전송
                String delimiter = "--" + boundary + lineEnd;
                StringBuffer postDataBuilder = new StringBuffer();
                for(int i = 0; i < data.length; i = i + 1){
                    postDataBuilder.append(delimiter);
                    postDataBuilder.append("Content-Disposition : " + "form-data; name=\"" + dataNames[i] + "\"" +
                            lineEnd + lineEnd + data[i] + lineEnd);
                }
                //파일 파라미터 만들기
                String fileName = "ball.jpg";
                if(fileName != null){
                    postDataBuilder.append(delimiter);
                    postDataBuilder.append("Content-Disposition : " + "form-data; name=\"profile" + "\"filename=\"" +
                            fileName + "\"" + lineEnd);
                }
                DataOutputStream ds = new DataOutputStream(con.getOutputStream());
                ds.write(postDataBuilder.toString().getBytes());
                //파일 전송
                if(fileName != null){
                    ds.writeBytes(lineEnd);
                    InputStream fres = getResources().openRawResource(R.raw.ball);
                    byte [] buffers = new byte[fres.available()];
                    int length = -1;
                    while((length = fres.read(buffers)) != -1){
                        ds.write(buffers, 0, length);
                    }
                    ds.writeBytes(lineEnd);
                    ds.writeBytes(lineEnd);
                    ds.writeBytes("--" + boundary + "--" + lineEnd);
                    fres.close();
                }else{
                    ds.writeBytes(lineEnd);
                    ds.writeBytes("--" + boundary + "--" + lineEnd);
                }
                ds.flush();
                ds.close();

                //결과 가져오기
                BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
                while (true){
                    String line = br.readLine();
                    if(line == null){
                        break;
                    }
                    sb.append(line + "\n");
                }
                br.close();
                con.disconnect();
                Log.e("다운로드 받은 문자열", sb.toString());
            }catch(Exception e){
                Log.e("서버 연동 실패", e.getMessage());
            }
            //데이터 파싱
            Map<String, Object> map = new HashMap<>();
            try{
                JSONObject object = new JSONObject(sb.toString());
                boolean result = object.getBoolean("result");
                boolean emailcheck = object.getBoolean("emailcheck");
                boolean nicknamecheck = object.getBoolean("nicknamecheck");
                map.put("result", result);
                map.put("emailcheck", emailcheck);
                map.put("nicknamecheck", nicknamecheck);
            }catch(Exception e){
                Log.e("파싱 예외", e.getMessage());
            }
            Message message = new Message();
            message.obj = map;
            handler.sendMessage(message);
        }
    }

    Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message message) {
            //서버와 연동한 결과를 가지고 다음 작업을 수행
            Map<String, Object> map = (Map<String, Object>)message.obj;
            boolean result = (Boolean)map.get("result");
            if(result){
                Toast.makeText(MemberJoinActivity.this, "회원가입 성공", Toast.LENGTH_LONG).show();
            }else{
                boolean emailcheck = (Boolean)map.get("emailcheck");
                if(emailcheck == false){
                    Toast.makeText(MemberJoinActivity.this, "이메일 중복", Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(MemberJoinActivity.this, "닉네임 중복", Toast.LENGTH_LONG).show();
                }
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_member_join);
        emailInput = (EditText)findViewById(R.id.emailInput);
        pwInput = (EditText)findViewById(R.id.pwInput);
        nicknameInput = (EditText)findViewById(R.id.nicknameInput);
        btnJoin = (Button)findViewById(R.id.btnJoin);
        btnLogin = (Button)findViewById(R.id.btnLogin);
        btnMain = (Button)findViewById(R.id.btnMain);

        btnJoin.setOnClickListener(new Button.OnClickListener(){
            @Override
            public void onClick(View view) {
                //데이터 유효성 검사
                boolean result = validationCheck();
                new ThreadEx().start();
            }
        });
    }

    //유효성 검사해주는 메소드
    private boolean validationCheck(){
        boolean result = false;
        //입력한 내용 가져오기
        String email = emailInput.getText().toString().trim();
        String pw = pwInput.getText().toString().trim();
        String nickname = nicknameInput.getText().toString().trim();

        //필수 입력 체크와 정규식을 체크
        String msg = null;
        if(email.length() < 1){
            msg = "email은 필수 입력입니다.";
        }else{
            //정규식 객체 생성
            String regex = "^[_a-z0-9-]+(.[_a-z0-9-]+)*@(?:\\w+\\.)+\\w+$";
            //정규식 적용
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(email);
            //정규식 패턴과 일치하지 않으면
            if(m.matches() == false){
                msg = "email 형식이 일치하지 않습니다.";
            }
        }

        if(pw.length() < 1){
            msg = "비밀번호는 필수 입력입니다.";
        }else{
            //정규식 객체 생성
            String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%&])[A-Za-z\\d!@#$%&]{8,}";
            //정규식 적용
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(pw);
            //정규식 패턴과 일치하지 않으면
            if(m.matches() == false){
                msg = "비밀번호는 영문 대소문자 1개 숫자 1개 특수문자 1개 이상의 조합으로 만들어져야 합니다.";
            }
        }

        if(nickname.length() < 2){
            msg = "별명은 2자 이상이어야 합니다.";
        }else{
            //정규식 객체 생성
            String regex = "[0-9]|[a-z]|[A-Z]|[가-힣]{2,}";
            for(int i = 0; i < nickname.length(); i = i + 1){
                String ch = nickname.charAt(i) + "";
                //정규식 적용
                Pattern p = Pattern.compile(regex);
                Matcher m = p.matcher(ch);
                //정규식 패턴과 일치하지 않으면
                if(m.matches() == false){
                    msg = "별명은 숫자 그리고 영문자와 한글만 가능합니다.";
                    break;
                }
            }
        }
        if(msg == null){
            result = true;
        }else{
            Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
        }
        return result;
    }
}

 

** 로그인 작업

1. 실행 가능한 Activity 생성(LoginActivity)

2. 화면 디자인

<?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=".LoginActivity"
    android:orientation="vertical">
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/nicknameInput"
        android:hint="닉네임을 입력하세요"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pwInput"
        android:inputType="textPassword"
        android:hint="비밀번호를 입력하세요"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="로그인"
            android:id="@+id/btnlogin"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="회원가입"
            android:id="@+id/btnjoin"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="메인으로"
            android:id="@+id/btnmain"/>
    </LinearLayout>

</LinearLayout>

3. LoginActivity.java 파일에 소스 코드 작성

package com.example.androidmysqlhibernate;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.Buffer;
import java.util.HashMap;
import java.util.Map;

public class LoginActivity extends AppCompatActivity {
    EditText nicknameinput, pwinput;
    Button btnlogin, btnjoin, btnmain;
    ImageView imgProfile;

    class ThreadEx extends Thread{
        //다운로드 받을 문자열을 저장할 변수 생성
        StringBuilder sb = new StringBuilder();
        @Override
        public void run() {
            try{
                URL url = new URL("http://192.168.0.84:8080/mysqlserver/login");
                HttpURLConnection con = (HttpURLConnection)url.openConnection();
                con.setDoInput(true);
                con.setDoOutput(true);
                con.setUseCaches(false);
                con.setConnectTimeout(30000);

                //POST 설정
                con.setRequestMethod("POST");

                //파라미터 만들기
                String parameter = URLEncoder.encode("nickname", "UTF-8") + "=" + URLEncoder.
                        encode(nicknameinput.getText().toString().trim(), "UTF-8") + "&" + URLEncoder.encode
                        ("pw", "UTF-8") + "=" + URLEncoder.encode(pwinput.getText().toString().trim());
                //파라미터 전송
                OutputStreamWriter os = new OutputStreamWriter(con.getOutputStream());
                os.write(parameter);
                os.flush();

                //결과 가져오기
                BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
                while(true){
                    String line = br.readLine();
                    if(line == null){
                        break;
                    }
                    sb.append(line + "\n");
                }
                br.close();
                con.disconnect();
                Log.e("다운로드 받은 문자열", sb.toString());
            }catch(Exception e){
                Log.e("서버 연동 실패", e.getMessage());
            }

            //파싱 작업
            try{
                Map<String, Object> map = new HashMap<>();
                JSONObject object = new JSONObject(sb.toString());
                map.put("result", (Boolean)object.getBoolean("result"));
                //로그인이 성공한 경우에만 나머지 데이터를 읽어온다.
                if((Boolean)object.getBoolean("result") == true) {
                    map.put("nickname", (String) object.getString("nickname"));
                    map.put("profile", (String) object.getString("profile"));
                    map.put("email", (String) object.getString("email"));
                }
                Message message = new Message();
                message.obj = map;
                handler.sendMessage(message);
            }catch(Exception e){
                Log.e("파싱 실패", e.getMessage());
            }
        }
    }

    Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message message) {
            Map<String, Object> map = (Map<String, Object>)message.obj;
            boolean result = (Boolean)map.get("result");
            if(result == true){
                Toast.makeText(LoginActivity.this, "로그인 성공", Toast.LENGTH_LONG).show();
                //회원정보를 파일에 저장
                String email = (String)map.get("email");
                String nickname = (String)map.get("nickname");
                String profile = (String)map.get("profile");
                //파일에 저장할 문자열 생성
                String str = email + ":" + nickname + ":" + profile;
                try {
                    FileOutputStream fos = openFileOutput("login.txt", Context.MODE_PRIVATE);
                    fos.write(str.getBytes());
                    fos.flush();
                    fos.close();
                    //로그아웃이나 로그인 실패했을 때는 delete("login.txt")를 호출
                    //login.txt가 존재하면 로그인된 상태이고 존재하지 않으면 로그인이 안된 상태가 된다.
                }catch (Exception e){
                    Log.e("파일 저장 예외", e.getMessage());
                }
                //이미지 다운받는 스레드 생성
                new ImageThread(profile).start();
            }else{
                Toast.makeText(LoginActivity.this, "로그인 실패", Toast.LENGTH_LONG).show();
            }
        }
    };
    //이미지 파일을 다운받는 스레드
    class ImageThread extends Thread{
        String profile;
        public ImageThread(String profile){
            this.profile = profile;
        }
        @Override
        public void run() {
            try{

                //파일 다운받을 스트림 생성
                InputStream is = new URL("http://192.168.0.84:8080/mysqlserver/profile/" + profile).openStream();
                /*
                //파일로 저장
                FileOutputStream fos = openFileOutput(profile, Context.MODE_PRIVATE);
                while(true){
                    byte [] b = new byte[1024];
                    int length = is.read(b);
                    if(length <= 0){
                        break;
                    }
                    fos.write(b, 0, length);
                    fos.flush();
                }
                fos.close();
                is.close();
                */
                Bitmap bit = BitmapFactory.decodeStream(is);
                is.close();
                Message message = new Message();
                message.obj = bit;
                imageHandler.sendMessage(message);
            }catch (Exception e){
                Log.e("이미지 다운 실패", e.getMessage());
            }
        }
    }

    Handler imageHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message message) {
            Bitmap bit = (Bitmap)message.obj;
            imgProfile.setImageBitmap(bit);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        nicknameinput = (EditText)findViewById(R.id.nicknameInput);
        pwinput = (EditText)findViewById(R.id.pwInput);
        btnlogin = (Button)findViewById(R.id.btnlogin);
        btnjoin = (Button)findViewById(R.id.btnjoin);
        btnmain = (Button)findViewById(R.id.btnmain);
        imgProfile = (ImageView)findViewById(R.id.imgprofile);

        btnlogin.setOnClickListener(new Button.OnClickListener(){
            @Override
            public void onClick(View view) {
                //유효성 검사 수행

                //서버에 요청
                new ThreadEx().start();
            }
        });

    }
}

 

 

※ Hibernate 사용법

1. 테이블과 매핑할 DTO 클래스 필요

2. 테이블과 DTO를 매핑할 하이버네이트 설정 파일 필요

- DTO와 테이블을 매핑하고 기본키와 ID를 매핑하고 프로퍼티로 나머지 컬럼을 매핑

3. DataSource와 Hibernate 설정 - root-context.xml에서 설정

4. 트랜잭션 매니저를 설정 : servlet-context.xml

5. DAO 클래스에서 SessionFactory를 주입받아서 사용

 

※ MyBatis

- xml을 이용한 SQL

- 인터페이스를 이용한 SQL

- Oracle은 접속하는 계정별로 별도의 영역을 할당해서 데이터베이스를 사용한다

   MySQL은 데이터베이스를 이용한다.

   계정이 다르더라도 동일한 데이터베이스에 만들면 이전 내용이 소멸될 수 있다.

 

Hibernate

- Hibernate가 제공하는 메소드를 이용한 작업

- HQL이라고 하이버네이트에서 제공하는 쿼리를 이용하는 작업

- Nativer Query라는 전통적 SQL을 이용해서 작업

   Nativer Query를 추천하지 않는 가장 큰 이유는 SQL은 절차적 프로그래밍 언어가 아니고 데이터베이스 종류마다 다르기 때문이다.

   개발자들이 SQL을 선호하는 이유는 SQL에 익숙하기 때문에 SQL을 사용하는 것에 익숙하다.

 

※ DAO, Service, Controller

- 사용자의 요청이 1개이면 Service의 메소드는 1개이다.

   매칭이 되는 DAO나 Controller의 메소드는 1개 이상이다.

   웹 프로그래밍이 아니면 Controller도 1개이다.

   웹 프로그래밍에서 ajax를 사용하지 않는 경우에는 redirect하게되면 2개의 메소드를 호출한다.

- 검색 항목과 검색어 그리고 페이지 번호를 입력받아서 데이터를 조회하는 작업

   List<Item> ? (Map 또는 DTO)

- 하나의 itemid를 가지고 데이터를 찾아오는 작업

   primary key 또는 unique는 아닌지 : 0개 또는 1개

   0개 이상

   Item ? (int itemid)

- 가장 큰 itemid를 찾아오는 메소드

   데이터가 1개도 없을 때는 어떻게 할지 ->ㄱ 데이터 개수를 확인해서 데이터가 없을 때는 1로 설정

- 데이터 삽입

   기본키나 unique가 있는 경우 어떻게 값을 설정할 것인지 고민해봐야 한다.

   void ? (DTO 또는 Map)

   기본키나 unique를 직접 입력받는 경우 : 중복 검사를 수행해야 한다.

   기본키나 unique를 자동 생성하는 경우

     - 데이터베이스의 auto_increment나 sequence를 이용하거나 가장 큰 번호를 찾아서 1을 증가시킨다.

     - 직접 코드를 생성(권장) : 분류나 조회를 빠르게 하기 위해서

     - 기본키와 unique는 자동으로 인덱스를 생성, 인덱스가 설정되면 빠르게 조회된다.

- 전체 데이터가 아니라 일부분의 데이터 단위로 분할해서 조회하는 경우 전체 데이터의 개수를 같이 전송해주어야 한다.

 

※ JSON Parsing

- JSON : 자바 스크립트 데이터 표현방식으로 데이터를 표현

- [] : Array, {} : Map

   Array(List) : 인덱스를 이용해서 데이터를 조회 - 길이를 알아야 한다.

   Map : Key(Attribute)를 이용해서 데이터를 조회

- Java에서는 Parsing해주는 라이브러리를 추가해서 사용한다.

- Android에서는 별도의 라이브러리 없이 파싱이 가능하다.(라이브러리가 내장됨)

- JavaScript에서도 별도의 라이브러리 추가 없이 파싱이 가능하다.

 

※ 안드로이드에서 서버와 연동할 때 주의할 점

1. INTENET 권한 부여

2. http 서버와 연동할 때 별도의 설정 필요

3. Android는 네트워크 사용하는 코드는 반드시 스레드에 작성되어야 한다.

4. Android에서는 메인 스레드를 제외하고는 출력하는 코드를 작성할 수 없다.

- 스레드를 사용해서 데이터를 다운로드 받은 후 UI를 갱신할 때는 Handler를 이용한다.

- Thread와 Handler로 나누어서 작성하는 것이 싫으면 AsyncTask를 사용하면 된다.

   AsyncTask는 메소드로 Thread의 기능과 Handler의 기능을 분할해 놓은 클래스이다.

 

 HttpURLConnection을 이용한 post 전송

- 전송하는 방법이 어려워서 별도의 라이브러리를 다운받아 사용하기도 한다.

- 다른 라이브러리를 사용하는 것은 라이브러리에 종속이 되고 스마트폰 프로그래밍에서는 조심해야 한다.

- 스마트 폰 프로그래밍은 마켓에서 심사를 한다.

   라이브러리가 위험한 코드를 내포하고 있으면 reject가 된다.

- 파일을 전송할 때는 ENCTYPE을 multipart/form-data로 설정해야 하고 Content-Type도 multipart/form-data 그리고 boundary까지 설정해야 한다.

- 직접 입력을 받아 서버에 전송할 때는 데이터의 유효성을 검사해서 유효성을 통과한 경우에만 전송한다.

   유효성 검사는 정규식을 이용하는 경우가 많다.

 

하이버네이트 연동 서비스 생성

- DTO - 테이블과 연동할 클래스

- him.xml - DTO와 테이블을 매핑하는 파일

- DAO

- Service, ServiceImpl

- RestController

- Controller, View : 웹 애플리케이션을 만들 때

 

 회원 가입

- email, nickname 중복 검사

   email은 암호화해서 저장할 거라서 데이터베이스 직접 비교가 안된다.

   데이터를 암호화할 때 동일한 데이터라도 결과는 달라지게 된다.

   email은 전부 가져와서 복호화하면서 동일한지 비교한다.

   nickname은 암호화가 안되어 있으므로 데이터베이스에 nickname이 있는지 조회하면 된다.

   회원가입해주는 메소드 - insert

 

※ 로그인

- 웹 프로그래밍의 경우는 로그인에 성공하면 로그인한 정보를 세션에 저장한다.

- 모바인 프로그래밍의 경우는 파일이난 데이터베이스에 저장한다.

- 로그인 여부를 확인할 때는 파일이나 데이터베이스를 확인하면 된다.

- 웹 프로그래밍에소는 로그아웃을 구현할 때 세션을 초기화하면 된다.

- 모바일 프로그래밍에서는 파일이나 데이터베이스를 초기화하면 된다.

 

※ Open API(Application Programming Interface)

- 응용 프로그램을 만들 수 있도록 제공되는 데이터 또는 라이브러리이다.

- Open API로 제공되는 데이터 포맷으로 가장 많이 사용되는 것이 JSON, XML 이다.

 

HTML Parsing

- 웹 사이트 만들어져 있어서 웹 브라우저를 통해 확인은 할 수 있는 Open API로 제공되지 않는 데이터들이 있다.

   이런 경우 데이터를 직접 읽어 원하는 부분만 추출해서 사용한다.

- 이 경우 사용하는 자바 라이브러리가 jsoup이다.

   Document 변수 = Jsoup.parse(html 문자열);

   Elements elements = 변수.select(String 선택자)

   for(Elements temp : elements){

      temp.text()     //호출하면 태그 안의 값을 가져올 수 있다.

   }

- jsoup는 안드로이드에서 제공이 안되므로 의존성을 추가해서 사용해야 한다.

 

※ finance.naver.com의 링크의 문자열을 전부 가져와서 출력

1. Android Application 생성

2. build.gradle 파일에 jsoup 라이브러리의 의존성을 추가

- mvnrepository.com에서 조회하여 라이브러리 whghl

- Module의 build.gradle의 dependencies 안에 추가

  implementation group: 'org.jsoup', name: 'jsoup', version: '1.11.3'

- 코드를 추가하고 상단에 Sync Now를 클릭

3. AndroidManifest.xml 파일에 인터넷 권한을 설정

    <uses-permission android:name="android.permission.INTERNET"/>

4. http 프로토콜의 사이트에 접속할 때는 application 태그에 설정을 추가

android:usesCleartextTraffic="true"

5. 레이아웃 수정

- ScrollView에 TextView 만 배치

<?xml version="1.0" encoding="utf-8"?>



    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">



    <ScrollView

        android:layout_width="match_parent"

        android:layout_height="match_parent">

        <TextView

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            android:textSize="24sp"

            android:id="@+id/display"/>

        

    </ScrollView>



</LinearLayout>

6. Activity.java 파일 작성

package com.example.dataparsing;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.w3c.dom.Text;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class MainActivity extends AppCompatActivity {
    TextView display;

    class ThreadEx extends Thread{
        StringBuilder sb = new StringBuilder();
        @Override
        public void run() {
            try{
                URL url = new URL("https://finance.naver.com/");
                HttpURLConnection con = (HttpURLConnection)url.openConnection();
                con.setUseCaches(false);
                con.setConnectTimeout(30000);
                //문자열 읽는 객체를 생성
                BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "EUC-KR"));
                while (true){
                    String line = br.readLine();
                    if(line == null){
                        break;
                    }
                    sb.append(line + "\n");
                }
                br.close();
                con.disconnect();
            }catch (Exception e){
                Log.e("다운로드 예외", e.getMessage());
            }
            try{
                //HTML 파싱
                // html을 전부 펼쳐서 DOM 객체로 생성
                Document doc = Jsoup.parse(sb.toString());
                //원하는 항목 가져오기
                Elements elements = doc.select("a");
                String result = "";
                for(Element element : elements){
                    result += element.text();
                    result += ": " + element.attr("href") + "\n";
                }
                //출력하기 위해서 핸들러를 호출
                Message message = new Message();
                message.obj = result;
                handler.sendMessage(message);
            }catch (Exception e){
                Log.e("파싱 예외", e.getMessage());
            }
        }
    }

    Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message message) {
            String result = (String)message.obj;
            display.setText(result);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        display = (TextView)findViewById(R.id.display);
    }

    @Override
    protected void onResume() {
        super.onResume();
        new ThreadEx().start();
    }
}