사용자 ID 이름 패스워드를 jdbc를 이용해서 데이터베이스에 저장 조회하는 코드를 작성해보자
1) vo
2) db DAO
package step1.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import step1.vo.User;
// Database 처리하는 코드
// 2개의 method를 작성할거에요
// 사용자 입력, 조회
public class UserDao {
public void insert(User user) throws Exception {
// pure JDBC 이용해서 Database 처리를 해 보아요
// 6단계로 처리해요
// 1. Driver Loading
getClass().forName("com.mysql.cj.jdbc.Driver");
// 2. 연결해 보아요
String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/spring?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true";
String id = "root";
String pw = "test1234";
Connection con = DriverManager.getConnection(jdbcUrl, id, pw);
// 3. PreparedStatement를 만들어요
String sql = "INSERT INTO users VALUES (?, ?, ?)";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getName());
// 4. SQL 구문 실행
int result = pstmt.executeUpdate();
// 5. 결과 처리
if (result == 1) {
System.out.println("정상적으로 입력되었어요");
}
// 6. resource 해제
pstmt.close();
con.close();
}
public User select(String userId) throws Exception {
// pure JDBC 이용해서 Database 처리를 해 보아요
// 6단계로 처리해요
// 1. Driver Loading
getClass().forName("com.mysql.cj.jdbc.Driver");
// 2. 연결해 보아요
String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/spring?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true";
String id = "root";
String pw = "test1234";
Connection con = DriverManager.getConnection(jdbcUrl, id, pw);
// 3. PreparedStatement를 만들어요
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, userId);
// 4. SQL 구문 실행
ResultSet result = pstmt.executeQuery();
// 5. 결과 처리
result.next();
User user = new User();
user.setId(result.getString("id"));
user.setPassword(result.getString("password"));
user.setName(result.getString("name"));
// 6. resource 해제
pstmt.close();
con.close();
return user;
}
}
우리가 코드를 작성할떄 항상 염두해두어야 하는 사항
: 요구사항이 변화함에 따라우리 코드가 이런 변화에 잘 대응할 수 있도록 작성되어야 함
1) 분리
Soc 관심사의 분리
같은 concern(주제)이 한곳에 분리되어 존재해야함
메서드 추출을 통해 같은 주제를 분리
Step2로 코드를 refactoring
2) 우리 코드가 확장성이 있게 설계되어 있는지?
UserDao를 확장성 있게 만들어보자
(혼자 쓰는데는 무리가 없으나, UserDao를 판매해야한다면?
=> .class file을 제공. source code는 제공x
❓그럼 어떻게 써야 할까?
java에서 확장 -> Inheritance
변할 수 있는 여지가 있는 코드(concern)을 추상화해서 전달
=> abstract라는 키워드
클래스 자체가 추상클래스가 되어야 함
상속관계에 있어서
UserDao(로직흐름은 여기에 있어요)
NUserDao(기능의 일부(로직의 일부))분을 가지고 있어요
이렇게 구조로 잡아서 프로그래밍 하는 디자인 패턴을 template method pattern 패턴이라고 함. 상위클래스가 가지고 있어요
aa(){
call_A();
call_B();
}call_C();
* 상속관계에요
Factory method pattern
상속관계에 있어서 상위클래스인 UserDao에서 getConnection()-> 객체 생성
=>추상화시켜서 직접 생성하지x. 하위 클래스에서 만듬= NUserDao(오버라이딩된 getConnection에서 생성)
구체적인 객체 생성에 대한 방법을 하위클래스에서 결정하는 디자인패턴(템플릿메소드패턴과 별개임)
inheritance(상속)
단점
1) java는 single inheritance(단일상속)만을 지원
이미 상속받고 있으면
2) 상속은 class가 tightly coupled됨
일반적으로는 상속보다는 다른 사항을 이용함
그럼 inheritance를 사용하지 않고 확장성을 가지려면?
UserDao를 어떻게 만들어야 하나요?
분리를 통해 확실하에 두개의 class로 분리
=> 결과만 보면 확장성을 확보할 수 없었어요
지금 현재 코드가 UserDao는 연결정보를 가져오는 class에 대해 너무 상세히 알고 있어요
UserDao에서 자세하게 알고 있기 때문에
class이름을 UserDao안에 명시하고 있어요
UserDao안에 다른 클래스 이름이 박혀있음
결국, Tightly Coupled되어 있어요
-> 이문제를 해결해야해요 -> Interface로!
확장성이 있는 형태로 내 클래스를 디자인 해야함
(그림첨부)
✔️한 클래스 이름에 다른 클래스 이름이 명시적을 나오는건 좋지 않아요!(재사용, 유지보수에 좋지 않음)
아직 내 UserDao안에 class이름이 박혀있어서 좋지 않음!
Class안에서 다른 Class이름이 나오지 않도록 처리해야함
(그림첨부)
UserDao
⭐ Template method pattern - 상속관계
상위에 만들어놓고 하위가 상속override받아 수행
⭐ Strategy pattern -> 전략패턴
자신의 기능context의 일부분을 inetface를 통해 통째로 외부로 분리시키는 design pattern
우리가 만든 맨 마지막 예제는 전략패턴을 이용하고 있고,
전략패턴을 사용하는 경우 우리 예제처럼 전략을 주입하는 또 다른 객체가 필요(UserDaoTest처럼)
package step6.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import step6.vo.User;
// Database 처리하는 코드
// 2개의 method를 작성할거에요
// 사용자 입력, 조회
public class UserDao {
ConnectionMaker connectionMaker;
// injection(주입)이 발생한거에요
// 생성자를 통한 주입이 있고 setter를 통한 주입이 있어요
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
public void insert(User user) throws Exception {
Connection con = connectionMaker.getConnection();
// pure JDBC 이용해서 Database 처리를 해 보아요
// 6단계로 처리해요
// 3. PreparedStatement를 만들어요
String sql = "INSERT INTO users VALUES (?, ?, ?)";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getName());
// 4. SQL 구문 실행
int result = pstmt.executeUpdate();
// 5. 결과 처리
if (result == 1) {
System.out.println("정상적으로 입력되었어요");
}
// 6. resource 해제
pstmt.close();
con.close();
}
public User select(String userId) throws Exception {
// pure JDBC 이용해서 Database 처리를 해 보아요
// 6단계로 처리해요
// 1. Driver Loading
Connection con = connectionMaker.getConnection();
// 3. PreparedStatement를 만들어요
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, userId);
// 4. SQL 구문 실행
ResultSet result = pstmt.executeQuery();
// 5. 결과 처리
result.next();
User user = new User();
user.setId(result.getString("id"));
user.setPassword(result.getString("password"));
user.setName(result.getString("name"));
// 6. resource 해제
pstmt.close();
con.close();
return user;
}
}
package step6.dao;
import java.sql.Connection;
public interface ConnectionMaker {
Connection getConnection() throws Exception;
}
package step6.dao;
import java.sql.Connection;
import java.sql.DriverManager;
public class SimpleMakeConnection implements ConnectionMaker {
@Override
public Connection getConnection() throws Exception{
// 1. Driver Loading
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 연결해 보아요
String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/spring?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true";
String id = "root";
String pw = "test1234";
Connection con = DriverManager.getConnection(jdbcUrl, id, pw);
return con;
}
}
package step6;
import step6.dao.ConnectionMaker;
import step6.dao.SimpleMakeConnection;
import step6.dao.UserDao;
import step6.vo.User;
public class UserDaoTest {
public static void main(String[] args) throws Exception {
// 1. 사용자 VO 생성
User user = new User();
user.setId("hong");
user.setPassword("1234");
user.setName("홍길동");
ConnectionMaker connectionMaker = new SimpleMakeConnection();
// 2. DAO 생성
UserDao dao = new UserDao(connectionMaker);
// 3. 사용자 입력
dao.insert(user);
System.out.println("사용자 등록성공!");
// 4. 사용자 조회
User result = dao.select("hong");
System.out.println(result.getName()); // 홍길동
}
}
일명, 제어의 역전
Spring IoC container
앞에서 진행한 코드를 보자,
UserDaoTest는 userdao의 기능이 정상적으로 동작하는지 확인하는 기능을 갖고있었는데 하는김에 다른 객체의 연관관계를 맺어주는 userdao와 simplemakerconnection의 기능이 추가됨(객체간의 runtime 연관성)
객체를 만들어 return하는 특수한 형태를 factory
라고 하는데 이 객체를 찍어내는 역할을 해요
팩토리 디자인 패턴과눈 다름!
(그림첨부)
IoC(제어의 역전)은 컨테이너에 의해 역전됨
제어의 흐름을 거꾸로 뒤집는걸 지칭
객체가 알아서 만들어지고 알아서 실행되는것
: 정해져있는 키워드(Java)
/개발자가 만들 수도 있음
이런 Annotation은 주석이라고 해석되지만 실제 주석은 아님.
-> 로직에는 영향을 주지 않고, 어떤 프로그램의 구조에 영향을 줌
Spring의 가장 대표적인 기능
Bean factory
Spring ioc 컨테이너가 관리하는 자바 객체를 빈(Bean)이라고 부른다.
new 키워드를 사용하는 객체가 아닌 Spring 에서 ApplicationContext에서 만들어짐
bean을 어떤 클래스에서 어떤방식으로 사용해야할까?
Aplication context는 설정정보를 통해 Bean을 관리함
ApplicationContext라고 불리는 bean factory에서 관리하는 객체
Bean의 구성요소
1) class -> bean으로 등록한 java class(full package)
2) id
3) scope를 가지고 있어야 함 -> singleton, prototype,...
4) constructoe args가 있어요 : bean 생성시 생성자에 인자를 전달
5) property: bean생성시 setter에게 전달할 인자를 표현
수동방법
: annotation으로 알아보아요
public class MyResourse{ }
Aplication context는 설정정보를 바탕으로 bean을 관리(XML/annotation)
@Configuration
public class MyResourseConfig {
@Bean
public MyResourseConfig getMyResourseConfig() {
~~~
}
}
@Configuration 을 이용해서 @Bean 을 사용
Spring container는 @Configuration이 붙은 class