(스프링JDBC와 Spring Data JPA비교)Spring JDBC와 JPA를 간단한 CRUD 예제로 만들면서...
Transcript of (스프링JDBC와 Spring Data JPA비교)Spring JDBC와 JPA를 간단한 CRUD 예제로 만들면서...
0. 스프링 부트, JPA에서 데이터베이스 초기화
스프링 부트에서는 클래스패스 경로에(src/main/resources등) schema.sql, data.sql,
schema-${platform}.sql, data-${platform}.sql 파일등이 존재한다면 자동으로 실행해서 스키
마 구조와 데이터를 초기화 시켜주는데. ${platform} 값은, 만약 application.properties 파
일에서 spring.datasource.platform=mysql 이라고 했다면 schema-${platform}.sql 파일의
이름은 schema-mysql.sql이 될 것이다.
JPA는 시작시점에 DDL을 자동 생성 및 실행해 주는 기능이 있는데 두개의 외부 속성으로 정
의한다. spring.jpa.generate-ddl (boolean) on, off 값을 가지고 DB벤더에 종속적이지 않으며
spring.jpa.hibernate.ddl-auto (enum)는 하이버네이트 특성으로 하이버네이트 Sessionfactory가
시작할 때 JPA의 엔티티 매핑, 연관관계 설정을 기본으로 테이블과 같은 스키마 생성 스크립
트를 만들고 실행하여 데이터베이스 초기화를 지원하는데 열거형 값인 none, validate, update,
create, create-drop 값을 가진다.
hsqldb, h2 and derby 데이터베이스는 create-drop이 기본이며 그외 DB는 none이 기본값이다.
none : 자동 DDL 생성 안함.
create : 하이버네이트 Sessionfactory 가 시작될 때 항상 다시 생성, 이미 있다면 지우고
생성.
create-drop : Sessionfactory 가 시작될 때 생성 후 종료할 때 삭제한다.
update : Sessionfactory 가 시작될 때 엔티티 클래스(도메인 클래스)와 DB 에 생성된 스키마
구조를 비교해서 DB 쪽에 생성이 안된 테이블 또는 칼럼이 있다면 DB 스키마를 변경해서
생성시키지만 기 생성된 스키마 구조를 삭제하지는 않는다.
validate : Sessionfactory 가 시작될 때 엔티티 클래스(도메인 클래스)와 DB 에 생성된 스키마
구조를 비교해 같은지 확인만 할 뿐 DB 스키마 구조는 변경하지 않고 만약 다르다면 예외를
발생시킨다.
1. jdbcTemplate을 이용한 CRUD 예제
Spring Boot, MariaDB를 이용해서 EMP 테이블을 만들고 JdbcTemplate을 이용하여 CRUD 기
능을 구현해 보자.
STS에서
File -> New -> Project -> Spring Starter Project
Name : jdbc2
Package : jdbc
다음화면에서 SQL : JDBC, MySQL 선택
MariaDB에서 직접 SQL을 작성하여 테이블 및 데이터를 생성할 수 있지만 스프링 부트
에서는 클래스패스 경로에 schema.sql, data.sql이 존재하면 자동실행 하므로 스키마 생성
부분과 데이터 생성부분을 파일로 만들어두면 된다.
[src/main/resources/schema.sql]
drop database if exists jdbc2;
create database jdbc2;
use jdbc2;
create table emp
(
empno int(4) not null auto_increment,
ename varchar(50),
primary key (empno)
) ENGINE=InnoDB;
[src/main/resources/data.sql(파일 속성에서 text encoding을 UTF-8로)]
insert into emp(ename) values ('1길동');
insert into emp(ename) values ('2길동');
insert into emp(ename) values ('3길동');
[src/main/resources/application.properties]
spring.datasource.platform=mysql
spring.datasource.url=jdbc:mysql://localhost/jdbc2?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=1111
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.sql-script-encoding=UTF-8
#커넥션풀에서 커넥션을 가져올 경우 커넥션이 유효한지 검사
도메인 클래스(Emp.java) – 테이블구조와 동일하다.
package jdbc.domain;
public class Emp {
private Long empno;
private String ename;
public Emp() { }
public Emp(String ename) { this.ename = ename; }
public Emp(Long empno, String ename) {
this.empno = empno;
this.ename = ename;
}
public Long getEmpno() { return empno; }
public void setEmpno(Long empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String toString() { return "[empno=" + empno +",ename=" + ename + "]"; }
}
RowMapper 구현체(EmpRowMapper.java)
package jdbc.repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import jdbc.domain.Emp;
@Repository
public class EmpRowMapper implements RowMapper {
@Override
public Emp mapRow(ResultSet rs, int rowNum) throws SQLException {
Long empno = rs.getLong("empno");
String ename = rs.getString("ename");
return new Emp(empno, ename);
}
}
Repository 인터페이스(EmpRepository.java) – 영속성 서비스용 인터페이스
package jdbc.repository;
import java.util.List;
import jdbc.domain.Emp;
public interface EmpRepository {
List<Emp> findAll();
Emp findOne(Long empnno);
Emp save(Emp emp);
void delete(Long empno);
}
Repository 구현체(EmpRepositoryImpl.java) – 영속성 서비스용 구상클래스
package jdbc.repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import jdbc.domain.Emp;
@Repository
@Transactional(readOnly=true)
public class EmpRepositoryImpl implements EmpRepository {
private SimpleJdbcInsert jdbcInsert;
private JdbcTemplate jdbcTemplate;
@Autowired
RowMapper<Emp> empRowMapper;
@Autowired //스프링부트에서 DataSource를 자동 주입해 준다.
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public List<Emp> findAll() {
List<Emp> emps = jdbcTemplate.query("select empno, ename from
emp",empRowMapper);
return emps;
}
@Override
public Emp findOne(Long empno) {
return (Emp)jdbcTemplate.queryForObject("select empno, ename from emp
where empno = ?", empRowMapper, empno);
}
@Override
@Transactional(readOnly=false)
public Emp save(Emp emp) {
SqlParameterSource param = new BeanPropertySqlParameterSource(emp);
if (emp.getEmpno() == null) {
Number key = jdbcInsert.executeAndReturnKey(param);
emp.setEmpno(key.longValue());
}
else {
this.jdbcTemplate.update(
"insert into emp (empno, ename) values (?, ?)",
emp.getEmpno(), emp.getEname()
);
}
return emp;
}
@Override
@Transactional(readOnly=false)
public void delete(Long empno) {
this.jdbcTemplate.update(
"delete from emp where empno = ?",
empno
);
}
//생성자가 실행된 후에 실행된다.
@PostConstruct
public void init() {
//INSERT SQL Auto Create
jdbcInsert = new
SimpleJdbcInsert( jdbcTemplate).withTableName("emp").usingGeneratedKeyColumns("emp
no");
}
}
스프링 부트 메인(Jdbc2Application.java)
package jdbc;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import jdbc.domain.Emp;
import jdbc.repository.EmpRepository;
@SpringBootApplication
public class Jdbc2Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Jdbc2Application.class, args);
}
@Autowired
EmpRepository empRepository;
public void run(String...args) {
//전체 사원 SELECT
List<Emp> emps = empRepository.findAll();
for(Emp e : emps) { System.out.println(e); }
System.out.println("---------------------");
//2번 사원 SELECT
Emp e = empRepository.findOne(2L);
System.out.println(e);
System.out.println("---------------------");
//3번 사원 DELETE
empRepository.delete(3L);
emps = empRepository.findAll();
for(Emp e1 : emps) { System.out.println(e1); }
System.out.println("---------------------");
//4번 사원 INSERT
e = empRepository.save(new Emp(4L, "4길동"));
emps = empRepository.findAll();
for(Emp e1 : emps) { System.out.println(e1); }
System.out.println("---------------------");
//'5길동' 사원 INSERT
Emp e5 = new Emp(“5길동”);
e = empRepository.save(e5);
emps = empRepository.findAll();
for(Emp e1 : emps) { System.out.println(e1); }
}
}
[결과]
2. Spring Data JPA를 이용한 CRUD 예제
이전의 예제를 Spring Data JPA를 이용한 형태로 변형하여 작성해 보자.
간단히 Spring Data JPA 구조, 기본적인 CRUD인 경우 쿼리를 직접 만들지 않아도 된다는 것
만 확인하자.
마리아DB는 아래 URL을 참조하여 설치하자.
http://ojc.asia/bbs/board.php?bo_table=LecSpring&wr_id=524
STS에서 File -> New -> Project -> Spring Starter Project
Name : springjpa, Package name : jpa
다음화면에서 SQL-> JPA, MySQL 선택
src/main/resources/data.sql(파일 속성에서 text encoding을 UTF-8로)
insert into emp(ename) values ('1길동');
insert into emp(ename) values ('2길동');
insert into emp(ename) values ('3길동');
Spring Data JPA는 테이블을 자동으로 생성하므로 이전 예제에서 작성한 schema.sql은 필요 없다.
src/main/resources/application.properties(파일 속성에서 text encoding을 UTF-8로)
spring.datasource.platform=mysql
spring.datasource.url=jdbc:mysql://localhost/emp2?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=1111
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.sql-script-encoding=UTF-8
# 자동으로 DDL을 만들어 테이블 생성
spring.jpa.hibernate.ddl-auto=create
# 실행되는 SQL문을 로그에서 보이도록
spring.jpa.show-sql=true
도메인 클래스(Emp.java) – 테이블구조와 동일하다.
package jpa.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Emp {
@Id //PK임을 지정
@GeneratedValue //자동증분 칼럼
private Long empno;
//@Column 어노테이션이없더라도 테이블 칼럼명과 자동 매핑
private String ename;
public Emp() { }
public Emp(String ename) { this.ename = ename; }
public Emp(Long empno, String ename) { this.empno = empno; this.ename = ename; }
public Long getEmpno() { return empno; }
public void setEmpno(Long empno) { this.empno = empno; }
public String getEname() { return ename; }
public void setEname(String ename) { this.ename = ename; }
public String toString() { return "[empno=" + empno +",ename=" + ename + "]";}
}
Repository 인터페이스(EmpRepository.java) – 영속성 서비스용 인터페이스
package jpa.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import jpa.domain.Emp;
/*
* JpaRepository에는 기본적인 CRUD(findAll, findOne, save, delete, deleteAll) 메소드가
* 정의되어 있으며 이를 상속한 인터페이스를 만듦으로써 구현클래스 없이 레포지터리 구성이
가능하다. 제너릭으로 도메인 클래스와 키칼럼(ID칼럼)의 타입을 기술하면 된다.
*/
public interface EmpRepository extends JpaRepository<Emp, Long> { }
스프링 부트 메인(SpringjpaApplication.java)
package jpa;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import jpa.domain.Emp;
import jpa.repository.EmpRepository;
@SpringBootApplication
public class SpringjpaApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringjpaApplication.class, args); }
@Autowired
EmpRepository empRepository;
public void run(String...args) {
//전체 사원 SELECT
List<Emp> emps = empRepository.findAll();
for(Emp e : emps) { System.out.println(e); }
System.out.println("---------------------");
//2번 사원 SELECT
Emp e = empRepository.findOne(2L);
System.out.println(e);
System.out.println("---------------------2번사원 SELECT");
//3번 사원 DELETE
empRepository.delete(3L);
emps = empRepository.findAll();
for(Emp e1 : emps) { System.out.println(e1); }
System.out.println("---------------------3번 DELETE후");
//4번 사원 INSERT
e = empRepository.save(new Emp(4L, "4길동"));
emps = empRepository.findAll();
for(Emp e1 : emps) { System.out.println(e1); }
System.out.println("---------------------4번 INSERT후");
//'5길동' 사원 INSERT
Emp e5 = new Emp(“5길동”);
e = empRepository.save(e5);
emps = empRepository.findAll();
for(Emp e1 : emps) {
System.out.println(e1);
}
System.out.println("---------------------5번 INSERT후");