✔️ 자바에서 데이터베이스 접속할 수 있도록 하는 자바 API
💡 JDBC의 등장으로 2가지의 문제를 해결
데이터베이스를 다른 종류의 데이터베이스로 변경하면 애플리케이션 서버의 데이터베이스 사용 코드도 함께 변경해야 하는 문제
개발자가 각각의 데이터베이스마다 커넥셔 연결, SQL 전달, 그리고 그 결과를 응답 받는 방법을 새로 학습해야 하는 문제
✔️ 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
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.10</version>
</dependency>
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);
}
}
✔️ DB와 관계된 커넥션 정보를 담고있으며 빈으로 등록하여 인자로 넘겨준다.
➡️ 이 과정을 통해 Spring은 DataSource로 DB와의 연결을 획득한다.
DB 서버와의 연결
DB Connetion pooling 기능
종류 : JDBC Driver vendor(MySQL, Oracle)별로 여러가지가 존재함
<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&characterEncoding=utf8"></property>
<property name="username" value="asdf"></property>
<property name="password" value="1234"></property>
</bean>
이렇게 하면 sout을 통해서 잘 연결된 것을 확인할 수 있지만, 우리가 직접 눈으로 보고 연결된 것을 확인할 수 있다. 이렇게 말고 JUnit이라는 테스트도구를 활용해서 테스트를 자동화하게 하자.
➡️ TDD(Test Driven Development)
: 테스트 할 코드가 아무리 많아도 테스트를 한번에 돌려서 어떤 테스트가 실패했는지 여부를 알 수 있다.
sout을 TDD 형식으로 바꿔보자
✔️ 테스트 프레임워크
❗️ TDD(Test Driven Development)
: 테스트 주도 개발
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);
}
}
<!-- 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
이 붙은 테스트메서드들은 각 해당 테스트 와 독립적이여야 한다.deleteAll()
메서드 같은 것을 만들기!✔️ @RunWith
: ApplicationContext를 자동으로 만들어주는 어노테이션
✔️ @ContextConfiguration
: xml 설정 파일을 로드해주는 어노테이션
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
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
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