[スプリング入門]6Spring DBアクセス技術


パスH 2データベース


テーブルの作成
drop table if exists member CASCADE;
create table key_member
(
    id bigint generated by default as identity,
    name varchar(255),
    primary key (id)
);
🌱
generated by default as identity:値を設定せずに挿入すると、データベースは自動的に値を入力します.
❗H 2データベース作成が正常でない場合
次のエラーメッセージが表示され、H 2データベースが正常に作成されない可能性があります.
解決策は以下の通りです.
  • H 2データベースを閉じて再起動します.
  • URLのフロントエンド(ex 100.1.2.3)をlocalhostに変更します.後のセッション部分は変更できません.
  • 純Jdbc


    このように,直接JDBC APIで符号化するのは一般的ではない.だから我慢したい.
    build.jdbc、h 2データベースライブラリをgradeファイルに追加
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    runtimeOnly 'com.h2database:h2'
    スプリングの追加データベース接続設定の開始
    spring.datasource.url=jdbc:h2:tcp://localhost/~/test
    spring.datasource.driver-class-name=org.h2.Driver
    spring.datasource.username=sa
    Jdbcメンバーライブラリ
    package hello.hellospring.repository;
    
    import hello.hellospring.domain.Member;
    import org.springframework.jdbc.datasource.DataSourceUtils;
    
    import javax.sql.DataSource;
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Optional;
    
    public class JdbcMemberRepository implements MemberRepository {
    
     	private final DataSource dataSource;
        
     	public JdbcMemberRepository(DataSource dataSource) {
     		this.dataSource = dataSource;
     	}
        
     	@Override
     	public Member save(Member member) {
     		String sql = "insert into member(name) values(?)";
            
     		Connection conn = null;
     		PreparedStatement pstmt = null;
     		ResultSet rs = null;
            
     		try {
     			conn = getConnection();
     			pstmt = conn.prepareStatement(sql,
    Statement.RETURN_GENERATED_KEYS);
    
     			pstmt.setString(1, member.getName());
                
     			pstmt.executeUpdate();
     			rs = pstmt.getGeneratedKeys();
                
     			if (rs.next()) {
     				member.setId(rs.getLong(1));
     			} else {
     				throw new SQLException("id 조회 실패");
     			}
     			return member;
     		} catch (Exception e) {
     			throw new IllegalStateException(e);
     		} finally {
     			close(conn, pstmt, rs);
     		}
     }
     
     	@Override
     	public Optional<Member> findById(Long id) {
        
     		String sql = "select * from member where id = ?";
            
     		Connection conn = null;
     		PreparedStatement pstmt = null;
     		ResultSet rs = null;
            
     		try {
     			conn = getConnection();
     			pstmt = conn.prepareStatement(sql);
     			pstmt.setLong(1, id);
                
     			rs = pstmt.executeQuery();
                
     			if(rs.next()) {
     				Member member = new Member();
     				member.setId(rs.getLong("id"));
     				member.setName(rs.getString("name"));
     				return Optional.of(member);
     			} else {
     				return Optional.empty();
     			}
                
     		} catch (Exception e) {
     			throw new IllegalStateException(e);
     		} finally {
     			close(conn, pstmt, rs);
     		}
     	}
        
     	@Override
     	public List<Member> findAll() {
     			String sql = "select * from member";
                
     			Connection conn = null;
     			PreparedStatement pstmt = null;
     			ResultSet rs = null;
                
     			try {
     				conn = getConnection();
     				pstmt = conn.prepareStatement(sql);
                    
     				rs = pstmt.executeQuery();
     				List<Member> members = new ArrayList<>();
     				while(rs.next()) {
     					Member member = new Member();
     					member.setId(rs.getLong("id"));
     					member.setName(rs.getString("name"));
     					members.add(member);
     				}
                    
     				return members;
     			} catch (Exception e) {
     				throw new IllegalStateException(e);
     			} finally {
     				close(conn, pstmt, rs);
     			}
     		}
            
     		@Override
     		public Optional<Member> findByName(String name) {
            
     			String sql = "select * from member where name = ?";
                
     			Connection conn = null;
     			PreparedStatement pstmt = null;
     			ResultSet rs = null;
                
     			try {
     				conn = getConnection();
     				pstmt = conn.prepareStatement(sql);
     				pstmt.setString(1, name);
                    
     				rs = pstmt.executeQuery();
                    
     				if(rs.next()) {
     					Member member = new Member();
     					member.setId(rs.getLong("id"));
     					member.setName(rs.getString("name"));
     					return Optional.of(member);
     				}
     				return Optional.empty();
     			} catch (Exception e) {
     				throw new IllegalStateException(e);
     			} finally {
     				close(conn, pstmt, rs);
     			}
     		}
            
     		private Connection getConnection() {
     			return DataSourceUtils.getConnection(dataSource);
     		}
            
     		private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
     			try {
     				if (rs != null) {
     					rs.close();
     				}
     			} catch (SQLException e) {
     				e.printStackTrace();
     			}
     			try {
     				if (pstmt != null) {
     					pstmt.close();
     				}
     			} catch (SQLException e) {
     				e.printStackTrace();
     			}
     			try {
    				if (conn != null) {
     				close(conn);
     				}
     			} catch (SQLException e) {
     				e.printStackTrace();
     			}
     		}
            
     		private void close(Connection conn) throws SQLException {
     			DataSourceUtils.releaseConnection(conn, dataSource);
     		}
    	}
    スプリング設定の変更
    package hello.hellospring;
    
    import hello.hellospring.repository.JdbcMemberRepository;
    import hello.hellospring.repository.JdbcTemplateMemberRepository;
    import hello.hellospring.repository.MemberRepository;
    import hello.hellospring.repository.MemoryMemberRepository;
    import hello.hellospring.service.MemberService;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import javax.sql.DataSource;
    
    @Configuration
    public class SpringConfig {
    
     	private final DataSource dataSource;
     	public SpringConfig(DataSource dataSource) {
     		this.dataSource = dataSource;
     	}
        
     	@Bean
     	public MemberService memberService() {
     		return new MemberService(memberRepository());
     	}
        
     	@Bean
     	public MemberRepository memberRepository() {
    		// return new MemoryMemberRepository();
    		return new JdbcMemberRepository(dataSource);
     }
    
    🌱 DataSourceは、データベース接続を取得する際に使用するオブジェクトです.Spring Bootは、データベース接続情報に基づいてデータソースを作成し、Spring VINに設定します.だからDIを受け入れることができます
    インプリメンテーションクラスイメージの追加

    スプリング設定イメージ

    積層の定理

  • オープンクローズ原則(OCP、オープンクローズ原則)
    拡張は開放的で、修正は閉鎖的で、変更は閉鎖的です.
  • スプリングの依存注入(DI)を使用すると、既存のコードをまったく変更せずに、設定変更のみでクラスを実装できます.
  • ばね集積試験


    会員サービススプリング統合テスト
    package hello.hellospring.service;
    
    import hello.hellospring.domain.Member;
    import hello.hellospring.repository.MemberRepository;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.transaction.annotation.Transactional;
    
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    
    @SpringBootTest
    @Transactional
    class MemberServiceIntegrationTest {
    
     @Autowired MemberService memberService;
     @Autowired MemberRepository memberRepository;
     
     @Test
     public void 회원가입() throws Exception {
     	//Given
     	Member member = new Member();
     	member.setName("hello");
        
     	//When
     	Long saveId = memberService.join(member);
        
     	//Then
     	Member findMember = memberRepository.findById(saveId).get();
     	assertEquals(member.getName(), findMember.getName());
     }
     
     @Test
     public void 중복_회원_예외() throws Exception {
     	//Given
     	Member member1 = new Member();
     	member1.setName("spring");
        
     	Member member2 = new Member();
     	member2.setName("spring");
        
     	//When
     	memberService.join(member1);
     	IllegalStateException e = assertThrows(IllegalStateException.class,
     			() -> memberService.join(member2));//예외가 발생해야 한다.
                
     	assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
     }
     
    }
    🌱
  • @SpringBootTest:スプリングコンテナとともにテストを行います.
  • @Transactional:テストケースにこのプレゼンテーションがある場合は、テストが開始される前にトランザクションが開始され、テストが完了すると常にロールバックされます.これにより、データベースにデータが残らず、次のテストに影響を与えません.
  • スプリングJdbcTemplate


    Spring JdbcTemplateやMyBasisのようなライブラリは、JDBC APIから見た重複コードの大部分を削除できます.しかし、SQLは自分で書く必要があります.
    SpringJdbcTemplateメンバーライブラリ
    package hello.hellospring.repository;
    
    import hello.hellospring.domain.Member;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
    import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
    
    import javax.sql.DataSource;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    
    public class JdbcTemplateMemberRepository implements MemberRepository {
    
     private final JdbcTemplate jdbcTemplate;
     
     public JdbcTemplateMemberRepository(DataSource dataSource) {
     
     	jdbcTemplate = new JdbcTemplate(dataSource);
     }
     
     @Override
     public Member save(Member member) {
     
     	SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
     	jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
        
    	Map<String, Object> parameters = new HashMap<>();
     	parameters.put("name", member.getName());
        
     	Number key = jdbcInsert.executeAndReturnKey(new
    MapSqlParameterSource(parameters));
     	member.setId(key.longValue());
        
     	return member;
     }
     
     @Override
     	public Optional<Member> findById(Long id) {
     		List<Member> result = jdbcTemplate.query("select * from member where id 
    = ?", memberRowMapper(), id);
     		return result.stream().findAny();
     	}
        
     @Override
     public List<Member> findAll() {
     	return jdbcTemplate.query("select * from member", memberRowMapper());
     }
     
     @Override
     public Optional<Member> findByName(String name) {
     	List<Member> result = jdbcTemplate.query("select * from member where 
    name = ?", memberRowMapper(), name);
     	return result.stream().findAny();
     }
     
     private RowMapper<Member> memberRowMapper() {
     	return (rs, rowNum) -> {
     		Member member = new Member();
     		member.setId(rs.getLong("id"));
     		member.setName(rs.getString("name"));
     		return member;
     	};
     }
    }
    
    スプリング設定を変更してJdbcTemplateを使用
    package hello.hellospring;
    
    import hello.hellospring.repository.JdbcMemberRepository;
    import hello.hellospring.repository.JdbcTemplateMemberRepository;
    import hello.hellospring.repository.MemberRepository;
    import hello.hellospring.repository.MemoryMemberRepository;
    import hello.hellospring.service.MemberService;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import javax.sql.DataSource;
    
    @Configuration
    public class SpringConfig {
    
     	private final DataSource dataSource;
        
     	public SpringConfig(DataSource dataSource) {
     		this.dataSource = dataSource;
     	}
        
     	@Bean
     	public MemberService memberService() {
     		return new MemberService(memberRepository());
     	}
        
     	@Bean
     	public MemberRepository memberRepository() {
    		// return new MemoryMemberRepository();
    		// return new JdbcMemberRepository(dataSource);
     		return new JdbcTemplateMemberRepository(dataSource);
     	}
    }
    

    ✍ JPA

  • JPAは既存の重複コードだけでなく、基本的なSQLもJPA自身が作成して実行します.
  • JPAを使用すると、SQLおよびデータ中心の設計からオブジェクト中心の設計に移行できます.
  • JPAを使用すると、開発生産性が大幅に向上します.
  • build.JPA、H 2データベース関連ライブラリをgradeファイルに追加
    dependencies {
    	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    	implementation 'org.springframework.boot:spring-boot-starter-web'
    	//implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    	runtimeOnly 'com.h2database:h2'
    	testImplementation('org.springframework.boot:spring-boot-starter-test') {
    	exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    	}
    }
    JPA設定をスプリングガイドに追加
    spring.datasource.url=jdbc:h2:tcp://localhost/~/test
    spring.datasource.driver-class-name=org.h2.Driver
    spring.datasource.username=sa
    
    spring.jpa.show-sql=true
    spring.jpa.hibernate.ddl-auto=non
    🌱
  • show-sql:JPA生成SQL出力.
  • ddl-auto:JPAはテーブルを自動的に作成する機能を提供し、noneを使用する場合はこの機能をオフにします.createを使用すると、エンティティ情報に基づいてテーブルを直接作成することもできます.
  • JPAエンティティのマッピング
    package hello.hellospring.domain;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    
    @Entity
    public class Member {
    
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    JPA会員庫
    package hello.hellospring.repository;
    
    import hello.hellospring.domain.Member;
    
    import javax.persistence.EntityManager;
    import java.util.List;
    import java.util.Optional;
    
    public class JpaMemberRepository implements MemberRepository {
    
        private final EntityManager em; // JPA는 EntityManager라는 것으로 모든게 동작함
    
    	// JPA 라이브러리를 받으면 스프링부트가 현재 데이터베이스와 연결되어 있는 EntityManager를 자동으로 생성해준다. 만들어진 것을 injection 받으면 된다.
        public JpaMemberRepository(EntityManager em) {
            this.em = em;
        }
    
        @Override
        public Member save(Member member) {
            em.persist(member); // JPA가 Insert 쿼리 만들어서 DB에 넣고 Member에 setId까지 해준다.
            return member;
        }
    
        @Override
        public Optional<Member> findById(Long id) {
            Member member = em.find(Member.class, id); / find(조회할 타입, 식별자(pk))
            return Optional.ofNullable(member);
        }
        
        // id(pk)와 같은 경우는 위와 같이 조회가 가능한데, 아래와 같은 경우는 JPQL 객체지향쿼리를 사용해야 한다.
    
        @Override
        public Optional<Member> findByName(String name) {
            List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
                    .setParameter("name", name)
                    .getResultList();
    
            return result.stream().findAny();
        }
    
        @Override
        public List<Member> findAll() {
            return em.createQuery("select m from Member m", Member.class)
                    .getResultList(); // Entity 자체를 select
        }
    }
    サービス・レベルへのトランザクションの追加
    import org.springframework.transaction.annotation.Transactional
    
    @Transactional
    public class MemberService {}
    🌱
    JPAによるすべてのデータ変更は、トランザクションで実行する必要があります.

    ⛴SpringデータJPA


    Spring Data JPAメンバーライブラリ
    package hello.hellospring.repository;
    
    import hello.hellospring.domain.Member;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import java.util.Optional;
    
    public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
    
        @Override
        Optional<Member> findByName(String name);
    }
    🌱
  • SpringDataJpaがJpaRepoを受信している場合、SpringDataJpaは自動的にインプリメンテーションを生成します.
  • Spring Data JPAはSpringDataJpaMemberRepositoryをSpringBeanとして自動的に登録します.
  • Spring Data JPA提供クラス