공유리소스 반환에 대한 문제 -> 예외 발생시점에서 리소스 반환이 안되는 경우 DB풀이 차기 때문에 문제가 발생할 수 있다. -> try/catch/finally 사용이 필수 -> 1.7이후 AutoClose 인터페이스 경우 : try~with~catch 구문을 활용하는게 이득 -> 리소스 반환은 역순으로 close()
2 변하는 것과 변하지 않는 것
변하는 부분과 변하지 않는 부분을 분리 (기능단위 메서드 분리) -> 템플릿메서드 패턴 활용 / 전략 패턴
전략패턴
public interface StatementStrategy {
PreparedStatement makePreparedStatement(Connection connection) throws SQLException;
}
서브클래스1 - DeleteAllStatement
public class DeleteAllStatement implements StatementStrategy {
@Override
public PreparedStatement makePreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement("delete from test");
return ps;
}
}
서브클래스2 - AddStatement
public class AddStatement implements StatementStrategy {
private User user;
public AddStatement(User user) {
this.user = user;
}
@Override
public PreparedStatement makePreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement("insert into test values (?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
return ps;
}
}
마이크로 DI 스프링컨테이너가 DI하는 것이 아니라 코드내에서 DI
3 JDBC 전략 패턴의 최적화
익맹내부 클래스 사용 / 로컬 클래스 사용
클래스파일을 줄일 수 있다
public void test_3_19(){
// 익명클래스 사용
jdbcContextWithStatementStrategy(new StatementStrategy() {
@Override
public PreparedStatement makePreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement("select * from test ");
return ps;
}
});
// 람다 (함수형인터페이스)
jdbcContextWithStatementStrategy(connection -> connection.prepareStatement("select * from test"));
}
4 컨텍스트와 DI
JdbcContext 분리
JdbcContext
public class JdbcContext {
private DataSource dataSource;
public JdbcContext(DataSource dataSource) { // DI
this.dataSource = dataSource;
}
public DataSource getDataSource() {
return dataSource;
}
// 전략패턴
public void workWithStatementStrategy(StatementStrategy strategy) {
try (Connection connection = dataSource.getConnection();
PreparedStatement ps = strategy.makePreparedStatement(connection)) {
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 분리 전
private void executeSql(String sql){
this.jdbcContext.workWithStatementStrategy(connection -> connection.prepareStatement("delete from test"));
}
// 분리 후 (변경되는 부분 -> sql 만 분리)
public void deleteAll() throws SQLException{
executeSql("delete from test");
}
// 메서드 분리 -> 변하지 않음
private void executeSql(String sql){
this.jdbcContext.workWithStatementStrategy(connection -> connection.prepareStatement(sql));
}
템플릿/콜백 응용
public class Calculator {
public Number calcSum(Path path) {
try(BufferedReader br = Files.newBufferedReader(path)) {
OptionalInt reduce = br.lines()
.flatMapToInt(s -> IntStream.of(Integer.parseInt(s)))
.reduce((left, right) -> left + right);
return reduce.getAsInt();
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
}
@Test
public void test_3_30() throws URISyntaxException {
Calculator calculator = new Calculator();
Path path = Paths.get("numbers.txt");
Number number = calculator.calcSum(path);
System.out.println("number = " + number);
}
public Number fileReadTemplate(Path path, Function<BufferedReader, OptionalInt> function) {
try (BufferedReader br = Files.newBufferedReader(path)) {
return function.apply(br).getAsInt();
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
public Number calcSum2(Path path, IntBinaryOperator op) {
return fileReadTemplate(path, br -> br.lines()
.flatMapToInt(s -> IntStream.of(Integer.parseInt(s)))
.reduce(op));
}
@Test
public void test_3_35() {
Calculator calculator = new Calculator();
Path path = Paths.get("numbers.txt");
Number number = calculator.calcSum2(path, (left, right) -> left * right);
System.out.println("number = " + number);
}
제네릭스를 이용한 방법
// 사용은 책 참고
public interface LineCallBack<T> {
T doSomething(String line, T value);
}
6 스프링의 JdbcTemplate
위에서 만든 JdbcContext 클래스 대신 사용
public class UserDao {
private DataSource dataSource;
private JdbcTemplate JdbcTemplate;
...
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
this.JdbcTemplate = new JdbcTemplate(this.dataSource);
}
// JdbcTemplate 사용 -> JdbcContext와 동일 구조
public void deleteAll2() {
jdbcTemplate.update(connection -> connection.prepareStatement("delete from test"));
}
// sql문만 넘김 -> 내장 콜백 사용
public void deleteAll3() {
jdbcTemplate.update("delete from test");
}
// add 기존 -> AddStatement 전략 클래스
@Override
public PreparedStatement makePreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement("insert into test values (?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
return ps;
}
// add
public void add2(User user) {
jdbcTemplate.update("inset into test values(?,?,?)",
user.getId(), user.getName(), user.getPassword());
}
}
// Add 테스트 (JdbcTemplate)
@Test
public void test_3_47() {
userDao.add2(new User("1", "so", "1234"));
}
// Delete 테스트 (JdbcTemplate)
@Test
public void test_3_46() {
userDao.deleteAll2();
}
queryForInt -> deprecated
// 내장 콜백 x
public int getCount() {
return jdbcTemplate.query(
connection -> connection.prepareStatement("select count(*) from test"), // 첫번째 파라미터 : PreparedStatementCreator
resultSet -> { // 두번째 파라미터 : ResultSetExtractor
if (resultSet.next()) {
return resultSet.getInt(1);
}
return 0;
});
}
// 디프리케이트 돼서 queryForInt -> queryForObject로 수정
// 내장콜백 사용
public int getCount2() {
return jdbcTemplate.queryForObject("select count(*) from test", Integer.class);
}
queryForObject
RowMapper
// 1.8이전 기준
public User get2(String id) {
return jdbcTemplate.queryForObject("select * from test where id = ?",
new Object[]{id},
new RowMapper<User>() {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User();
user.setId(resultSet.getString("id"));
user.setName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
return user;
}
});
}
// 1.8이상
public User get22(String id) {
return jdbcTemplate.queryForObject("select * from test where id = ?",
new Object[]{id},
(resultSet, i) -> {
User user = new User();
user.setId(resultSet.getString("id"));
user.setName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
return user;
}
);
}
getAll
query() 같은 경우 데이터가 없으면 빈값을 리턴해줌
queryForObject() 같은 경우는 데이터가 없는 경우 예외발생
public List<User> getAll() {
return jdbcTemplate.query("select * from test order by id",
(resultSet, i) -> {
User user = new User();
user.setId(resultSet.getString("id"));
user.setName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
return user;
});
}
리팩토링 (중복되는 부분 제거)
```java // 중복되는 부분 메서드 추출 private RowMapper getRowMapper() { return (ResultSet rs, int rowNum) -> { User user = new User(); user.setId(rs.getString("id")); user.setName(rs.getString("name")); user.setPassword(rs.getString("password")); return user; }; }
public List getAllRefactoring() { return jdbcTemplate.query("select * from test order by id", getRowMapper() // private 사용 ); }
@Test public void test_3_56() { System.out.println(userDao.getAllRefactoring()); // 리팩토링 후 맞는지 확인 Assert.assertThat(userDao.getAll().toString(), CoreMatchers.is(userDao.getAllRefactoring().toString())); }