본문 바로가기
카테고리 없음

자바 - 백앤드 학습 #4. 스프링-JDBC

네이버에서 제공하는 부스트코스 강좌 "웹 백앤드 심화" 과정에서
자바의 JDBC에 대한 기본 학습을 진행하였습니다.

https://www.boostcourse.org/web326/lecture/58973?isDesc=false


자바와 PHP 차이점을 살펴보니..

잠깐 자바와 PHP를 비교해보며 느낀 점을 적어보겠습니다.
PHP 의 편리한 개발 방식에 비해, 자바는 매우 불편하다는 느낌을 받았습니다.
PHP에서는 1~2줄이면 끝날 코드를 JDBC 는 이런 저런 객체를 가져다가 정확히 일치시켜 주어야 하거든요.
그 과정에 코드가 많아집니다.
그나마 데이터베이스를  다루면서 JDBC를 사용하지 않으면 코드가 더욱 많아집니다.

하지만 비록 불편함을 감수하며 얻을 수 있는 이로움은 테이블 필드명을 잘못 기재해 발생하는 문제와 같은 논리적인 오류입니다. 차라리 오류가 발생하면 바로 그것을 쉽게 감지할 수 있지만 논리적인 오류는 그것을 이야기 해주지 않아 발견하기가 매우 난해합니다.
그래서 PHP의 자유도는 양날의 검입니다. 서버가 오류 때문에 죽는 일이 거의 없습니다. 대신 잘못된 정보를 제공할 가능성이 높은 단점이 있지요.

하지만 자바는 조금이라도 이상하면 바로 바로 오류를 내뱉습니다.
그래서 논리적인, 눈에 보이지 않는 오류가 발생할 가능성이 적은 것이지요.
엄격한 언어는 이것이 장점입니다. 엄격한 만큼 실수할 가능성이 적습니다.

속도를 비교하자면 PHP는 ZEND 라는 실시간 컴파일 기능이 있어 한번 수정한 소스는 웹 페이지가 실행되면서 그 순간 기계어로 자동 컴파일됩니다. 그 후로 2번째 웹페이지에 접근하면 기계어가 자동 실행됩니다.
하지만 자바는 비록 컴파일이 되나 중간 인터프리터 언어로 컴파일 되어 실행시 중간 인터프리터 언어를 하나씩 번역하며 실행합니다. 그러다 보니 PHP가 훨씬 속도가 빠릅니다. ( PHP7 기준 )

양측 언어에 장단점이 있다보니 어느쪽이 더 좋다 할순 없지만,
크레이는 다년간 PHP 개발 경험이 축적되어 있어 논리적 오류는 대부분 대처할 수 있습니다.
기본 코드도 대부분 몸이 외우고 있기 때문에 PHP 가 더욱 친숙한 면도 있지요.

다만 한국은 자바 IT 강국이고 자바의 범위는 웹뿐만 아니라 앱개발이나 전자기기에도 사용되기에 자바를 배우지 않을 이유는 없다는 생각이 들었습니다 :)

크레이의 느낀 점은 이만 뒤로 하고, 이제 진행했던 부분 정리하겠습니다.
크레이에게도 다른 자바 학습을 하시는 분에게도 도움이 되는 글이 될 것을 기대하면서 말이지요 :)


스프링 JDBC 를 위한 설정

자바는 기본 db 접근 방법이 있는 반면,
스프링 JDBC(제이디비씨:Java Database Component) 라는 접근 방법이 있는데요.
이 방법이 코드량을 쥴이면서도 실수할 가능성이 적은 향상된 방법입니다.

그런데 초기 설정이 복잡합니다.
하지만 한번 설정하면 그 후로는 편리하게 사용할 수 있습니다.
부스트 코스의 강의가 약간 옛날꺼라 그대로 따라하다 보면 막히는데 일부 내용을 수정하였습니다.
( 정리일자 : 2024. 6. 2 )

초기 세팅 과정입니다.

먼저 메이븐 프로젝트를 생성합니다.

팝업창이 뜰텐데요. 처음 화면은 그냥 Next 버튼을 클릭해서 넘긴 후,
그 다음 화면에서 
1) Internal 선택
2) maven-archetype-quickstart 선택
3) Next 버튼을 클릭합니다.

다음 화면에서 Group Id와 Artiface Id 를 적어주면 되는데요.
Group Id 는 jdbcExam, Artifact Id는 jdbcExam01 로 적어 주었습니다.
그리고 나서 Finish 버튼 클릭!

그러면 프로젝트를 생성하는데 항상 그렇듯 콘솔창에서 설치 과정의 동의여부를 물어옵니다.
콘솔 창 선택 후 Enter 키를 타격!하시면 됩니다.

이제 메이븐 프로젝트를 만들었으니 JDBC 를 비롯, 갖가지 기능을 설치하면 되는데요.
프로젝트를 펼쳐 pom.xml 파일을 더블 클릭해 연 다음에,

아래 부분을 수정 후 저장하면 됩니다. (빨간색)


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>jdbcExam</groupId>
  <artifactId>jdbcExam01</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>jdbcExam01</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>5.2.3.RELEASE</spring.version>
  </properties>

  <dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  
  <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
  </build>
</project>


그리고 프로젝트를 1) 마우스 우클릭 - 2) Maven - 3) Update Project 메뉴를 선택해 위의 설정대로 모듈을 설치합니다. pom.xml 파일을 수정하면 항상 이 작업을 해주어야 합니다. ( 또는 단축키 Alt + F5 )

프로젝트를 펼쳐 보면 앞에서 입력한 id대로 jdbExam.jdbcExam01 패키지가 선물 꾸러미 모양의 아이콘을 하고 있을텐데요. 이를 패키지라고 합니다.

환경설정용 패키지는 따로 만들어주는게 좋다고 하는데요,. 패키지는 일종의 폴더 구성이라고도 할 수 있습니다.
한 폴더에 소스들이 몰려 있는 것보다는 용도별로 분리하는데 나중에 소스파일들이 많아 졌을떄 관리하기 편하기 떄문인데요.
jdbcExam.jdbcExam01 패키지에 소속된 jdbcExam.jdbcExam01.config 와 같은 이름으로 패키지명을 분리할 수 있습니다.

1) jdbcExam.jdbcExam01 패키지명을 마우스 우클릭 - 2) New - 3) Package 메뉴를 선택하고

Name 란에 jdbcExam.jdbcExam01 이 이미 입력되어 있을텐데요.
4) 뒷 부분에 .config 를 타이핑하여 이름을 jdbcExam.jdbcExam01.config 이 되도록 하고
5) Finish 버튼을 선택하면

프로젝트에 이렇게 패키지가 추가가 됩니다.
아직은 흰색 모양이지만 이 패키지 내에 파일을 넣으면 노란색으로 곧 바뀌게 됩니다.

이제 설정 클래스 파일을 추가하겠습니다.
1) jdbcExam.jdbcExam01.config 패키지를 선택
2) Ctrl + N 단축키를 누르면 기본으로 Class 가 선택되어 있으며,

3) Enter 키를 누르면 클래스명을 입력하는 화면이 등장합니다.
4) Name 란에
ApplicationConfig 입력 후 Enter 를 치면 클래스가 생성됩니다.

기본 소스에 아래 부분을 추가합니다 ( 빨간색 )


package jdbcExam.jdbcExam01.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({DBConfig.class})
public class ApplicationConfig {

}


DBConfig.class 는 아직 만들지 않아, 빨간 줄이 그어져 있을텐데요.

이제 동일한 방법으로 DBConfig 클래스를 추가합니다.
1) jdbcExahttp://m.jdbcExam01.config 패키지 선택 => 2) Ctrl + N => 3) Enter 입력
4) DBConfig 입력 후 Enter

소스 내용은 아래와 같이 구성합니다.

package jdbcExam.jdbcExam01.config;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class DBConfig {
	private String driverClassName = "com.mysql.jdbc.Driver";
    //private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8";
    // 내용 수정
    private String url = 
			"jdbc:mysql://localhost:3306/connectdb?"
			+ "useSSL=false&serverTimezone=Asia/Seoul";

    private String username = "connectuser";
    private String password = "connect123!@#";

    @Bean
    public DataSource dataSource() {
    	BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;

    }
}

이제 DB 설정을 테스트 해볼텐데요, 강좌보다는 좀 더 간단히 가겠습니다.
jdbcExam.jdbcExam01 패키지를 열어 App.java 파일을 연 다음

소스를 아래와 같이 수정합니다. ( 빨간색 )


package jdbcExam.jdbcExam01;

import java.sql.Connection;

import javax.sql.DataSource;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import jdbcExam.jdbcExam01.config.ApplicationConfig;

public class App 
{
    public static void main( String[] args )
    {
     ApplicationContext ac = 
     new AnnotationConfigApplicationContext(
     ApplicationConfig.class
     );
     DataSource ds = ac.getBean(DataSource.class);
     Connection conn = null;
     try {
     conn = ds.getConnection();
     if(conn != null)
     System.out.println("접속 성공!");
     }
     catch (Exception e) {
     e.printStackTrace();
     }finally {
     if(conn!=null) {
     try {
     conn.close();
     }
     catch (Exception e) {
     e.printStackTrace();
     }
     }
     }
    }
}


그리고 나서 프로그램을 실행했을 때, 터미널 창에 아래 문구가 나오면 제대로 접속이 된 것입니다.


데이터와 DTO 클래스 사전 준비

이제 설정은 끝났으니 본격적으로 데이터를 제어해보겠습니다.
사실 위 과정대로 매번 커넥션을 받아오고 처리하는 것은 코드량이 길어질뿐더러 작업 시간도 많아지게 마련인데요. 스프링 jdbc을 사용하면 좀 더 수월한 방법으로 데이터를 받아올 수 있습니다.

우선 테이블을 구성해 보겠습니다. mysql 에서 아래 쿼리를 실행하여 테이블과 데이터를 생성합니다.

CREATE TABLE actor (
    actor_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(45) NOT NULL,
    last_name VARCHAR(45) NOT NULL,
    PRIMARY KEY (actor_id)
);
INSERT INTO actor (first_name, last_name) VALUES
('펜', '스미스'),
('조니', '뎁'),
('브래드', '피트'),
('안젤리나', '졸리'),
('레오나르도', '디카프리오'),
('제니퍼', '로렌스'),
('윌', '스미스'),
('스칼렛', '요한슨'),
('로버트', '다우니'),
('크리스', '에반스'),
('엠마', '왓슨');

이제 이 구성대로 데이터를 가져올텐데요.
먼저 이 데이터 틀에 맞는 DTO 클래스를 구성해야 합니다.

앞에서 .config 패키지를 추가한 것처럼 jdbcExam.jdbcExam01.dto 패키지도 추가한 다음.
ActorData 클래스를 추가합니다.

그리고 소스 내용을 아래와 같이 구성하는데요.

package jdbcExam.jdbcExam01.dto;

public class ActorData {
	int actorId;
	String firstName;
	String lastName;
}


이 때 DB테이블과 클래스간의 이름에 대한 규칙을 지켜야 합니다.
DB 테이블의 이름은 각 단어간 _(언더바) 기호를 사용해서 이름을 지어야 하고
( actor_id, first_name, last_name )

DTO 자바 클래스는 첫글자를 제외한 각 단어의 시작 부분을 대문자로 명명해야 한다는 것입니다.
( actorId, frstName, lastName )

이 규칙을 잘 지키면 매우 편리한 이점이 있거든요.

이어서 getter, setter 함수를 코드 자동 생성기능을 통해 생성해 줍니다.
소스를 입력할 위치로 이동해서
1) Alt + Shift + S 키
2) Generate Getters and Setters... 선택
3) Select All 선택
4) Generate 버튼을 선택하면,

코드가 주루룩 생겨납니다.
스프링 JDBC에서 이 getter, setter 함수는 필수이니 꼭 해줘야 합니다.

이어서 toString() 함수도 추가합니다. 아래 순으로 선택하면,
1) Alt + Shift + S 키 - 2) Generate toString().. - 3) Generate 버튼

toString() 함수도 짜잔~ 생겨납니다. 이 것으로 DTO 클래스 는 준비하였습니다.


데이터를 가져와 볼까? DAO 클래스 만들어 SELECT 하기

db에서 데이터를 가져오는 부분은 별도의 클래스로 빼놓는게 좋은데요.
테이블이 배우(Actor), 영화(Movie) 등과 같은 경우 각 용도별로 배우클래스, 영화클래스와 같이 개별적으로 나누는게 관리 측면에서 좋습니다.

전산 용어로 데이터를 처리하는 객체를 Data Access Object, DAO(다오)라고 부릅니다.

여기서는 Actor(배우) 테이블 데이터를 처리할 목적이니 ActorDao 클래스를 생성해보겠습니다.
역시 jdbcExam.jdbcExam01.dao 패키지를 생성한 다음,

ActorDao 클래스를 생성합니다.

그리고 소스를 아래와 같이 구성합니다.

package jdbcExam.jdbcExam01.dao;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

import jdbcExam.jdbcExam01.dto.ActorData;

@Repository
public class ActorDao {
	private NamedParameterJdbcTemplate jdbc;
	private SimpleJdbcInsert insertAction;
	private RowMapper<ActorData> rowMapper = 
		BeanPropertyRowMapper.newInstance(ActorData.class);
	
	// 생성자
	public ActorDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
	}
	
	public List<ActorData> selectAll(){
		return jdbc.query(
			"SELECT * FROM actor order by actor_id", 
			Collections.emptyMap(), 
			rowMapper);
	}
}

그리고 기존의 ApplicationContext 클래스를 열어 아래 소스를 추가합니다. ( 빨강 )


@Configuration
@Import({DBConfig.class})
@ComponentScan(basePackages = { "jdbcExam.jdbcExam01.dao" })
public class ApplicationConfig {

}


App 클래스를 열어 소스를 아래와 같이 수정 후,

package jdbcExam.jdbcExam01;

import java.sql.Connection;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import jdbcExam.jdbcExam01.config.ApplicationConfig;
import jdbcExam.jdbcExam01.dao.ActorDao;
import jdbcExam.jdbcExam01.dto.ActorData;

public class App 
{
    public static void main( String[] args )
    {
    	ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class); 
		
		ActorDao actorDao =ac.getBean(ActorDao.class);

		List<ActorData> list = actorDao.selectAll();
		
		for(ActorData one: list) {
			System.out.println(one);
		}
    }
}

 

실행하면 배우의 이름이 콘솔창에 주루룩 나열됩니다.

ActorData [actorId=1, firstName=펜, lastName=스미스]
ActorData [actorId=2, firstName=조니, lastName=뎁]
ActorData [actorId=3, firstName=브래드, lastName=피트]
ActorData [actorId=4, firstName=안젤리나, lastName=졸리]
ActorData [actorId=5, firstName=레오나르도, lastName=디카프리오]
ActorData [actorId=6, firstName=제니퍼, lastName=로렌스]
ActorData [actorId=7, firstName=윌, lastName=스미스]
ActorData [actorId=8, firstName=스칼렛, lastName=요한슨]
ActorData [actorId=9, firstName=로버트, lastName=다우니]
ActorData [actorId=10, firstName=크리스, lastName=에반스]
ActorData [actorId=11, firstName=엠마, lastName=왓슨]

원래는 DTO 클래스 각 멤버변수에 일일히 대입하는 코드가 필요했을텐데 이런게 자동으로 된다니 참 신기하더군요 :)

특정 조건에 맞는 1건의 데이터만 Select할 경우는 굳이 LIST 로 받아올 필요가 없는데요.
ActorDao 클래스에 아래 메소드를 추가 후,

         :
    public ActorData selectOne(Integer actorId) {
		try {
			Map<String, ?> params = 
				Collections.singletonMap("actorId", actorId);
			return jdbc.queryForObject(
				"SELECT * FROM actor WHERE actor_id = :actorId LIMIT 1", 
				params, 
				rowMapper);
		}catch(EmptyResultDataAccessException e) {
			return null;
		}
	}
        :

App 클래스에서 아래 코드를 사용하면,

ActorData one = actorDao.selectOne(1);
System.out.println(one);

id가 1번에 해당하는 배우를 출력할 수 있습니다.

ActorData [actorId=1, firstName=펜, lastName=스미스]

이제 데이터를 추가해볼까요? - INSERT

데이터를 추가할 때는 SQL 쿼리문이 필요하지 않슴니다.
SimpleJdbcInsert 라는 내장 기능을 사용하면 되거든요.

먼저 ActorDao 클래스의 생성자에 아래 코드를 추가해 줍니다. ( 빨강 )


public ActorDao(DataSource dataSource) {
    this.jdbc = new NamedParameterJdbcTemplate(dataSource);
    this.insertAction = new SimpleJdbcInsert(dataSource)
        .withTableName("actor");
}


그리고 나서 아래 insert 메소드를 구현해주는데요.
입력 파라미터로 actorData를 건네주게 되어 있습니다.

public int insert(ActorData actorData) {
    SqlParameterSource params = 
        new BeanPropertySqlParameterSource(actorData);
    return insertAction.execute(params);
}

이제 App 클래스에서 데이터를 삽입하는 쿼리를 만드는 부분을 앞단에 추가한 다음 ( 빨강 )


public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class); 

        ActorDao actorDao =ac.getBean(ActorDao.class);
        ActorData actorData = new ActorData();
        actorData.setActorId(200);
        actorData.setFirstName("오드리");
        actorData.setLastName("햇반");
        actorDao.insert(actorData);

        List<ActorData> list = actorDao.selectAll();
            :


실행하면 새로 추가한 데이터가 자동 삽입된 것을 알 수 있습니다.

하지만 코드를 다시 실행하면 오류가 발생하는데요.
그것은 actorId 값에 동일한 200번을 세팅하였기 때문입니다.
중복을 허용하지 않는 조건이거든요.
이 때는 actorId 값을 0을 입력하면 알아서 번호가 자동 증가하며 다음 값을 입력해 줍니다.

ActorData actorData = new ActorData();
actorData.setActorId(0);
actorData.setFirstName("오드리");
actorData.setLastName("햇반");
actorDao.insert(actorData);


데이터를 수정해봅시다 - UPDATE

데이터를 1건 받아오는 코드가 있었는데요. 그 쪽에 연결해서 데이터를 수정하는 부분을 다뤄보겠습니다.

ActorDao 클래스에 업데이트 함수를 추가합니다.

public int update(ActorData actorData) {
    SqlParameterSource params = 
        new BeanPropertySqlParameterSource(actorData);
    return jdbc.update(
        "UPDATE actor SET "
            + "first_name = :firstName, "
            + "last_name = :lastName "
            + "WHERE actor_id = :actorId "
            + "LIMIT 1", 
        params);
}

그리고 App 클래스 main 함수 하단에 아래 코드를 추가한 다음 실행하면,

ActorData one = actorDao.selectOne(1);
System.out.println(one);

one.setFirstName("펜슬");
actorDao.update(one);

one = actorDao.selectOne(1);
System.out.println(one);

처음에 받아왔던 펜 스미스의 데이터를 수정한 후에는 펜슬 스미스로 출력되는 것을 확인할 수 있습니다.


데이터를 삭제해 봅시다 - DELETE

드디어 대망의(?) 마지막 삭제 액션입니다. 한번에 다 써내려가려니 좀 지치긴 합니다 ㅎㅎ

ActorDao 클래스에 삭제 함수를 추가한 다음, ( 본문 중 jdbc.update 가 맞습니다. delete 가 아니예요 )

public int deleteOne(Integer actor_id) {
    Map<String, ?> params = Collections.singletonMap("actorId", actor_id);
    return jdbc.update(
        "DELETE FROM actor WHERE actor_id = :actorId LIMIT 1", 
        params);
}

App 클래스의 main 함수 하단에 코드를 추가하면

one = actorDao.selectOne(1);
System.out.println(one);

// 삭제
actorDao.deleteOne(1);

// 삭제 후에는 null 로 반환됨
one = actorDao.selectOne(1);
System.out.println(one);

자료가 존재할 때 select 함수로 1번 배우를 받아올수 있었으나,
삭제 액션을 수행한후에는 받아올 수 없는 것을 확인할 수 있습니다.


마무~리

이상 여기까지가 다뤄본 내용인데요.
아직은 저도 스프링 JDBC가 익숙하지는 않지만,
이렇게 정리해 놓으면 나중에 써먹기가 좋을것 같다는 생각입니다.

아무쪼록 방문해주신 분들께 감!사!드립니다.
도움 되셨다면? 공감 주시면 감사! 댓글은 더더욱 감사입니다 :)


제이어스라는 열정적인 CCM 찬양팀의 찬양인데요.
한번 들어보시렵니까? 귀에 꽃히면 글쎄 자꾸 듣게 된다니까요 :)