DongDD's IT

[Spring] Datasource, Spring JDBC, Transaction 본문

프로그래밍/Spring

[Spring] Datasource, Spring JDBC, Transaction

DongDD 2019. 3. 27. 21:00

[Spring] Datasource, Spring JDBC, Transaction



Data Source


- Application이 database에 접근하기 위한 추상화된 연결 방식


종류


1. Application module이 제공하는 Datasource

- 서드파티가 제공하는 datasource나 스프링 프레임워크가 제공하는 datasource 사용

- DB 정보를 Application이 관리


2. Application Server가 제공하는 Datasource

- JNDI를 이용해 가져와 사용하는 방식

- DB 정보를 Server가 관리(Application과 분리)

JNDI(Java Naming and Directory Interface) : Java가 제공하는 directory, naming service


3. 내장 database를 사용하는 Datasource

- Application 구동 시, datasource 설정과 생성이 자동으로 일어남


Data Source 설정


1. Spring에서 제공하는 DriverManagerDataSource 설정

- Data source 설정 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@PropertySource("classpath:aa.properties")
public class driver {
    @Value("${database.url}")
    private String url;
 
    @Value("${database.username}")
    private String username;
 
    @Value("${driverClassName}")
    private String driverClassName;
 
     @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        return dataSource;
    }
}
 
 
cs

- properties 파일에 사용할 정보 저장

- @Value()를 통해 가져옴

- 사용할 datasource를 생성하고 각 정보 설정 후 반환


2. JNDI 설정

- JNDI 설정 코드

1
2
3
4
5
6
7
8
@Configuration
public class jndi {
    @Bean
    public DataSource dataSource() throws NamingException {
        JndiTemplate jndiTemplate = new JndiTemplate();
        return jndiTemplate.lookup("java:resource", DataSource.class);
    }
}
cs

- lookup을 통해 "resource"명을 가진 resource를 찾아옴


3. 내장형 데이터베이스 사용

- Embedded DB 설정 코드

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class Embedded {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.DERBY)
                .setScriptEncoding("UTF-8")
                .addScripts("/resource/test.sql")
                .build();
    }
}
cs

- Type 설정

embeddedDB

- Encoding 설정

- 실행 시, 실행 될 Script 추가



스프링 JDBC


- 반복적으로 수행되는 JDBC 처리를 Spring framework가 제공

→ Connection

→ SQL 문 실행

→ 예외 처리


JdbcTemplate


- SQL로 데이터베이스에 접근할 수 있게 도와주는 클래스

- DI로 주입받아 사용


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Jdbc  
    @Autowired
    private JdbcTemplate jdbcTemplate;
 
    private int id = 10;
 
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
 
    public String execSql() {
        String sql = "SELECT * FROM table where id=?";
        return jdbcTemplate.queryForObject(sql, String.class, id);
    }
}
cs

- 사용할 datasource를 지정

- sql문을 작성하고 "?"를 이용해 bind

- queryForObject 메소드를 사용하여 sql문을 실행하여 db에 접속


DAO(Data Access Object)


- 실제로 DB에 접근하기 위한 객체

- DB의 데이터를 조회, 조작하는 기능

- SQL문을 사용하여 DB의 CRUD 기능 제공


1. DAO 생성


1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class TestDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
 
    public String getName() {
        return jdbcTemplate.queryForObject("SELECT name FROM table"String.class);
    }
 
    public Integer getId() {
        return jdbcTemplate.queryForObject("SELECT id from table",Integer.class);
    }
}
cs

- @Component로 TestDao 생성

- method에 JdbcTemplate를 이용하여 쿼리문 실행


2. DAO 사용


1
2
3
4
5
6
7
8
9
@Autowired
TestDao testDao;
 
public void test() {
    testDao.getId();
    testDao.getName();
}
 
 
cs

- DI 컨테이너의 주입을 받은 후, 사용


NamedParameterJdbcTemplate


- JdbcTemplate에서 "?"를 이용해 bind했던 것을 map 또는 SqlParameterSource을 이용해 bind할 수 있음


1. NamedParameterJdbcTemplate DAO


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class TestDao1 {
    @Autowired
    NamedParameterJdbcTemplate namedParameterJdbcTemplate;
     
    public String getName(Integer id) {
//        MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource();
//        mapSqlParameterSource.addValue("id",id);
        Map<String, Object> map = new HashMap<>();
        map.put("id",id);
        return namedParameterJdbcTemplate
                .queryForObject("SELECT name FROM table WHERE id=:id",map,String.class);
    }
}
cs

- :변수명 으로 bind할 지점 생성

- map으로 원하는 값을 넣어 sql문 실행


2. SQL 결과 확인

1) 결과 row가 한개인 경우

- Map<String, Object>를 이용하여 조회


2) 결과 row가 여러개인 경우

- List<Map<String,Object>>를 이용하여 조회


3) 결과 row가 없는 경우

- queryForObject 메소드 : 빈 List 반환

- 나머지 메소드 : EmptyResultDataAccessException 처리


3. CRUD


1
2
3
public void setName(String name) {
    return namedParameterJdbcTemplate.update(sql, param);
}
cs

- insert, update, delete 모두 update 메소드를 사용


POJO로 결과 받기


- POJO(Plain Old Java Object) : 특정 규약, 환경에 영향 받지 않고 다른 객체에 영향(상속, 구현체)을 받지 않는 value성 오브젝트


1. POJO 변환 인터페이스

1) RowMapper

- Result set을 순차적으로 읽으며 POJO 객체로 변환

- 행에 대한 커서 제어는 Spring framework가 수행

- RowMapper implementation

1
2
3
4
5
6
7
8
9
public class TestRowMapper implements RowMapper<TestPOJO> {
    @Override
    public TestPOJO mapRow(ResultSet rs, int rowNum) throws SQLException {
        return TestPOJO.builder()
                .user_id(rs.getInt("user_id"))
                .user_name(rs.getString("user_name"))
                .build();
    }
}
cs

- DB에서 가져온 값을 TestPOJO 객체를 생성하여 넣고 반환


- DAO class 생성

1
2
3
4
5
6
7
8
9
10
11
@Component
public class RowMapperDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
     
    public TestPOJO getTestPojo(Integer id) {
        TestRowMapper testRowMapper = new TestRowMapper();
        return jdbcTemplate.queryForObject("SELECT * FROM table WHERE user_id=?",
                testRowMapper,id);
    }
}
cs


2) ResultSetExtractor

- Result set을 자유롭게 제어하면서 원하는 POJO 형태로 매핑

- ResultSetExtractor Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestResultSetExtractor implements ResultSetExtractor<List<TestPOJO>> {
    @Override
     public List<TestPOJO> extractData(ResultSet resultSet) throws SQLException {
        Map<Integer, TestPOJO> map = new LinkedHashMap<>();
        TestPOJO testPOJO;
        while(resultSet.next()) {
            Integer userId = resultSet.getInt("user_id");
            testPOJO = map.get(userId);         
           if(testPOJO == null) {
                map.put(userId, TestPOJO.builder()
 
                        .user_id(userId)
                        .user_name(resultSet.getString("user_name"))
                        .build());
            }
        }
        if(map.size() == 0){
            throw new EmptyResultDataAccessException(1);
        }
        return new ArrayList<>(map.values());
    }
}
 
 
cs

- DB에서 가져온 각 row를 List로 변환하여 반환


- DAO class 생성

1
2
3
4
5
6
7
8
9
10
11
@Component
public class ResultSetExtractorDao {
     
    @Autowired
    JdbcTemplate jdbcTemplate;
     
    public List<TestPOJO> getTestPojoAll() {
        TestResultSetExtractor testResultSetExtractor = new TestResultSetExtractor();
        return jdbcTemplate.query("SELECT * FROM table",testResultSetExtractor);
    }
}
cs


3) RowCallbackHandler

- Result set을 반환하기 위한 것이 아니라 다른 처리를 하고 싶을 때 사용

- 반환 값이 없음

- RowCallbackHandler Implementation

1
2
3
4
5
6
7
8
public class TestRowCallbackHandler implements RowCallbackHandler {
 
    @Override
    public void processRow(ResultSet rs) throws SQLException {
        // Process
        // no return;
    }
}
cs

- 반환 값이 없고 특정 process를 처리할 때 사용


- RowCallbackHandler DAO

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class RowCallbackHandlerDao {
 
    @Autowired
    JdbcTemplate jdbcTemplate;
 
    public void processTestPojo() {
        TestRowCallbackHandler testRowCallbackHandler = new TestRowCallbackHandler();
        jdbcTemplate.query("SELECT * FROM table",testRowCallbackHandler);
    }
}
 
cs


데이터 일괄 처리


1. Batch process

- 대량의 데이터 조작 시, SQL문을 각각 실행하지 안하고 배치 형태로 모아서 실행

- batchUpdate 메소드 사용(JdbcTemplate)


2. Stored Procedure 

- stored procedure를 사용

- call, execute 메소드 사용(JdbcTemplate)



Transaction


Transcation 관리


- 선언적(declarative) 방법 : annotation을 사용해 관리

- 프로그램적(programmatic), 명시적 방법 : commit, rollback method를 사용해 관리


Spring Transaction


1. 선언적(declarative) 트랜잭션

- 정해진 룰에 따라 트랜잭션 제어

- 트랜잭션의 시작과 커밋, 롤백등의 처리를 비즈니스 로직에 삽입할 필요가 없음

- 메소드에 @Transactional을 추가하여 메소드 시작, 종료 시점에 트랝개션 관리

- 메소드 안에서 예외 발생 시, 자동으로 롤백


1
2
3
4
5
6
7
8
9
10
@Service
public class TestPojoService {
    @Autowired
    private RowMapperDao rowMapperDao;
          
    @Transactional(readOnly = true)
    public TestPOJO getTestPojo(Integer userId) {
        return rowMapperDao.getTestPojo(userId);
    }   
}
cs

- 클래스에 @Transactional을 추가하면 클래스 내부에 모두 적용

- 메소드에 @Transactional을 추가하면 해당 메소드에 적용


- 트랜잭션 관리자 설정 파일

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    @Autowired
    DataSource dataSource;
 
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager();
    }
}
cs


2. 명시적(프로그램적) 트랜잭션

- 소스 코드에 직접 롤백, 커밋을 기술하여 사용하는 방법

- 메소드 보다 더 작은 단위를 제어하거나 선언적으로 처리하기 힘든 부분을 제어할 때 사용


1) PlatformTransactionManager

- 트랜잭션 직접 제어

- TransactionDefinition과 TransactionStatus 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Service
public class Programmatic {
    @Autowired
    PlatformTransactionManager platformTransactionManager;
 
    @Autowired
    RowMapperDao rowMapperDao;
 
    public void insertTestPojo(TestPOJO testPOJO) {
        DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
        defaultTransactionDefinition.setName("testTransaction");
        defaultTransactionDefinition.setReadOnly(false);
        defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        defaultTransactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        TransactionStatus transactionStatus = platformTransactionManager
                                    .getTransaction(defaultTransactionDefinition);
        try{
            //insert code
        }
        catch (Exception e) {
            platformTransactionManager.rollback(transactionStatus);
            throw new DataAccessException("error", e){};
        }
        platformTransactionManager.commit(transactionStatus);
    }
}
cs

- setName : 트랜잭션 이름 설정

- setReadOnly : ReadOnly 설정

- setPropagationBehavior : 트랜잭션의 전파 방식 설정

propagation

- setIsolationLevel : 트랜잭션의 격리 수준 설정

isolation


2) TransactionTemplate

- PlatformTransactionManager 보다 구조적으로 트랜잭션 제어 기술

- TransactionCallback 인터페이스가 제공하는 메소드에 트랜잭션 제어 작업 구현

- TransactionTemplate의 execute 메소드에 인수로 전송


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class Programmatic2 {
    @Autowired
    TransactionTemplate transactionTemplate;
 
    @Autowired
    RowMapperDao rowMapperDao;
 
    public void insertTestPojo(TestPOJO testPOJO) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                //insert code
            }
        });
    }
}
cs


Transaction Isolation


- 동시에 실행되는 트랜잭션들이 서로에게 영향을 끼치지 않도록 함

- @Transactional(isolation = Isolation.DEFAULT) 또는 TransctionDefinition의 setIsolationLevel 사용


1. Isolation 문제점

1) Dirty Read

- 다른 트랜잭션이 변경한 커밋되지 않은 데이터를 조회하여 가지고 있을 때, 다른 트랜잭션에서 커밋 완료하지 않고 종료하는 경우, 잘못된 데이터를 가지고 있게 됨


2) Unrepeatable Read

- 데이터를 조회하고 있는 상황에서 다른 트랜잭션이 해당 데이터를 변경/삭제하고 커밋하게 되면 잘못된 데이터를 찾게 됨


3) Phantom Read

- 특정 조건을 이용하여 데이터를 조회하고 있을 때, 다른 트랜잭션이 해당 조건에 맞지 않게 데이터를 변경하는 경우에 다시 조회를 하면 해당 데이터를 찾을 수 없게 됨


2. 종류

isolation1


1) DEFAULT

- Database의 격리 수준을 이용


2) READ_COMMITED

- 커밋되지 않은 변경 데이터를 다른 트랜잭션에서 참조할 수 없음


3) READ_UNCOMMITED

- 커밋되지 않은 변경 데이터를 다른 트랜잭션에서 참조할 수 있음

- 변경 데이터가 롤백된 경우, 롤백 전 데이터를 조회하게 됨


4) REPEATABLE_READ

- 데이터를 조회하고 있는 도중에 다른 트랜잭션이 해당 데이터를 변경하여도 영향받지 않음

- 다른 트랜잭션에서 update 불가능


5) SERIALIZABLE

- 해당 데이터를 조회하고 있는 도중에 다른 트랜잭션이 해당 데이터를 조작할 수 없음

- 다른 트랜잭션에서 insert, update, delete 불가능


Transaction Propagation


- 트랜잭션의 경계에서 트랜잭션에 참여하는 방법 결정

- 새로운 트랜잭션의 시작이나 기존 트랜잭션에 참여하는 것을 결정

- ex) 트랜잭션 관리 메소드에서 다른 트랜잭션 관리 메소드를 실행하는 경우

- @Transactional(propagation = Propagation.MANDATORY) 또는 TransactionDefinition의 setPropagationBehavior 사용


1. 종류

propagation1


1) MANDATORY

- 기존에 만들어진 트랜잭션에 들어가야 함

- 기존에 만들어진 것이 없다면 예외 발생


2) NESTED

- 기존에 만들어진 트랜잭션이 있다면 해당 범위에 들어감

- 기존에 만들어진 트랜잭션이 없다면 새로운 트랜잭션 생성

- 해당 propagation이 적용된 구간에서 롤백이 발생하면 해당 구간에서만 롤백이 일어남

- 부모 트랜잭션에서 롤백될 경우, 같이 롤백됨


3) NEVER

- 트랜잭션 관리를 하지 않음

- 기존에 만들어진 것이 있다면 예외 발생


4) NOT_SUPPORTED

- 트랜잭션 관리를 하지 않음

- 기존에 만들어진 것이 있다면 기해당 트랜잭션이 끝나기를 기다림


5) REQUIRED

- 기존에 만들어진 트랜잭션이 있다면 해당 범위에 들어감

- 기존에 만들어진 것이 없다면 새로운 트랜잭션 생성


6) REQUIRES_NEW

- 새로운 트랜잭션을 생성함

- 기존에 만들어진 트랜잭션이 있다면 해당 트랜잭션이 끝나기를 기다림


7) SUPPORTS

- 기존에 만들어진 트랜잭션이 있다면 해당 범위에 들어감

- 기존에 만들어진 트랜잭션이 없다면 트랜잭션 관리를 하지 않음

Comments