▸ 오늘의 코드카타
▸ JDBC
▸ ORM
2024년 3월 5일 - [프로그래머스 - 자바(JAVA)] 33 : 타겟 넘버 | 최솟값 만들기
void jdbcTest() throws SQLException {
//given
// docker run -p 5432:5432 -e POSTGRES_PASSWORD={비밀번호} -e POSTGRES_USER={이름} -e POSTGRES_DB=messenger --name postgres_boot -d postgres
// docker exec -i -t postgres_boot bash
// su - postgres
// psql --username yejin --dbname messenger
// \list (데이터 베이스 조회)
// \dt (테이블 조회)
// 각 어플리케이션에서 이 컨테이너에 접속하기 위해서는
// url 경로로 username과 password로 접속하겠다.
String url = "jdbc:postgresql://localhost:5432/messenger";
String username = "{이름}";
String password = "{비밀번호}";
//when
try {
Connection connection = DriverManager.getConnection(url, username, password);
String creatSql = "CREATE TABLE ACCOUNT (id SERIAL PRIMARY KEY, username varchar(255), password varchar(255))";
// 쿼리 실행
PreparedStatement statement = connection.prepareStatement(creatSql);
statement.execute();
// 제일 중요한 자원 해제
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//build.gradle
//PostgreSQL 의존성 추가
implementation 'org.postgresql:postgresql:42.2.27'
@Test
@DisplayName("JDBC 삽입/조회 실습")
void jdbcInsertSelectTest() throws SQLException {
// given
String url = "jdbc:postgresql://localhost:5432/messenger";
String username = "yejin";
String password = "pass";
// when
//자동으로 close(자원해제) 하는 방법 : try() -> 괄호 안에 connection 넣어주기
try (Connection connection = DriverManager.getConnection(url, username, password)) {
System.out.println("Connection created: " + connection);
String insertSql = "INSERT INTO ACCOUNT (id, username, password) VALUES ((SELECT coalesce(MAX(ID), 0) + 1 FROM ACCOUNT A), 'user1', 'pass1')";
try (PreparedStatement statement = connection.prepareStatement(insertSql)) {
statement.execute();
}
// then
String selectSql = "SELECT * FROM ACCOUNT";
try (PreparedStatement statement = connection.prepareStatement(selectSql)) {
var rs = statement.executeQuery();
while (rs.next()📌) {
System.out.printf("%d, %s, %s", rs.getInt("id"), rs.getString("username"),
rs.getString("password"));
}
}
}
}
📌
executeQuery()
는 JDBC에서 사용되는 메서드로, SELECT 쿼리를 실행하고 그 결과를 나타내는 ResultSet
객체를 반환한다.executeQuery()
메서드는 Statement
또는 PreparedStatement
인터페이스에서 호출할 수 있다. 이 메서드는 DB로부터 데이터를 검색할 때 사용된다.ResultSet
객체가 반환된다. 반환된 ResultSet
객체는 DB로부터 검색된 결과를 포함하며, 이를 통해 자바 애플리케이션에서 데이터를 읽을 수 있다.📌
rs
는 JDBC(Java Database Connectivity)를 통해 실행된 쿼리에 대한 결과 집합을 나타내는 객체이다. JDBC에서 ResultSet
인터페이스를 구현한 것이다.ResultSet
은 테이블로부터 데이터를 가져올 때 사용되며, 테이블의 각 행(row)에 대한 데이터를 나타낸다. 각 행은 열(column)의 집합으로 이루어져 있다.next()
, getInt()
, getString()
등이 있다.next()
: 결과 집합에서 다음 행으로 이동하고, 그 행이 존재하면 true를 반환한다. 만약 다음 행이 없다면 false를 반환한다.getInt()
, getString()
: 현재 행에서 지정된 열의 데이터를 가져온다. 열은 이름(String) 또는 인덱스(int)로 지정할 수 있다.while (rs.next())
는 결과 집합에서 다음 행이 존재하는 동안 반복하고, 각 행의 id, username, password 열의 데이터를 가져와 출력한다.@Test
@DisplayName("JDBC DAO 삽입/조회 실습")
void jdbcDAOInsertSelectTest() throws SQLException {
// given
AccountDAO accountDAO = new AccountDAO();
// when
var id = accountDAO.insertAccount(new AccountVO("new user", "new password"));
// then
var account = accountDAO.selectAccount(id);
assert account.getUsername().equals("new user");
}
public class AccountDAO {
//JDBC 관련된 변수 관리
private Connection conn = null;
private PreparedStatement stat = null;
private ResultSet rs = null;
private final String url = "jdbc:postgresql://localhost:5432/messenger";
private final String username = "yejin";
private final String password = "pass";
//SQL 쿼리
private final String ACCOUNT_INSERT = "INSERT INTO account(ID, USERNAME, PASSWORD) "
+ "VALUES((SELECT coalesce(MAX(ID), 0) + 1 FROM ACCOUNT A), ?, ?)";
private final String ACCOUNT_SELECT = "SELECT * FROM account WHERE ID = ?";
//CRUD 기능 메서드
public Integer insertAccount(AccountVO vo) {
var id = -1;
try {
String[] resultId = {"id"};
conn = DriverManager.getConnection(url, username, password);
stat = conn.prepareStatement(ACCOUNT_INSERT, resultId);
stat.setString(1, vo.getUsername());
stat.setString(2, vo.getPassword());
stat.executeUpdate();
try (ResultSet rs = stat.getGeneratedKeys()) {
if (rs.next()) {
id = rs.getInt(1);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return id;
}
public AccountVO selectAccount(Integer id) {
AccountVO vo = null;
try {
conn = DriverManager.getConnection(url, username, password);
stat = conn.prepareStatement(ACCOUNT_SELECT);
stat.setInt(1, id);
var rs = stat.executeQuery();
if (rs.next()) {
vo = new AccountVO();
vo.setId(rs.getInt("ID"));
vo.setUsername(rs.getString("USERNAME"));
vo.setPassword(rs.getString("PASSWORD"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return vo;
}
}
public class AccountVO {
private Integer id;
private String username;
private String password;
public AccountVO(String username, String password) {
this.username = username;
this.password = password;
}
public AccountVO() {
}
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getId() {
return id;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
// Team.java
@Entity
@Cacheable
public class Team {
@Id @GeneratedValue
private Long id;
...
}
# application.yml
spring.jpa.properties.hibernate.cache.use_second_level_cache: true
# 2차 캐시 활성화합니다.
spring.jpa.properties.hibernate.cache.region.factory_class: XXX
# 2차 캐시를 처리할 클래스를 지정합니다.
spring.jpa.properties.hibernate.generate_statistics: true
# 하이버네이트가 여러 통계정보를 출력하게 해주는데 캐시 적용 여부를 확인할 수 있습니다.
# appplication.yml
spring.jpa.properties.javax.persistence.sharedCache.mode: ENABLE_SELECTIVE
cache mode 종류
ALL 모든 엔티티를 캐시합니다. NONE 캐시를 사용하지 않습니다. ENABLE_SELECTIVE Cacheable(true)로 설정된 엔티티만 캐시를 적용합니다. DISABLE_SELECTIVE 모든 엔티티를 캐시하는데 Cacheable(false)만 캐시하지 습니다. UNSPECIFIED JPA 구현체가 정의한 설정을 따릅니다
(영속성 컨텍스트에 저장된 상태)
> flush() > (DB에 쿼리가 전송된 상태)
> commit() > (DB에 쿼리가 반영된 상태)
Item item = new Item(); // 1
item.setItemNm("테스트 상품");
EntityManager em = entityManagerFactory.createEntityManager(); // 2
EntityTransaction transaction = em.getTransaction(); // 3
transaction.begin();
em.persist(item); // 4-1
em.flush(item). // 4-2 (DB에 SQL 보내기/commit시 자동수행되어 생략 가능함)
transaction.commit(); // 5
em.close(); // 6
1 영속성 컨텍스트에 담을 상품 엔티티 생성
2 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
3 데이터 변경 시 무결성을 위해 트랜잭션 시작
4 영속성 컨텍스트에 저장된 상태, 아직 DB에 INSERT SQL 보내기 전
5 트랜잭션을 DB에 반영, 이 때 실제로 INSERT SQL 커밋 수행
6 엔티티 매니저와 엔티티 매니저 팩토리 자원을 close() 호출로 반환
키 생성전략이
generationType.IDENTITY
로 설정 되어있는 경우 생성쿼리는 쓰기지연이 발생하지 못한다.
-> 단일 쿼리로 수행함으로써 외부 트랜직션에 의한 중복키 생성을 방지하여 단일키를 보장한다.
Team teamA = new Team();
teamA.setName("TeamA");
em.persist(teamA); //persist -> 영속상태가 됨
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
Member member_A = new Member();
member_A.setName("memberA");
member_A.setTeam(teamA);
em.persist(member_A);
em.flush();
Member findMember = em.find(Member.class, member_A.getId());
Team findTeam= findMember.getTeam();
System.out.println(findTeam.getName());
flush가 있는 경우
create member
create team
insert team // flush로 인해 쓰기지연이 발생하지 않음
insert member // flush로 인해 쓰기지연이 발생하지 않음
print "TeamA" (memberA.getTeam())
flush가 없는 경우 -> em.flush()가 없는 경우 트랜직션이 끝날때까지 insert 쿼리가 실행되지 않음
create member
create team
print "TeamA" (memberA.getTeam()) // 쓰기 지연이 발생하더라도 영속성 컨텍스트에서 조회해옴(persist로 인해서 영속성 컨텍스트에 존재함)
insert team // 쓰기 지연이 발생한 부분
insert member // 쓰기 지연이 발생한 부분