preparedStatement는 다음과 같이 4가지 단계로 이루어져있다. 이중 parse 부분을 컴파일한채로 캐시에 저장하고 데이터가 bind될때까지 기다린다 이후 patch부분까지 실행이 완료되고 sql을 재사용할때 parse를 다시 실행하지않고 캐시에 저장되어있는 부분을 가져다 나머지 3단계만 실행한다. 따라서 sql이 반복적일때 효율적이다.
또한 바인딩 된 데이터는 SQL문법이 아닌 내부의 인터프리터나 컴파일 언어로 처리하므로, 문법적인 의미를 가질 수 없다. 이것이 의미하는것은 parse부분에서 이미 쿼리의 문법적인 처리부분이 선 수행되었으므로 바인딩 단계에서 입력된 부분은 쿼리로 인식하지 않는다는 의미이다.따라서 sql injection에 좋다.
사용 방법
String sql = "SELECT NAME, AGE FROM TABLE WHERE userID = ?"
PreparedStatement stmt = conn.prepareStatement(sql);
pstmt.setInt(1, userID);
ResultSet rst = pstmt.executeQuery();
#2) Statement의 동작방식
Statement는 prepare방식과 달리 4가지단계를 매번 실행하여 결과 값을 반환한다. 만약 반복적으로 쿼리를 수행할경우 PreparedStatement가 DB에 적은 부하를 주며, 성능이 더 좋다.
하지만 sql을 전체를 한눈에 볼 수 있다는 장점이 있다.
String sql = "SELECT NAME, AGE FROM TABLE WHERE USERID = " + userID
Statement stmt = conn.credateStatment();
ResultSet result = stmt.executeQuery(sqlstr);
2. SQL 삽입
사용자가 입력 폼 및 URL 입력란에 SQL을 삽입하여 DB로부터 정보를 열람하거나 조작할 수 있는 보안적 약점을 의미한다.
이를 대비하기위해 PreparedStatement를 이용하는것이 바람직하다 예를들어 다음과 같은 statement 쿼리문을 작성했다고 가정할때 사용자가 name값을 name' OR 'a' = 'a라고 입력할경우 모든 테이블이 조회가 되는 문제가 생긴다.
String query = "SELECT * FROM " + tableName + "WHERE Name = " + name;
따라서 이를 PreparedStatement(데이터 바인딩) 으로 처리하면 안전하게 SQL 삽입으로부터 대비할 수 있다. 보다 안전한 이유는 PreparedStatement를 보면 알 수 있다.
String query = "SELECT * FROM ? WHERE Name = ?";
stmt = con.prepareStatement(query);
stmt.setString(1, tableName);
stmt.setString(2, name);
rs = stmt.executeQuery();
@Controller
@RequestMapping(value = "/brokenSVC")
public class testController {
@Autowired
private BrokenService testService;
@GetMapping(value = "test")
public String home(Model model) {
System.out.println(testService.getdata().get(0).getDate());
return "testpage";
}
}
- Service 생성
다음과 같은 Service를 작성한다.
@Service
public class BrokenService {
@Autowired
private testDAO testdao;
public List<Test_Model> getData() {
return testdao.getData();
}
}
- DAO 생성
다음과 같은 DAO를 작성한다.
@Repository
public class BrokenDAO {
@Autowired private SqlSession sqlSession;
private static final String Namespace = "test.main.mapper.rmcMapper"; //rmcMapper가 있는 위치
public List<Test_Model> getData() {
return sqlSession.selectList(Namespace+".getData");
}
}
4. 결론
mybatis를 사용할 경우 기존 방법은 DAO에 쿼리문이 섞여있다는 단점이 있었는데 쿼리를 완벽하게 분리하여 사용할 수 있다는 장점이 있다.
지난 #8 강의에 이어 MYSQL과 연동하여 Update, delete, insert 하는 방법을 설명하도록 한다.
1. INSERT
먼저 지난번 query와 달리 update메소드를 사용하며 다음과 같이 사용한다 update메서드 내부에는 insert sql문, new Object로 name, email, text를? 에 넣어주는 방식으로 사용하며 이때 반환하는 값이 1이면 true를 리턴한다.
public boolean insert(Offer offer) {
String sqlStatement = "insert into offers (name, email, text) values (?,?,?)";
String name = offer.getName();
String email = offer.getEmail();
String text = offer.getText();
return (jdbcTemplateObject.update(sqlStatement, new Object[]{name, email, text}) == 1);
}
2. UPDATE
insert와 같이 update메소드를 사용하며 사용방법은 다음과 같다.
public boolean update(Offer offer) {
String sqlStatement = "update offers set name=?, email=?, text=? where id=?";
String name = offer.getName();
String email = offer.getEmail();
String text = offer.getText();
int id = offer.getId();
return (jdbcTemplateObject.update(sqlStatement, new Object[]{name, email, text, id}) == 1);
}
3. DELETE
마찬가지로 update 메소드를 사용하며 사용방법은 다음과 같다.
public boolean delete(int id) {
String sqlStatement = "delete from offers where id = ?";
return (jdbcTemplateObject.update(sqlStatement, new Object[]{id}) == 1);
}
DB에 접근하기 위해 다음 그림과 같이 DAO는 JDBC Template에 이는 JDBC Driver를 통해 Database에 접근하고 DataSource를 이용하여 DB에 실질적으로 커넥션 한다.
2. Data Source
데이터 베이스에 접근하기 위해 DataSource를 사용한다 DataSource의 종류는 다양하지만 DBCP라이브러리의 BasicDataSource를 통상 많이 사용하므로 실습에서도 이를 사용할 예정이다.
3. DAO란?
Data Access Object의 약자로서 서비스는 DB에 대해 아무것도 모르고, 실질적으로 DB에 접근하는 오브젝트를 의미한다. 쉽게 생각하여 DB의 CRUD를 실질적으로 수행하는 코드가 담긴 부분이다.
4. JDBC란?
JDBC란 DB와의 커넥션, SQL 사용, Close 커넥션 등 잡다한 일들을 해주는 프레임 워크이다. 기본 JAVA JDBC는 커넥션 등 try catch의 반복으로 반복적인 코드를 짜야하지만 Spring JDBC를 사용하면 SQL만 코드를 작성하고 나머지는 Spring 프레임워크에서 담당한다.
5. 실습하기
#1) 사전 준비
Mysql 5.7.28 설치
Mysql내부에는 csemall라는 DB, offers라는 테이블을 만들고 다음과 같은 구조로 이루어져 있다.
id(INT)
name
email
text
1
Alice
Alice@google.com
alice
2
Bob
Bob@google.com
bob
3
Jun
Jun@google.com
jun
#2) Simple Spring Maven 프로젝트 실행
다음과 같은 helloDB프로젝트를 생성한다.
#3) pom.xml 수정
다음과 같은 dependency를 추가하고 저장을 누르면 maven에 라이브러리가 추가된 것을 확인할 수 있다.
props라는 패키지를 생성하고 그 밑에 jdbc.properties라는 파일을 생성한다. 파일 내부는 다음과 같다 이는 DB의 정보를 담고 있으며, 생성할 bean에 하드 코딩하는 것보다 좋은 방법이다.
jdbc.username = root
jdbc.password = 사용자 비밀번호를 입력
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/csemall
#5) beans.xml 생성
다음과 같이 beans.xml을 생성한다 context:annotation은 DAO클래스에서 사용할 @Autowired를 활성화한다는 의미이며 context:component는 @Component 에노 테이션이 있는 어떤 패키지를 scan 할 건지 명시하는 부분이다. bean으로 생성한 dataSource는 #4에서 생성한 jdbc.properties를 사용하여 실제 DB와 커넥션을 하는 부분이다 dbcp2의 BasicDataSource클래스를 사용하고 있고 이 클래스는 DataSource인터페이스를 따르는 클래스이다. 마지막으로 context:property-placeholder는 앞서 사용할 jdbc.properties가 어떤 위치에 있는지 명시하는 부분이다.
src/main/java아래의 csemall이라는 패키지를 만들고 다음과 같은 Class를 생성한다. @Component 에노테이션은 이 클래스는 id가 offerDAO라는 bean을 하나 생성하겠다는 의미이다. 또한 JdbcTemplate를 사용하여 bean에 생성한 dataSource를 주입(@Autowired)시켜 DB와 실질적인 커넥션을 해주는 부분이 담겨있다. getRowCount() 부분은 다음과 같은 sql을 query 하고 결과는 Integer로 받는다는 의미이다.
package csemall;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component("offerDAO")
public class OfferDAO {
private JdbcTemplate jdbcTemplateObject; //얘는 new를 사용해됨 이후 DataSource는 DI를 통해 주입 시킬예정
@Autowired
public void setDataSource(DataSource dataSource) { //DataSource는 인터페이스인데 우리가 앞서 bean에 선언한 datasource가 이 인터페이스를 따르고 DI한다
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public int getRowCount() {
String sqlStatement = "select count(*) from offers";
return jdbcTemplateObject.queryForObject(sqlStatement, Integer.class);
}
}
#6) Offer.java 클래스 생성
Lombok을 이용해 @Getter, @Setter를 생성하였고 DB에 저장된 id, name, email, text를 받는다. 이는 추후에 사용될 클래스이다.