DAO란?
DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.
✅클래스의 분리
//SimpleConnectionMaker
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws SQLException {
Map<String, String> env = System.getenv();
String dbHost = env.get("DB_HOST");
String dbUser = env.get("DB_USER");
String dbPassword = env.get("DB_PASSWORD");
Connection conn = DriverManager.getConnection(dbHost, dbUser, dbPassword);
return conn;
}
}DB커넥션 생성 기능을 다른 클래스로 완전히 독립시켰다.
더 이상 상속을 이용한 확장 방식을 사용할 필요가 없으니 추상 클래스로 만들 필요가 없다.
//UserDAO
public class UerDAO {
private SimpleConnectionMaker simpleConnectionMaker;
public UerDAO() {
**simpleConnectionMaker = new SimpleConnectionMaker();**
//상태를 관리하는게 아닌 한 번만 만들어 인스턴스 변수에 저장해두고
//메소드에서 사용하게 한다.
}
public void add(User user) throws SQLException {
Connection conn = simpleConnectionMaker.makeNewConnection();
PreparedStatement ps = conn.prepareStatement("INSERT INTO user(id, name, password) VALUES (?, ?, ?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
ps.executeUpdate();
ps.close();
conn.close();
}
public User get(String id) throws SQLException {
Connection conn = simpleConnectionMaker.makeNewConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
ps.close();
conn.close();
rs.close();
return user;
}
}
문제점
✅인터페이스의 도입
//ConnectionMaker **(커넥션 인터페이스)**
public interface ConnectionMaker {
public Connection makeConnection() throws SQLException, ClassNotFoundException;
}**//인터페이스 구현체**
//우리가 사용할 awsConnectionMaker
public class AWSConnectionMaker implements ConnectionMaker{
@Override
public Connection makeConnection() throws SQLException {
Map<String, String> env = System.getenv();
String dbHost = env.get("DB_HOST");
String dbUser = env.get("DB_USER");
String dbPassword = env.get("DB_PASSWORD");
Connection conn = DriverManager.getConnection(dbHost, dbUser, dbPassword);
return conn;
}
}
//예시로 구현한 LocalConnectionMaker
public class LocalConnectionMaker implements ConnectionMaker {
@Override
public Connection makeConnection() throws SQLException, ClassNotFoundException {
return null;
}
}//UserDao
public class UserDao {
private ConnectionMaker connectionMaker;
//인터페이스를 통해 오브젝트에 접근하므로 구체적인 클래스 정보를 알 필요가 없다.
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = new AWSConnectionMaker;
}
public void add(User user) throws SQLException, ClassNotFoundException {
**Connection conn = connectionMaker.makeConnection();
//인터페이스에 정의된 메소드를 사용하므로 클래스가 바뀐다해도 메소드
// 이름이 변경될 걱정이 없다**
PreparedStatement ps = conn.prepareStatement("INSERT INTO user(id, name, password) VALUES (?, ?, ?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
ps.executeUpdate();
ps.close();
conn.close();
}
public User get(String id) throws SQLException, ClassNotFoundException {
Connection conn = connectionMaker.makeConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
ps.close();
conn.close();
rs.close();
return user;
}
public void deleteAll() throws SQLException, ClassNotFoundException {
Connection conn = connectionMaker.makeConnection();
PreparedStatement ps = conn.prepareStatement("DELETE FROM users");
ps.executeUpdate();
conn.close();
ps.close();
}
public void getCount() throws SQLException, ClassNotFoundException {
Connection conn = connectionMaker.makeConnection();
PreparedStatement ps = conn.prepareStatement("SELECT COUNT(*) FROM users");
ResultSet rs = ps.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
ps.close();
conn.close();
}
}문제점
✅관계설정 책임의 분리
@Test
void 관계설정 책임의 분리() throws SQLException, ClassNotFoundException {
ConnectionMaker connectionMaker = new AWSConnectionMaker();
UserDao dao = new UserDao(connectionMaker); 위의 예제는 test코드로 클라이언트를 대체하였다.적용한 방법
✅제어의 역전(loC)
public class UserDaoFactory {
public UserDao awsUserDao(){
AWSConnectionMaker awsConnectionMaker = new AWSConnectionMaker();
UserDao userDao = new UserDao(awsConnectionMaker);
return userDao;
}
public UserDao localUserDao(){
UserDao userDao = new UserDao(new LocalConnectionMaker());
return userDao;
}
}제어의 역전
dependency 추가
스프링 loC의 용어 정리
빈(bean): IoC방식으로 관리하는 오브젝트 라는 뜻이다. 스프링이 직접 그 생성과 제어를 담당하는 오브젝트를 빈이라고 부른다.빈 팩토리(bean factory): 스프링의 IoC를 담당하는 핵심 컨테이너. 빈을 등록하고, 생성하고, 조회하고 돌려주고, 그 외에 부가적인 빈을 관리하는 기능을 담당한다.애플리케이션 컨텍스트(application context): 빈 팩토리를 확장한 IoC컨테이너이다. 빈을 등록화고 관리하는 기본적인 기능은 빈 팩토리와 동일하다.설정 정보(Configuration): 스프링의 설정정보란 애플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보를 말한다.public class UserDaoFactory {
@Bean
public UserDao awsUserDao(){
AWSConnectionMaker awsConnectionMaker = new AWSConnectionMaker();
UserDao userDao = new UserDao(awsConnectionMaker);
return userDao;
}
@Bean
public UserDao localUserDao(){
UserDao userDao = new UserDao(new LocalConnectionMaker());
return userDao;
}
}@Configuration : Bean Factory를 위한 Object 설정을 담당하는 Class를 인식시켜줌. 이 annotation이 붙은 Class를 스캔해서 ApplicationContext에 Bean을 등록.
@Bean : Object를 만들어 주는 Method에 붙임.
getBean : ApplicationContext에 등록된 Bean중 해당되는 것이 있다면, Bean 생성 Method를 호출하여 Object를 생성시켜 가져옴. "awsUserDao" : 찾고자 하는 Bean의 이름, UserDao.class : 원하는 리턴 타입. (기본적으로 Object 타입으로 리턴)
다만, getBean을 두번 이상 사용 했을 때, Object가 새로 생성 되는 것이 아니라, 최초에 생성된 Object를 리턴한다. 이를 Singleton pattern이라 한다.
Bean애너테이션을 통해 스프링에 등록했다.
```java
@ExtendWith(SpringExtension.class)
// Junit에서 Spring ApplicationContext를 쓸 수 있게 해주는 기능
@ContextConfiguration(classes = UserDaoFactory.class)
// Junit5 Test코드를 실행할 때 ApplicationContext에 들어갈 설정 정보(관계 설정)를 불러오게 해주는 기능
class UerDAOTest {
@Autowired
ApplicationContext context;
@Test
void 테스트() throws SQLException, ClassNotFoundException {
UserDao dao = context.getBean("awsUserDao", UserDao.class);
String id = "23";
dao.add(new User(id,"Suhwan","789456123"));
User selectedUser = dao.get(id);
Assertions.assertEquals("Suhwan",selectedUser.getName());
}
}
```
ExtendWith(SpringExtension.class) : Junit에서 Spring ApplicationContext를 쓸 수 있게 해주는 기능
ContextConfiguration : Junit5 Test코드를 실행 할 때 ApplicationContext에 들어갈 설정 정보(관계 설정)를 불러오게 해주는 기능
✅싱글톤 패턴
DaoFactory를 직접 사용하는 것과 @Configuration 애너테이션을 추가해서 스프링의 애플리케이션 컨텍스트를 통해 사용하는 것은 결과만 보자면 동일하지만 중요한 차이점이 있다.
직접 생성한 DaoFactory 오브젝트 → 호출될 때마다 달라지는 값
스프링 컨텍스트에서 호출한 오브젝트 → 항상 같은 값
즉, 스프링은 기본적으로 싱글톤 개념을 사용하기 때문이다.
싱글톤이란? 오브젝트를 한번 생성 해놓고 계속 사용하는 것을 말한다.
사용이유는? 계속 인스턴스를 생성하게 되면 서버에 부하가 온다. 서버의 부하를 줄이기 위해 사용한다.
싱글톤 패턴의 특징
private 로 만듦getInstance() 메소드를 통해 이미 만들어져 스테틱 필드에 저장해둔 오브젝트를 넘겨준다.싱글톤 패턴의 단점