[Android] Android - Spring - Oracle 연동

(Modify : 2018-02-19)

Oracle

계정 생성

SQL

1

2

3

CREATE USER android IDENTIFIED BY android DEFAULT TABLESPACE USERS;
GRANT connect, resource TO android;
ALTER USER android ACCOUNT UNLOCK;

테이블 생성

SQL

1

2

3

4

5

CREATE TABLE users (
    id          VARCHAR2(255),
    password    VARCHAR2(255),
    enabled     NUMBER(1)   DEFAULT 1
);

데이터 삽입

SQL

1

INSERT INTO users (id, password) VALUES ('dico', 'dico');

Spring

프로젝트 생성

Spring Legacy Project > Spring MVC Project (Package Name: spring.mvc.android)

pom.xml

MyBatis와 JSON을 사용하기 위한 jar 추가

Plain Text

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.1</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.4</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>

    <!-- JSON -->
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-mapper-asl</artifactId>
        <version>1.9.13</version>
    </dependency>
    <dependency>
        <groupId>com.googlecode.json-simple</groupId>
        <artifactId>json-simple</artifactId>
        <version>1.1.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.1</version>
    </dependency>

https://mvnrepository.com에서 직접 다운로드 가능

context.xml

Servers>Tomcat [version] Server at localhost-config에 위치.

데이터베이스 연결 정보 추가.

1

2

3

4

5

6

7

8

9

10

    <Resource auth="Container"
        driverClassName="oracle.jdbc.driver.OracleDriver"
        maxActive="50"
        maxWait="1000"
        name="jdbc/Oracle11g/android"
        username="android"
        password="android"
        type="javax.sql.DataSource"
        url="jdbc:oracle:thin:@localhost:1521:xe"
    />

servlet-context.xml

Connection Pool에 접근하는 bean 생성.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<!-- MyBatis -->

<!-- DAO에서 사용할 Connection Pool 접근 변수 -->
<beans:bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <beans:constructor-arg index="0" ref="sqlSessionFactory" />
</beans:bean>

<beans:bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- Connection Pool 호출 -->
    <beans:property name="dataSource" ref="dataSource" />
    <!-- mapper.xml 경로 설정 -->
    <beans:property name="mapperLocations" value="classpath:android/*.xml" />
</beans:bean>

<!-- 3. Connection Pool 접속 -->
<beans:bean name="dataSource"
        class="org.springframework.jndi.JndiObjectFactoryBean">
    <!-- context.xml의 ConnectionPool lookup -->
    <beans:property name="jndiName" value="java:comp/env/jdbc/Oracle11g/android" />
    <beans:property name="resourceRef" value="true" />
</beans:bean>

Users Class

VO Class 생성.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

public class Users {

private String id;
private String password;
private int enabled;

public String getId() {
return id;
    }

public void setId(String id) {
this.id = id;
    }

public String getPassword() {
return password;
    }

public void setPassword(String password) {
this.password = password;
    }

public int getEnabled() {
return enabled;
    }

    public void setEnabled(int enabled) {
this.enabled = enabled;
    }
}

DAO

Java

1

2

3

public interface AndroidDAO {
    public String confirmUser(Users user);
}

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

@Repository
public class AndroidDAOImpl implements AndroidDAO {

    @Autowired
    SqlSession sqlSession;

    @Override
    public String confirmUser(Users user) {
        AndroidDAO mapper = sqlSession.getMapper(AndroidDAO.class);
        String enabled = mapper.confirmUser(user);
        return enabled;
    }
}

Mapper

src>main>resources>android>mapper.xml

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<?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="spring.mvc.android.persistence.AndroidDAO">

    <select id="confirmUser" resultType="String">
        SELECT enabled
        FROM users
        WHERE id = #{id} AND password = #{password}
    </select>

</mapper>

Controller

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

@Controller
public class AndroidController {
    private Logger log = Logger.getLogger(this.getClass());
    
    @Autowired
    AndroidDAO dao;
    
    @ResponseBody
    @RequestMapping("androidSignIn")
    public Map<String, Object> android(HttpServletRequest req) {
        log.info("androidSignIn()");

        String id = req.getParameter("id");
        String pwd = req.getParameter("pwd");
        
        Users user = new Users();
        user.setId(id);
        user.setPassword(pwd);
        
        String enabled = dao.confirmUser(user);
        if(enabled == null) {
            enabled = "0";
        }
        
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("id", id);
        map.put("password", pwd);
        map.put("enabled", enabled);
        return map;
    }
}

Android

AndroidManifest.xml

인터넷 권한 부여.

1

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

첫 시작 Activity를 바꿀 경우 수정.

1

2

3

4

5

6

<application
        ...>
    <activity android:name=".SignInActivity">
        ...
    </activity>
</application>

build.gradle(Module: app)

GSON(JSON을 편하게 사용할 수 있도록 google에서 만든 라이브러리) 추가

1

implementation 'com.google.code.gson:gson:2.8.2'

Users.java

Spring의 Users Class와 동일

activity_signin.xml

로그인 화면 구성

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/edt_id"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="32dp"
        android:layout_marginStart="32dp"
        android:layout_marginTop="96dp"
        android:ems="10"
        android:hint="ID"
        android:inputType="textPersonName"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/edt_pwd"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="32dp"
        android:layout_marginStart="32dp"
        android:ems="10"
        android:hint="PASSWORD"
        android:inputType="textPassword"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edt_id" />

    <Button
        android:id="@+id/btn_signin"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="32dp"
        android:layout_marginStart="32dp"
        android:layout_marginTop="16dp"
        android:background="@android:color/holo_blue_dark"
        android:text="SignIn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edt_pwd" />
</android.support.constraint.ConstraintLayout>

Web.java

Task를 각 Activity마다 Inner Class로 사용하므로 공통 부분을 따로 뺐다.

Java

1

2

3

4

public class Web {
    public static String ip = "[본인IP]";
    public static String servletURL = "http://" + ip + ":[포트번호]/android/"; //URL 공통 부분
}

CMD 명령어 ipconfig

HttpClient.java

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


public class HttpClient {

    private int httpStatusCode; //HTTP 응답 상태 코드. (ex: 200, 401, 식별 불가능하면 -1)
    private String body;

    public int getHttpStatusCode() {
        return httpStatusCode;
    }

    public String getBody() {
        return body;
    }

    private Builder builder;

    private void setBuilder(Builder builder) {
        this.builder = builder;
    }

    //HTTP 요청 결과 수집 후 연결 해제
    public void request() {
        HttpURLConnection conn = getConnection();
        setHeader(conn);
        setBody(conn);

        httpStatusCode = getStatusCode(conn);
        body = readStream(conn); //요청 결과

        conn.disconnect(); //연결 해제
    }

    private HttpURLConnection getConnection() {
        try {
            URL url = new URL(builder.getUrl());
            return (HttpURLConnection) url.openConnection();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void setHeader(HttpURLConnection conn) {
        setContentType(conn);
        setRequestMethod(conn);

        conn.setConnectTimeout(30 * 1000); //서버 연결 시간 제한. 지정된 시간 안에 연결되지 않으면 SocketTimeoutException 발생
        conn.setReadTimeout(15 * 1000); //Input Stream으로 읽을 때의 시간 제한. 읽기 전에 시간 초과되면 SocketTimeoutException 발생
        conn.setDoOutput(true); //출력 사용 여부
        conn.setDoInput(true);  //입력 사용 여부
    }

    private void setContentType(HttpURLConnection conn) {
        conn.setRequestProperty("content-Type", "application/x-www-form-urlencoded");
    }

    private void setRequestMethod(HttpURLConnection conn) {
        try {
            conn.setRequestMethod(builder.getMethod());
        } catch (ProtocolException e) {
            e.printStackTrace();
        }
    }

    private void setBody(HttpURLConnection conn) {
        String parameter = builder.getParameter();
        if (parameter != null && parameter.length() > 0) {
            OutputStream outputStream = null;
            try {
                outputStream = conn.getOutputStream();
                outputStream.write(parameter.getBytes("UTF-8"));
                outputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private int getStatusCode(HttpURLConnection conn) {
        try {
            return conn.getResponseCode();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return -1;
    }

    private String readStream(HttpURLConnection conn) {
        String result = "";
        BufferedReader reader = null;

        //요청 결과 읽기
        try {
            reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                result += line;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    //전체 URL 생성하는 Builder Pattern
    public static class Builder {
        private Map<String, String> parameters;
        private String method;
        private String url;

        public String getMethod() {
            return method;
        }

        public String getUrl() {
            return url;
        }

        public Builder(String method, String url) {
            if (method == null) {
                method = "GET";
            }
            this.method = method;
            this.url = url;
            this.parameters = new HashMap<>();
        }

        public void addOrReplace(String key, String value) {
            parameters.put(key, value);
        }

        public void addAllParameters(Map<String, String> param) {
            parameters.putAll(param);
        }

        public String getParameter() {
            return generateParameters();
        }

        public String getParameter(String key) {
            return this.parameters.get(key);
        }

        private String generateParameters() {
            StringBuffer urlParameters = new StringBuffer();

            Iterator keys = getKeys();

            //전달할 값을 URL 형식으로 바꾸기
            String key = "";
            while (keys.hasNext()) {
                key = (String) keys.next();
                urlParameters.append(String.format("%s=%s", key, parameters.get(key)));
                urlParameters.append("&");
            }
            String params = urlParameters.toString();
            if (params.length() > 0) {
                params = params.substring(0, params.length() - 1);
            }
            return params;
        }

        private Iterator getKeys() {
            return this.parameters.keySet().iterator();
        }

        public HttpClient create() {
            HttpClient client = new HttpClient();
            client.setBuilder(this);
            return client;
        }
    }
}

SignInActivity.java

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.google.gson.Gson;

import java.util.HashMap;
import java.util.Map;

public class SignInActivity extends AppCompatActivity {

    private EditText edtId, edtPwd;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_signin);

        edtId = findViewById(R.id.edt_id);
        edtPwd = findViewById(R.id.edt_pwd);

        Button btnSignIn = findViewById(R.id.btn_signin);
        btnSignIn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                Map<String, String> map = new HashMap<>();
                map.put("id", edtId.getText().toString());
                map.put("pwd", edtPwd.getText().toString());

                MapTask task = new MapTask();
                task.execute(map);
            }
        });
    }

    public class MapTask extends AsyncTask<Map, Integer, String> {

        //doInBackground 전에 동작
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        //작업을 쓰레드로 처리
        @Override
        protected String doInBackground(Map... maps) {
            //HTTP 요청 준비
            HttpClient.Builder http = new HttpClient.Builder("POST",  Web.servletURL + "androidSignIn");

            //Parameter 전송
            http.addAllParameters(maps[0]);

            //HTTP 요청 전송
            HttpClient post = http.create();
            post.request();

            //응답 상태 코드
            int statusCode = post.getHttpStatusCode();

            //응답 본문
            String body = post.getBody(); //Spring의 Controller에서 반환한 값. JSON 형식
            return body;
        }

        /*
            doInBackground 후에 동작.
            String s : doInBackground에서 반환한 body
         */
        @Override
        protected void onPostExecute(String s) {
            //super.onPostExecute(s);
            Log.d("JSON_RESULT", s);

            //JSON 형식의 데이터를 Class Object로 바꿔준다.
            Gson gson = new Gson();
            Users user = gson.fromJson(s, Users.class);

            if(user != null && user.getEnabled() != 0) {
                Toast.makeText(getApplicationContext(), "로그인", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(getApplicationContext(), "회원 정보가 올바르지 않습니다.", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Plain Text

1

D/JSON_RESULT: {"password":"dico","id":"dico","enabled":"1"}

URLConnection API

HttpURLConnection API

민갤

Back-End Developer

백엔드 개발자입니다.

[도서] 내 문장이 그렇게 이상한가요?

Review

[Android] RecyclerView

RecyclerView

Android

v5.0