[LIKELION] 221024

고관운·2022년 10월 24일

회고

😄 느낀점

  • 토비의 스프링3 3장까지 공부했는데 지속적으로 복습하긴 했지만 처음부터 작업을 한다고 하면 어려움이 많을 것 같다. 내일 있을 복습을 확실하게 해야겠다.

😁 목표

  • 스터디 DP 유형 공부

알고리즘(k번째 수)

방법1 (Arrays 사용)

메소드

  • Arrays.copyOfRange(arr, x, y) : 배열을 x부터 y-1까지 복사해서 리턴
  • Arrays.sort : 오름차순 정렬

과정

  1. 크기가 commands의 길이인 answer배열 만들기
  2. commands에서 하나씩 불러옴
  3. Arrays.copyOfRange를 사용하여 범위를 잘라옴
  4. Arrays.sort로 정렬
  5. answer배열에 자른 배열의 k번째 수 넣기

코드

import java.util.Arrays;

public class KthNum01 {
    public int[] solution(int[] array, int[][] commands) {
        // commands의 길이만큼 answer 배열 생성
        int[] answer = new int[commands.length];
        int idx = 0;
        for (int[] command : commands) {
            // Arrays.copyOfRange(arr, x, y) : arr의 x부터 y-1값까지 복사
            int[] sliceArr = Arrays.copyOfRange(array, command[0]-1, command[1]);
            // 배열 정렬
            Arrays.sort(sliceArr);
            // answer[idx]에 값을 넣어준 후 idx+1
            answer[idx++] = sliceArr[command[2]-1];
        }
        return answer;
    }
}

방법2 (우선순위 큐 사용)

우선순위 큐 개념

  • 큐 ➡ 선입선출(FIFO), 우선순위 큐 ➡ 정렬됨
  • 사용 방법 : PriorityQueue<Integer> pq = new PriorityQueue<>();
  • pq.add(값) : 값을 pq에 넣음
  • pq.poll() : 우선순위 값을 빼서 반환

    pq.forEach(item-> System.out.println(item));
    👉 정렬 되지 않고 출력

    while(!pq.isEmpty())
         System.out.println(pq.poll());
    👉 우선순위에 맞게 정렬되어 출력

Spring 입문(DAO)

개념

  1. DI(의존성 주입)할 때 final을 쓰는 이유
    final : 한번 초기화하면 바꿀 수 없는 것

장점

  • 신뢰성 : 불변이기 때문에 변화를 고려하지 않아도 됨
  • Memory를 적게 씁니다. - 바뀔 여지가 없기 때문에 바뀌는데 필요한 메모리 할당이 필요 없습니다.
  • DI하고 나서 DataSource가 바뀌는 경우 - 무슨 일이 일어날지 예측이 안됨

이유

  • Spring에서 DI되었다면 이미 Factory에서 조립이 끝난 상태이므로 변화하지 않는 것이 좋다
  • 변화하지 않는 것이 좋으므로 final로 쓰는게 좋다. 왜냐하면 메모리 사용에 유리하고 신뢰성이 있기 때문
  • 이후 SpringBoot에서 @Autowired하는 부븐이 final로 대체하는 것을 권장하게 바뀜
  1. 익명 클래스를 왜 사용할까?
    클래스를 자꾸 만들면 많아지기 때문에 이렇게 한번만 쓰는 경우는 내부 클래스(익명)를 쓴다.

오늘 실습 과정

  1. DataSource 인터페이스 적용
  2. 익명클래스 도입(deleteAll, add)
  3. JdbcContext로 분리
  4. 템플릿 콜백 적용(UserDao 내에서 executeSql)
  5. executeSql을 JdbcContext로 옮기기
  6. 스프링의 JdbcTemplate사용(deleteAll, add 바꾸기)
  7. getCount는 queryForObject사용
  8. findbyId에도 사용(이것은 RowMapper를 추가로 넣어줌)
  9. getAll 추가 및 테스트 코드 추가
  10. findbyId와 getAll에 있는 RowMapper(중복됨)을 밖으로 빼줌

과정별 실습

DataSource 인터페이스 적용

  • AWSConnectionMaker → AWSDataSource
  • DataSource는 Connection을 만들 때 가장 많이 쓰는 Interface로 Java에 내장되어 있어서 따로 구현할 필요가 없음
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

import javax.sql.DataSource;
import java.util.Map;

@Configuration
public class UserDaoFactory {
    @Bean
    public UserDao awsUserDao(){
        return new UserDao(awsDataSource());
    }

    @Bean
    DataSource awsDataSource() {
        Map<String, String> env = System.getenv();
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(com.mysql.cj.jdbc.Driver.class);
        dataSource.setUrl(env.get("DB_HOST"));
        dataSource.setUsername(env.get("DB_NAME"));
        dataSource.setPassword(env.get("DB_PASSWORD"));
        return dataSource;
    }
}
🔴 UserDao 클래스의 ConnectionMake를 모두 DataSource로 바꿔줌
private DataSource dataSource;

public UserDao(DataSource dataSource) {
        this.dataSource = dataSource;
}

익명클래스 도입(deleteAll, add)

deleteAll

<public void deleteAll() throws SQLException, ClassNotFoundException {
        jdbcContextWithStatementStrategy(new StatementStrategy() {
            @Override
            public PreparedStatement makePreparedStatement(Connection conn) throws SQLException {
                return conn.prepareStatement("delete from users");
            }
        });
    }

add
🔴 add를 익명클래스로 적용하면 user를 생성자로 굳이 넘겨주지 않아도 됨

public void add(User user) throws SQLException, ClassNotFoundException {
        jdbcContextWithStatementStrategy(new StatementStrategy() {
            @Override
            public PreparedStatement makePreparedStatement(Connection conn) throws SQLException {
                PreparedStatement pstmt =  conn.prepareStatement("INSERT INTO users(id, name, password) VALUES (?, ?, ?)");
                pstmt.setString(1, user.getId());
                pstmt.setString(2, user.getName());
                pstmt.setString(3, user.getPassword());

                return pstmt;
            }
        });
    }

JdbcContext로 분리

jdbcContextWithStatementStrategy는 다른 Dao 예를들어 HospitalDao에서도 쓸 수 있음
👉 다른 Dao에서도 쓰기 위해 UserDao에서 분리하자

🔴 workContextWithStatementStrategy로 메소드명 변경
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JdbcContext {
    private final DataSource dataSource;

    public JdbcContext(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void workContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = dataSource.getConnection();
            ps = stmt.makePreparedStatement(conn);
            ps.executeUpdate();
        } catch (SQLException e) {
            throw e;
        } finally {
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                }
            }
        }
    }
}
🔴 UserDao에 추가 한 후 기존 jdbcContextWithStatementStrategy메소드를 workWithStatementStrategy로 변경
🔴 jdbcContext를 생성자로 초기화
private final JdbcContext jdbcContext;

public UserDao(DataSource dataSource) {
    this.dataSource = dataSource;
    this.jdbcContext = new JdbcContext(dataSource);
}

템플릿 콜백 적용(UserDao 내에서 executeSql)

🟢 deleteAll에서 sql문만 변수로 넣는 하나의 메소드 생성

    public void executeSql(final String query) throws SQLException {
        this.jdbcContext.workContextWithStatementStrategy(new StatementStrategy() {
            @Override
            public PreparedStatement makePreparedStatement(Connection conn) throws SQLException {
                return conn.prepareStatement(query);
            }
        });
    }
public void deleteAll<() throws SQLException, ClassNotFoundException {
        executeSql("delete from users");
}

executeSql을 JdbcContext로 옮기기

🟢 executeSql을 JdbcContext로 옮기기
🟢 UserDao의 deleteAll this.jdbcContext.executeSql("delete from users");로 수정

6~9 단계. JdbcTemplate 사용

  1. 스프링의 JdbcTemplate사용(deleteAll, add 바꾸기)
  2. getCount는 queryForObject사용
  3. findbyId에도 사용(이것은 RowMapper를 추가로 넣어줌)
  4. getAll 추가 및 테스트 코드 추가

🟢 queryForObject에 두번째 파라미터로 Integer.class를 넘겨줌으로써 int형의 데이터를 받아옴 (첫번째는 sql문)
🟢 RowMapper : 인터페이스 구현체로 ResultSet의 정보를 User에 매핑할 때 사용함

🔴 주의할 점
JdbcTemplate 적용 후 테스트 코드 실행 시 : 템플릿에서 예외처리를 해주기 때문에 UserDao 메소드의 throws 모두 지워주기

<import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import week5_221017_221021.day_221020.domain.User;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class UserDao {
    private final DataSource dataSource;
    private final JdbcTemplate jdbcTemplate;

    public UserDao(DataSource dataSource) {
        this.dataSource = dataSource;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public void add(User user) {
        jdbcTemplate.update("INSERT INTO users(id, name, password) values (?, ?, ?)",
                user.getId(), user.getName(), user.getPassword());
    }

    public User findbyId(String id) {
        String sql = "SELECT * from users where id = ?";
        RowMapper<User> rowMapper = new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password"));
                return user;
            }
        };
        return jdbcTemplate.queryForObject(sql, rowMapper, id);
    }

    public List<User> getAll() {
        String sql = "select * from users order by id";
        RowMapper<User> rowMapper = new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password"));
                return user;
            }
        };
        return jdbcTemplate.query(sql, rowMapper);
    }

    public void deleteAll() {
        this.jdbcTemplate.update("delete from users");
    }

    public int getCount() {
        return this.jdbcTemplate.queryForObject("select count(*) from users", Integer.class);
    }
}
<import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import week5_221017_221021.day_221020.domain.User;

import java.sql.SQLException;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = UserDaoFactory.class)
class UserDaoTest {
    @Autowired
    private ApplicationContext context;
    private UserDao userDao;
    private User user1;
    private User user2;
    private User user3;

    @BeforeEach
    public void setUp(){
        userDao = context.getBean("awsUserDao", UserDao.class);
        user1 = new User("1", "박성철", "1234");
        user2 = new User("2", "이길원", "2345");
        user3 = new User("3", "박범진", "3456");
    }


    @Test
    void addAndGet() throws SQLException, ClassNotFoundException {


        userDao.deleteAll();
        assertEquals(0, userDao.getCount());

        userDao.add(user1);
        assertEquals(1, userDao.getCount());

        userDao.add(user2);
        assertEquals(2, userDao.getCount());

        User selectedUser = userDao.findbyId(user1.getId());
        assertEquals(user1.getName(), selectedUser.getName());
    }

    @Test
    void userNull() {
        assertThrows(EmptyResultDataAccessException.class, ()->{
            userDao.findbyId("30");
        });
    }

    @Test
    @DisplayName("없을 때 빈 리스트를 리턴하는지, 있을 때 개수만큼 리턴하는지")
    void getAllTest() {
        userDao.deleteAll();
        List<User> users = userDao.getAll();
        assertEquals(0, users.size());
        userDao.add(user1);
        userDao.add(user2);
        userDao.add(user3);
        users = userDao.getAll();
        assertEquals(3, users.size());
    }
}

findbyId와 getAll에 있는 RowMapper(중복됨)을 밖으로 빼줌

🟢 rowMapper가 중복되므로 메소드 밖으로 빼줌
🟢 이후 findbyId, getAll에서 제거

    RowMapper<User> rowMapper = new RowMapper<User>() {
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password"));
            return user;
        }
    };
    public User findbyId(String id) {
        String sql = "SELECT * from users where id = ?";
        return jdbcTemplate.queryForObject(sql, rowMapper, id);
    }

    public List<User> getAll() {
        String sql = "select * from users order by id";
        return jdbcTemplate.query(sql, rowMapper);
    }

0개의 댓글