자바 어플리케이션은 JDBC API를 이용하여 데이터계층과 통신을 합니다.
JDBC 인터페이스를 구현한 JDBC Driver라는 각각의 구현체를 통해서 데이터베이스와 연결할 수 있습니다.
@Slf4j
public class JDBCTest {
static final String JDBC_DRIVER = "org.h2.Driver";
static final String DB_URL = "jdbc:h2:~/test";
static final String USER = "sa";
static final String PASS = "";
static final String DROP_TABLE_SOL = "DROP TABLE customers IF EXISTS";
static final String CREATE_TABLE_SOL = "CREATE TABLE customers(id SERIAL, first_name VARCHAR(255), Last_name VARCHAR (255))";
static final String INSERT_SOL = "INSERT INTO customers (id, first_name, last_name) VALUES(1, 'honggu', 'kang')";
@Test
void makeJdbcSample() {
try {
Class.forName(JDBC_DRIVER);
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
log.info("GET CONNECTION");
// CONNECTION 획득
Statement statement = connection.createStatement();
// 데이터베이스와 통신하기위한 객체 Statement
statement.executeUpdate(DROP_TABLE_SOL);
statement.executeUpdate(CREATE_TABLE_SOL);
log.info("CREATED TABLE");
statement.executeUpdate(INSERT_SOL);
log.info("INSERTED CUSTOMER INFORMATION");
// STATEMENT 객체를 통해 QUERY 를 하게됩니다.
ResultSet resultSet = statement.executeQuery("SELECT * FROM customers WHERE id = 1");
// 조회성의 쿼리는 RESULT_SET이라는 객체를 반환합니다.
while (resultSet.next()) {
String fullName = resultSet.getString("first_name") + " " + resultSet.getString("last_name");
log.info("CUSTOMER FULL_NAME : {}", fullName);
}
// 반환된 쿼리정보를 사용합니다.
statement.close();
connection.close();
// 사용한 STATEMENT객체와 CONNECTION을 반납합니다.
} catch (Exception e) {
e.printStackTrace();
}
}
}
JDBC Template를 이용해서, 데이터 계층에 접근이 가능합니다.
기존 JDB를 이용했을때의 반복적인 작업을 JDBC Template이 대신 수행해줍니다.
반복적인 작업
Connection획득
Statement를 이용한 질의
ResultSet을 이용한 질의 결과 사용
Statement,Connection반납
@Slf4j
@SpringBootTest
public class JDBCTest {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void makeJdbcTemplateSample() {
jdbcTemplate.update(DROP_TABLE_SOL);
jdbcTemplate.update(CREATE_TABLE_SOL);
log.info("CREATED TABLE USING JDBC TEMPLATE");
jdbcTemplate.update(INSERT_SOL);
log.info("INSERTED CUSTOMER INFORMATION USING JDBC TEMPLATE");
String fullName = jdbcTemplate.queryForObject(
"SELECT * FROM customers WHERE id = 1",
(resultSet, i) -> resultSet.getString("first_name") + " " + resultSet.getString("last_name")
);
log.info("FULL_NAME : {}", fullName);
}
}
JDBC API를 직접 사용하는 것보다 코드양도 적어지고 해야하는 작업들도 적어집니다.
하지만 JDBC Template의 한계는 JAVA코드상에 SQL문을 작성하므로 코드와 쿼리가 섞이며 유지보수가 어려운 측면이 생기게 됩니다.
JDBC의 반복적인 작업을 쿼리매퍼인 Mybatis가 대신 수행해줍니다.
자바 코드와 쿼리를 분리해줍니다.
쿼리 수정으로 자바 코드 수정이나 컴파일 하는 작업을 하지 않아도 됩니다.
테이블의 모양과 객체의 모양이 매핑되며 동일성을 유지하며 관리하는 장점이 있습니다.
쿼리를 작성하지 않고서도 자바객체(엔티티)의 변경을 통해서 쿼리를 수행하는 장점을 가지고 있습니다.
따라서 JPA가 ORM 프레임워크라 불리는데 객체(Object)와 관계형 데이터베이스 테이블(Relation)을 매핑시켜주기 때문입니다.
spring:
h2:
console:
enabled: true
jpa:
generate-ddl: true
hibernate:
ddl-auto: create-drop
database: H2
show-sql: true
open-in-view: false
properties:
hibernate:
query.in_clause_parameter_padding: true
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
// 데이터 소스관련 빈
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:~/test");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
// jpa는 하나의 인터페이스로 여러 구현체들이 존재합니다.
// 그 중 대표적으로 많이 사용하는 구현체가 하이버네이트입니다.
// 어떤 구현체를 사용할지 결정하는 것이 JpaVendorAdapter 빈입니다.
@Bean
public JpaVendorAdapter jpaVendorAdapter(JpaProperties jpaProperties) {
// jpaProperties 빈에는 application.yml에 작성한 jpa의 속성들이 주입되어있습니다.
// 이를 밴더 어댑터에 세팅해줍니다.
AbstractJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setShowSql(jpaProperties.isShowSql());
jpaVendorAdapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
jpaVendorAdapter.setGenerateDdl(jpaProperties.isGenerateDdl());
return jpaVendorAdapter;
}
// Jpa에서 테이블과 매핑된 엔티티를 관리해주는 것이 엔티티 매니저입니다.
// 엔티티 매니저를 만들어내는 빈이 엔티티 매니저 팩토리 빈입니다.
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter, JpaProperties jpaProperties) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
// 테이블과 매핑된 엔티티들을 어떤 패키지에 두고 관리를 할것인지 명시합니다.
em.setPackagesToScan("com.kdt.lecture.domain");
em.setJpaVendorAdapter(jpaVendorAdapter);
Properties properties = new Properties();
properties.putAll(jpaProperties.getProperties());
em.setJpaProperties(properties);
return em;
}
// RDB의 트랜잭션을 관리하기위한 빈입니다.
@Bean
public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory.getObject());
return transactionManager;
}
참고글
DataSource