[Spring] JDBC: Spring으로 DB연결하기

Jeini·2023년 5월 28일
0

🍃  Spring

목록 보기
18/33
post-thumbnail

💡 JDBC(Java Database Connectivity)


✔️ 자바에서 데이터베이스 접속할 수 있도록 하는 자바 API

💡 JDBC의 등장으로 2가지의 문제를 해결

  1. 데이터베이스를 다른 종류의 데이터베이스로 변경하면 애플리케이션 서버의 데이터베이스 사용 코드도 함께 변경해야 하는 문제

    • 애플리케이션 로직은 이제 JDBC 표준 인터페이스에만 의존한다. 따라서 데이터베이스를 다른 종류의 데이터베이스로 변경하고 싶으면, JDBC 구현 라이브러리만 변경하면 된다.
      ➡️ 다른 종류의 데이터베이스로 변경해도 애플리케이션 서버의 사용 코드를 그대로 유지할 수 있다.
  2. 개발자가 각각의 데이터베이스마다 커넥셔 연결, SQL 전달, 그리고 그 결과를 응답 받는 방법을 새로 학습해야 하는 문제

    • 개발자는 JDBC 표준 인터페이스 사용법만 학습하면 된다. 한번 배워두면 수십개의 데이터베이스에 모두 동일하게 적용할 수 있다.

⚙️ JDBC 표준 인터페이스의 3가지 기능

  • java.sql.Connection : 연결
  • java.sql.Statement : SQL을 담은 내용
  • java.sql.ResultSet : SQL 요청 응답

✏️ 1. JDBC API를 이용해서 DB 연결

✏️ MySQL Connector/J » 8.0.33

✔️ java코드와 mysql을 연결하려면 JDBC 드라이버가 필요하다.

<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>
package kr.ac.jipark09;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

// JDBC API를 이용하여 DB와 연결함
public class DBConnectionTest {
    public static void main(String[] args) throws Exception {
        String DB_URL = "jdbc:mysql://localhost:3306/springBasic?useUnicode=true&characterEncoding=utf8";
        String DB_USER = "jipark09";
        String DB_PASSWORD = "1234";

        // 데이터베이스의 연결을 얻음
        Connection connection = DriverManager.getConnection(DB_URL, DB_URL, DB_PASSWORD);
        Statement statement = connection.createStatement();

        // 시스템의 현재 날짜시간을 출력하는 쿼리
        String query = "SELECT now()";
        // 쿼리를 실행한 결과를 resultSet에 담음
        ResultSet resultSet = statement.executeQuery(query);

        if (resultSet.next()) {
            String curDate = resultSet.getString(1);
            System.out.println(curDate);
        }

    }
}
2023-06-17 16:53:23

✏️ 2. Spring JDBC를 이용해서 DB연결

✏️ Spring JDBC » 6.0.9 라이브러리 가져오기

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.10</version>
</dependency>

🖇️ DB 연결하기

package kr.ac.jipark09;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;
import java.sql.Connection;

public class DBConnectionDB2 {
    public static void main(String[] args) throws Exception {
        // 데이터 정보가 계속 바뀔 때 마다 이거를 계속 고치고 컴파일하고 반복이됨
        String DB_URL = "jdbc:mysql://localhost:3306/springBasic?useSSL=false&useUnicode=true&characterEncoding=utf8";
//
//        String DB_USER = "jipark09";
//        String DB_PASSWORD = "1234";
//        String DB_DRIVER = "com.mysql.jdbc.Driver";
//
          // setter처리를 프로퍼티태그로 처리 -> 빈태그로 바꿈 -> root-context.xml에 넣을꺼임
//        DriverManagerDataSource ds = new DriverManagerDataSource();
//        ds.setDriverClassName(DB_DRIVER);
//        ds.setUrl(DB_URL);
//        ds.setUsername(DB_USER);
//        ds.setPassword(DB_PASSWORD);

        ApplicationContext ac = new GenericXmlApplicationContext("file:src/main/webapp/WEB-INF/spring/**/root-context.xml");
        DataSource ds = ac.getBean(DataSource.class);

        Connection conn = ds.getConnection(); // 데이터베이스의 연결을 얻는다.

        System.out.println("conn = " + conn);
//        assertTrue(conn!=null);
    }
}

💡 DataSource

✔️ DB와 관계된 커넥션 정보를 담고있으며 빈으로 등록하여 인자로 넘겨준다.
➡️ 이 과정을 통해 Spring은 DataSource로 DB와의 연결을 획득한다.

  • DB 서버와의 연결

  • DB Connetion pooling 기능

  • 종류 : JDBC Driver vendor(MySQL, Oracle)별로 여러가지가 존재함

📎 DataSource를 설정하고 빈에등록, 주입하는 방법

✏️ root-context.xml property 태그 설정

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://localhost:3306/springbasic?useUnicode=true&amp;characterEncoding=utf8"></property>
		<property name="username" value="asdf"></property>
		<property name="password" value="1234"></property>
</bean>

  • 이렇게 하면 sout을 통해서 잘 연결된 것을 확인할 수 있지만, 우리가 직접 눈으로 보고 연결된 것을 확인할 수 있다. 이렇게 말고 JUnit이라는 테스트도구를 활용해서 테스트를 자동화하게 하자.
    ➡️ TDD(Test Driven Development)
    : 테스트 할 코드가 아무리 많아도 테스트를 한번에 돌려서 어떤 테스트가 실패했는지 여부를 알 수 있다.

  • sout을 TDD 형식으로 바꿔보자

💡 JUnit


✔️ 테스트 프레임워크

  • 위 예제에서 DB를 연결해 보았다. 하지만 이렇게 되면 실행 결과를 일일이 눈으로 확인해야 한다. 결과가 잘 나왔는지!
    ➡️ JUnit 즉, 테스트 프레임워크를 이용하면 테스트를 자동화 할 수 있다. = TDD
    : 테스트 할 코드가 많아도 일괄적으로 한번에 돌려서 테스트에 성공했는지 실패했는지 알 수 있다.

❗️ TDD(Test Driven Development)
: 테스트 주도 개발

✏️ DB 연결 Test

package kr.ac.jipark09;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import javax.sql.DataSource;
import java.sql.Connection;

import static org.junit.Assert.*;

public class DBConnectionTest2Test {

    @Test
    public void main() throws Exception {
        ApplicationContext ac = new GenericXmlApplicationContext("file:src/main/webapp/WEB-INF/spring/**/root-context.xml");
        DataSource ds = ac.getBean(DataSource.class);

        Connection connection = ds.getConnection();
        assertTrue(connection != null);
    }

}
  • 근데 @Autowired를 이용해서 바로 DataSource를 받을려고 하니까 @Runwith해서 SpringJUnit4ClassRunner.class을 해줘야 하는데 SpringJUnit4ClassRunner는 Spring TestContext Framework 라이브러리가 필요하다.

💡 Spring TestContext Framework


✏️ Spring TestContext Framework >> 라이브러리 가져오기

<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${org.springframework-version}</version>
    <scope>test</scope>
</dependency>

⚙️ JUnit이 돌아가면서 SpringJUnit4ClassRunner를 이용해서 Test를 한다.

  • @Test 이 붙은 테스트메서드들은 각 해당 테스트 와 독립적이여야 한다.
    ➡️ 여러번 수행해도 test에 실패하면 안됨
    ➡️ 시작하기 전에 기록들을 모두 삭제하는 deleteAll() 메서드 같은 것을 만들기!

✔️ @RunWith
: ApplicationContext를 자동으로 만들어주는 어노테이션

  • 계속해서 ApplicationContext를 만들어 주지 않아도 되며, 성능도 좋다.

✔️ @ContextConfiguration
: xml 설정 파일을 로드해주는 어노테이션

  • 생성된 스프링 컨테이너에 스프링 빈을 추가하기 위해서는 application-context.xml 파일과 같은 설정 파일을 읽어야 하는데, 이런 설정파일을 로드해 주는 역할을 한다.

✔️ PreparedStatement의 관한 포스팅

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.sql.DataSource;
import java.sql.Connection;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/spring/**/root-context.xml")
public class DBConnectionTest {
    @Autowired
    DataSource ds;

    @Test
    public void main() throws Exception {
        Connection connection = ds.getConnection();
        assertTrue(connection != null);
    }
}

❌ 버전 에러

/Users/j_in/.sdkman/candidates/java/11.0.17-amzn/bin/java -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=50835:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit5-rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar:/Users/j_in/IdeaProjects/ch2/target/test-classes:/Users/j_in/IdeaProjects/ch2/target/classes:/Users/j_in/.m2/repository/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar:/Users/j_in/.m2/repository/com/google/protobuf/protobuf-java/3.21.9/protobuf-java-3.21.9.jar:/Users/j_in/.m2/repository/org/springframework/spring-jdbc/5.0.7.RELEASE/spring-jdbc-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-beans/5.0.7.RELEASE/spring-beans-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-core/5.0.7.RELEASE/spring-core-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-jcl/5.0.7.RELEASE/spring-jcl-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-tx/5.0.7.RELEASE/spring-tx-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-context/5.0.7.RELEASE/spring-context-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-aop/5.0.7.RELEASE/spring-aop-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-expression/5.0.7.RELEASE/spring-expression-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-webmvc/5.0.7.RELEASE/spring-webmvc-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/springframework/spring-web/5.0.7.RELEASE/spring-web-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/org/aspectj/aspectjrt/1.6.10/aspectjrt-1.6.10.jar:/Users/j_in/.m2/repository/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6.jar:/Users/j_in/.m2/repository/org/slf4j/jcl-over-slf4j/1.6.6/jcl-over-slf4j-1.6.6.jar:/Users/j_in/.m2/repository/org/slf4j/slf4j-log4j12/1.6.6/slf4j-log4j12-1.6.6.jar:/Users/j_in/.m2/repository/log4j/log4j/1.2.15/log4j-1.2.15.jar:/Users/j_in/.m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar:/Users/j_in/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/Users/j_in/.m2/repository/javax/servlet/jsp/jsp-api/2.1/jsp-api-2.1.jar:/Users/j_in/.m2/repository/javax/servlet/jstl/1.2/jstl-1.2.jar:/Users/j_in/.m2/repository/org/springframework/spring-test/5.0.7.RELEASE/spring-test-5.0.7.RELEASE.jar:/Users/j_in/.m2/repository/junit/junit/4.7/junit-4.7.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.fastcampus.ch2.DBConnectionTest,main

java.lang.ExceptionInInitializerError
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
	at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:31)
	at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:24)
	at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
	at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29)
	at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
	at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24)
	at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:33)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:50)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: java.lang.IllegalStateException: SpringJUnit4ClassRunner requires JUnit 4.12 or higher.
	at org.springframework.util.Assert.state(Assert.java:73)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<clinit>(SpringJUnit4ClassRunner.java:104)
	... 17 more


Process finished with exit code 255
  • junit의 버전이 Spring-test와 버전이 맞지 않아서 발생하는 에러이다.
    에러문구에 적힌대로 4.12로 바꿔주자!
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.12</version>
	<scope>test</scope>
</dependency> 

✏️ sql 데이터베이스에 데이터를 저장하고 읽어오는 방법 CRUD + @Test

package kr.ac.jipark09;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Date;
import java.sql.PreparedStatement;
import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})

public class DBConnectionTest2Test {
    @Autowired
    // 각 test들은 JUnit에서 각각 다른 객체를 생성해서 test해 주기 때문에 이 iv를 공유하지 않는다.
    DataSource dataSource;

    // 사용자 정보를 user_info 테이블에 저장하는 메서드 -> insert
    public int insertUser(User user) throws Exception {
        int rowCount = 0;

        // 1. db 연결: 데이터소스로부터 데이터를 가져옴
        Connection connection = dataSource.getConnection();

        // 2. sql문 작성
        String sql = "insert into user_info values (?, ?, ?, ?, ?, ?, now())";

        // 3. ?에 해당하는 값들을 채워줌
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, user.getId());
        preparedStatement.setString(2, user.getPwd());
        preparedStatement.setString(3, user.getName());
        preparedStatement.setString(4, user.getEmail());
        // setDate는 sql Date를 필요로 하기 때문에 이렇게 바꿔준다.
        preparedStatement.setDate(5, new java.sql.Date(user.getBirth().getTime()));
        preparedStatement.setString(6, user.getSns());

        /* 4. sql문 실행
         sql을 실행한 결과에서 몇개 행이 영향을 받았나를 반환
         insert 같은 경우에는 한문장만 넣으니까 성공하면 1이 반환될꺼고 실패하면 0이 반환된다. */
        rowCount = preparedStatement.executeUpdate(); // insert, delete, update
        return rowCount;
    }
    @Test
    public void insertUserTest() throws Exception {
        User user = new User("asdf2", "12345", "asd", "aaa@aaa", new Date(), "faceBook", new Date());
        // 기존의 데이터를 다 지우기 때문에 중복이 되지 않는다.
        deleteAll();
        int rowCount = insertUser(user);

        // 1이면 테스트 성공
        assertTrue(rowCount == 1);
    }

    // id하나로 사용자 정보 가져오기 -> select
    public User selectUser(String id) throws Exception {
        Connection connection = dataSource.getConnection();
        String sql = "select * from user_info where id= ?";

        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, id);
        ResultSet resultSet = preparedStatement.executeQuery();

        if(resultSet.next()) {
            User user = new User();
            user.setId(resultSet.getString(1));
            user.setPwd(resultSet.getString(2));
            user.setName(resultSet.getString(3));
            user.setEmail(resultSet.getString(4));
            user.setBirth(new java.sql.Date(resultSet.getDate(5).getTime()));
            user.setSns(resultSet.getString(6));
            user.setReg_date(new java.sql.Date(resultSet.getDate(7).getTime()));

            return user;
        }
        return null;
    }
    @Test
    public void selectUserTest() throws  Exception {
        deleteAll();
        User user = new User("asdf2", "12345", "asd", "aaa@aaa", new Date(), "faceBook", new Date());
        User user2 = selectUser("asdf2");
        assertTrue(user.getId().equals("asdf2"));

    }
	
    // user 정보를 지우는 메서드 -> delete
    public int deleteUser(String id) throws Exception {
        Connection connection = dataSource.getConnection();
        String sql = "delete from user_info where id= ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, id);
        int rowCount = preparedStatement.executeUpdate();
        return rowCount;
    }
    @Test
    public void deleteUserTest() throws  Exception {
        deleteAll();
        int rowCount = deleteUser("asdf");

        assertTrue(rowCount == 0);

        User user = new User("asdf2", "12345", "asd", "aaa@aaa", new Date(), "faceBook", new Date());
        rowCount = insertUser(user);
        rowCount = deleteUser("asdf2");

        assertTrue(rowCount == 1);
        assertTrue(selectUser(user.getId()) == null);

    }

    // 매개변수로 받은 사용자 정보로 user_info 테이블을 update하는 메서드 -> update
    public int updateUser(User user) throws Exception {
        int rowCount = 0;
        Connection connection = dataSource.getConnection();
        String sql = "update user_info set pwd=?, name=?, email=?, birth=?, sns=?, reg_date=now() where id =?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setString(1, user.getPwd());
        preparedStatement.setString(2, user.getName());
        preparedStatement.setString(3, user.getEmail());
        // setDate는 sql Date를 필요로 하기 때문에 이렇게 바꿔준다.
        preparedStatement.setDate(4, new java.sql.Date(user.getBirth().getTime()));
        preparedStatement.setString(5, user.getSns());
//        preparedStatement.setDate(6, new java.sql.Date(user.getReg_date().getTime()));
        preparedStatement.setString(6, user.getId());

        rowCount = preparedStatement.executeUpdate();
        return rowCount;
    }
    @Test
    public void updateUserTest() throws Exception {
        deleteAll();

        User user = new User("asd", "12345", "asd", "aaa@aaa", new Date(), "faceBook", new Date());
        int result = insertUser(user);
        assertTrue(result == 1);

        User user1 = new User("asd", "12345", "asd", "aaa@aaa", new Date(), "faceBook", new Date());
        result = updateUser(user1);
        assertTrue(result == 1);

        assertTrue(selectUser(user1.getId()).getName().equals("asd"));

    }
    
     // insert하기 전에 모두 다 지움 => 어떨때는 테스트 되고 어떨때는 테스트 안되고를 방지하기 위해
    private void deleteAll() throws Exception {
        Connection connection = dataSource.getConnection();
        String sql = "delete from user_info";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.executeUpdate(); // insert, delete, update
    }

    @Test
    public void springJdbcConnectionTest() throws Exception {

	Connection conn = dataSource.getConnection(); // 데이터베이스의 연결을 얻는다.

        System.out.println("conn = " + conn);
        assertTrue(conn != null); // 괄호 안의 조건식이 true이면 테스트 성공, 아니면 실패
    }

}

Reference
: https://esoongan.tistory.com/164
: https://fastcampus.co.kr/dev_academy_nks

profile
Fill in my own colorful colors🎨

0개의 댓글