꾸준히 하고싶은 개발자

스프링 DB 접근 기술 본문

Spring Boot

스프링 DB 접근 기술

프라우스 2023. 3. 15. 14:48
순수 JDBC
package oss.board.repository;

import org.springframework.jdbc.datasource.DataSourceUtils;
import oss.board.domain.Member;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class JdbcMemberRepository implements  MemberRepository{

    // db에 넣으려면 데이터 소스 가 필요하다.
    private final  DataSource dataSource;

    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
       // dataSource.getConnection();// sql 넣어서 db애 넘겨준다. 코드가 길어진다.

    }

    @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); // db에 insert 1.데이터 2.데이터
            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 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);
    }
}

코드가 상당히 길다.

특징으로는 connection을 생성,열고 닫을수 있다.

PreparedStatement를 이용해 쿼리를 날리고 받아온다.

ResultSet을 통해 결과값을 담아서 확인할 수 있다.

20년전 현업에서 쓰는 코드로 현재는 거의 쓰지 않는다 .

 

spring  boot framework을 쓸 때는 

 

configuration을  어노테이션 해주자.

지금까지 memory에 저장하고있었으니, DB에 저장을 하기로 했었다.

SpringConfig 클래스에 등록해놨던 memberRepository의 return 객체를 JdbcMemberRepository로 바꿔주기만 하면 된다.

 

스프링 통합 테스트

package oss.board.Service;


import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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 oss.board.domain.Member;
import oss.board.repository.MemberRepository;
import oss.board.repository.MemoryMemberRepository;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
   @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    void 회원가입(){
        //given
        Member member = new Member();
        member.setName("spring");
        //when
        Long saveId = memberService.join(member);
        //than
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        //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 : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.

@Transactional 을이용하지 않으면 db에 그대로 저장하기 떄문에 두번째 테스트할 떄 이미있는 회원이라고 오류가 난다.

왜냐하면

@Transactional 어노테이션을 사용하지 않아서 데이터 에 사용했기 때문이다.

@Transactional : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.

 

스프링 JdbcTemplate

순수 Jdbc와 동일한 환경설정을 하면 된다.
스프링 JdbcTemplateMyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해준다. 하지만 SQL은 직접 작성해야 한다.

실무에서 많이 사용된다. 

package oss.board.repository;

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.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import oss.board.domain.Member;

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 {

    public final JdbcTemplate jdbcTemplate;
    // 1. JdbcTemplate의 생성자 생성후
    // 2. JdbcTemplate인젝션에서 디비 인젝션으로 연동한다.

    //@Autowired 참고로 생성자가 하나면 스프링빈으로 등록될때 Autowired 어노테이션을 생략하 가능하다.
    // 데이터 소스가 자동으로 인젝션을 한다.
    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());
        // 인텔리제이에서 코드를 짜면 자동으로 insert into member(name) values(?)를 쿼리문을 써줄 필요없이 만들어준다.
    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());
        return result.stream().findAny();
    }


    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return jdbcTemplate.query("select * from member", memberRowMapper()); //리스트로 반환 
    }

    private RowMapper<Member> memberRowMapper() {
        return (rs, rowNum) -> {

            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;

        };
    }
}

1.클래스를 생성한다.

package oss.board.repository;

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.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import oss.board.domain.Member;

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 {

    @Override
    public Member save(Member member) {
        return null;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.empty();
    }

    @Override
    public Optional<Member> findByName(String name) {
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        return null;
    }
}

 

2.생성자를 생성한다.

public final JdbcTemplate jdbcTemplate;
// 1. JdbcTemplate의 생성자 생성후
// 2. JdbcTemplate인젝션에서 디비 인젝션으로 연동한다.

//@Autowired 참고로 생성자가 하나면 스프링빈으로 등록될때 Autowired 어노테이션을 생략하 가능하다.
// 데이터 소스가 자동으로 인젝션을 한다.
public JdbcTemplateMemberRepository(DataSource dataSource) {
    jdbcTemplate = new JdbcTemplate(dataSource);
}

참고로 생성자가 한개이면  스프링 빈으로 등록될때 Autowired 어노테이션을 생략이 가능하다. 생성자 가 두개면 Autowired 어노테이션을써야한다.

앞서 application.properties 에 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());
        // 인텔리제이에서 코드를 짜면 자동으로 insert into member(name) values(?)를 쿼리문을 써줄 필요없이 만들어준다.
    Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        member.setId(key.longValue());
        return member;
}

SimpleJdbcInsert 를 통해, table name 과 keyColums를 통해 insert 쿼리문을 만들수있다.

document를 참조해서 보면 쉽게 작성할 수 있다.

findBYId 작성하자

@Override
public Optional<Member> findById(Long id) {
    List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(),id);
    return result.stream().findAny();
}

findBYName 작성하자

@Override
public Optional<Member> findByName(String name) {
    List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
    return result.stream().findAny();
}

조회 기능 

@Override
public List<Member> findAll() {
    return jdbcTemplate.query("select * from member", memberRowMapper()); //리스트로 반환
}

리스트 로 반환한다.

RowMapper 작성하기

private RowMapper<Member> memberRowMapper() {
    return (rs, rowNum) -> {

        Member member = new Member();
        member.setId(rs.getLong("id"));
        member.setName(rs.getString("name"));
        return member;

    };
}

 JdbcMemberRepository 코드보다 작성했을때 보다 비교해보니 코드 간결해지고 가독성이 좋아진다.

@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);
    }
}

그리고 마지막으로 Springconfig 만 수정해주면 끝난다.

 @Bean
    public MemberRepository memberRepository() {
        //return new MemoryMemberRepository();
        //return  new JdbcMemberRepository(dataSource); 20년전 방식
        return new JdbcTemplateMemberRepository(dataSource); //스프링 JdbcTemplate
    }
}

JPA

JPA는 기존의 반복 코드는 물론이고, 기본적인 SQLJPA가 직접 만들어서 실행해준다.

JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.

JPA를 사용하면 개발 생산성을 크게 높일 수 있다.

 

1.build gradle에  라이브러리 추가하기 

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'
   }
}

//implementation 'org.springframework.boot:spring-boot-starter-jdbc' 을 지우거나 주석처리하고 나서

implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 을 디펜던시에 추가합시다.

gradle 리프레시 를 해주세요

2.application properties

show-sql : JPA가 생성하는 SQL을 출력한다.
ddl-auto : JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none 를 사용하면 해당 기능을 끈다.

create 를 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성해준다. 해보자.

 

gradle 리프레시 를 한번 더 해주세요

 

3. JPA개념

JPA라는 것은 간단히 말해서  인터페이스 이다. 그 구현체들이 여러개 있다. 우리는 hibernate 를 쓴다.

JPA는 object(객체) 와 ORM (object Relational Mapping)

JPA는 object(객체)와ORM(Object Relational Table Mapping)을 맵핑한다.

4.@Entity 어노테이션 추가

기존 우리가 작성했었던 Member 객체에 @Entity 어노테이션을 추가해 보자.

이렇게 하면 JPA가 관리하는 Entity라는 뜻이고, 자바 객체와 DB와 매핑이 된다. 

그리고 이전에 우리가 회원을 생성하면 회원의 id가 자동 증가(auto-increase) 하도록 하였다 (오라클, PostgreSQL 에서는 시퀀스).이러한 방식에는 다양한 방식이 있으며, DB가 알아서 생성해 주는 전략을 Identity 전략 이라고 한다. 

 

1. Member클래스 에 @Entity 을 맵핑한다.

package oss.board.domain;

import javax.persistence.*;

@Entity

public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // db가 자동으로 생성하는것을 IDENTITY라고한다.
    private Long id; // 회원 아이디
    //@Column(name="username") @Column(name="username")라고하면 db에있는 컬럼 username과 맵핑된다.
    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;
    }
}

domain 에 Member @id 를 추가하여 Primary key 임을 알려주고, @GenneratedValue 를 통해 전략을 설정할 수 있다. ( IDENTITY, SEQUENCE, AUTO, TABLE)

또한 각 필드에는 @Column을 통해 해당 테이블의 컬럼명과 일치시킬 수 있다.

column name과 필드의 name은 동일하여 생략 가능하고, 만약 DB의 column name이 user_name 일 경우 맵핑한다.

그래서 @Column 어노테이션에 입력해주면 된다.

2. JPAMemberRepository

2-1클래스 생성한다. 

package oss.board.repository;

import oss.board.domain.Member;

import java.util.List;
import java.util.Optional;

public class JPAMemberRepository  implements MemberRepository{
    @Override
    public Member save(Member member) {
        return null;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.empty();
    }

    @Override
    public Optional<Member> findByName(String name) {
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        return null;
    }
}

2-2 EntityManager 를 필드선언후 생성자를 만든다.

public class JPAMemberRepository  implements MemberRepository{

    private final EntityManager em; // @Entity 관리하는 시스템

    public JPAMemberRepository(EntityManager em) {
        this.em = em;
    }

 JpaRepository 생성

JpaRepository class를 생성하고,EntityManger를 필드에 선언한다.

EntityManager 란 JPA는 EntityManager로 모두 동작한다.

data jpa 라이브러리를 받았기 때문에, 스프링부트가 자동으로 EntityManager를 생성해주면  db 연동부터 모든 연결 까지  다 해주고  만들어주고 우리는 인젝션 해주기만 하면 된다. 내부에서 다 처리해준다.

@Override
public Member save(Member member) {
    em.persist(member); //persist 연구로 저장하다.
    return member;
}

persist // 영구로 db에저장한다.

@Override
public Optional<Member> findById(Long id) {
    Member member = em.find(Member.class, id);
    return Optional.ofNullable(member); //
}

Optional.ofNullable(value)

of.Nullable()메소드는 명시된 값이 null 이 아니면  명시된 값을 가지는 Optional객체를 반환하며 명시된 값이 null이면 비어있는 Optional 객체를 반환한다. 자바 8부터 쓸 수있다.

@Override
public Optional<Member> findByName(String name) {
List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class) // JpQl이라는 걸 짜야한다.
            .setParameter("name", name)
            .getResultList();
    return result.stream().findAny();
}

JPQL 쿼리를 짜야한다.

   @Override
    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
}

3. 서비스 객체에서 가서 @Transactional을 써야한다.

package oss.board.Service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import oss.board.domain.Member;
import oss.board.repository.MemberRepository;
import oss.board.repository.MemoryMemberRepository;

import java.util.List;
import java.util.Optional;
@Transactional // 항상 JPA를 쓰라면 @Transactional annotation을  데이터 변경하거나 저장 할 때사용된다..
public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    // 회원가입
    //Member member1 = result.get(); 겟으로 해서 직접 뽑아낼 수있지만 권장하지 않는다.
    public Long join(Member member){
        // 중복되는 이름이 있으면 안된다
        validateDuplicateMember(member); // 중복 회원 검증

        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
            .ifPresent(member1 -> {
                throw new IllegalStateException("이미존재하는 회원입니다.");
                });
    }
    // 전체 회원 조회
    public List<Member> findMembers(){
        return memberRepository.findAll();

    }
    public Optional<Member>findOne(Long memberId){
        return memberRepository.findById(memberId);

    }

}

3. 서비스 객체에서 가서 @Transactional을 써야한다.

항상  JPA를 쓰려면  @Transactional annotation을 주입한다.  데이터를저장하고 변경 할 때 쓴다. 

springConfig에서는 em 을  추가해주세요

package oss.board;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import oss.board.Service.MemberService;
import oss.board.repository.*;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {
//    // db와 연동하기 위해서 데이터소스 만든후에 생성자 추가후 Autowired을 만든다.
//    private DataSource dataSource;
//    @Autowired
//    public SpringConfig(DataSource dataSource) {
//        this.dataSource = dataSource;
//    }
    //@PersistenceContext 원래 스펙에서는 @PersistenceContext 써야되지만 스프링부트에서는 자동으로 의존성 주입이 됩니다.
    private EntityManager em;

    @Autowired
    public SpringConfig(EntityManager em) {
        this.em = em;
    }

    @Bean
    public MemberService memberService(){

        return new MemberService(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        //return new MemoryMemberRepository();
        //return  new JdbcMemberRepository(dataSource); 20년전 방식
        //return new JdbcTemplateMemberRepository(dataSource); //스프링 JdbcTemplate
        return  new JPAMemberRepository(em);
    }
}

기존에 직접 Repository @Bean을 등록하는 코드를 없애고, SpringConfig 생성자를 작성하면서 DI를 해주면 된다.

실무에서는 JPA 와 스프링 데이터 JPA를 기본으로 사용한다,

복잡한 동적쿼리는 Query dsl이라는 라이브러리를 사용하면 된다.

Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할수있고, 동적 쿼리도 편리하게 작성 가능하다.

그래도 해결하기 어렵다면, JPA가 제공하는 native query를 사용하거나, jdbcTemplate을 이용하면 된다.

스프링 데이터 JPA

 

스프링 부트와 JPA만 사용해도 개발 생산성이 정말 많이 증가하고, 개발해야할 코드도 확연히 줄어듭니다. 여기에 스프링 데이터 JPA를 사용하면, 기존의 한계를 넘어 마치 마법처럼, 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공합니다.

스프링 부트와 JPA라는 기반 위에, 스프링 데이터 JPA라는 환상적인 프레임워크를 더하면 개발이 정말 즐거워집니다. 지금까지 조금이라도 단순하고 반복이라 생각했던 개발 코드들이 확연하게 줄어듭니다.

따라서 개발자는 핵심 비즈니스 로직을 개발하는데, 집중할 수 있습니다.
실무에서 관계형 데이터베이스를 사용한다면 스프링 데이터 JPA는 이제 선택이 아니라 필수 입니다.

주의: 스프링 데이터 JPAJPA를 편리하게 사용하도록 도와주는 기술입니다. 따라서 JPA를 먼저 학습한 후에 스프링 데이터 JPA를 학습해야 합니다.

앞의 JPA 설정을 그대로 사용한다

 

'Spring Boot' 카테고리의 다른 글

스프링 핵심 원리  (0) 2023.04.05
AOP  (0) 2023.03.16
스프링 빈과 의존 관계  (2) 2023.03.14
회원관리 예제 -벡엔드 개발  (0) 2023.03.12
스프링 웹 개발 기초  (0) 2023.03.11