Dico

[Android] Android - Spring - Oracle 연동

  • 민갤

Oracle

계정 생성

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

테이블 생성

CREATE TABLE users (
    id          VARCHAR2(255),
    password    VARCHAR2(255),
    enabled     NUMBER(1)   DEFAULT 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 추가

    <!-- 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에 위치.

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

    <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 생성.

<!-- 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 생성.

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

public interface AndroidDAO {
    public String confirmUser(Users user);
}
@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

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

@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

인터넷 권한 부여.

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

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

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

build.gradle(Module: app)

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

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

Users.java

Spring의 Users Class와 동일

activity_signin.xml

로그인 화면 구성

<?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로 사용하므로 공통 부분을 따로 뺐다.

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

CMD 명령어 ipconfig

HttpClient.java

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

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();
            }
        }
    }
}
D/JSON_RESULT: {"password":"dico","id":"dico","enabled":"1"}


URLConnection API

HttpURLConnection API