1. 왜 내부 클래스는 private 변수에 접근할 수 있을까요?
- 캡슐화 강화: 내부 클래스는 외부 클래스의 일부로 간주됩니다. 따라서 외부 클래스의 private 멤버에 접근할 수 있는 것은 자연스럽게 내부 클래스의 기능을 확장하거나 개선하는데 도움이 됩니다. 이는 내부 클래스가 외부 클래스의 동작을 직접적으로 조작하고 상세하게 제어할 수 있게 하여, 두 클래스 간의 밀접한 통합을 가능하게 합니다.
- 구현 세부사항 숨기기: 외부 클래스가 특정 기능을 구현하는 데 있어 복잡하거나 세부적인 로직을 내부 클래스에 위임할 수 있습니다. 이 때, 외부 클래스의 private 데이터에 접근하게 하면, 외부 클래스는 보다 깔끔하게 인터페이스를 제공할 수 있으며, 구현 세부사항을 내부 클래스 내에 숨길 수 있습니다.
- 유연성 증가: 내부 클래스가 외부 클래스의 private 멤버에 접근할 수 있게 함으로써, 개발자는 더욱 유연하게 코드를 설계할 수 있습니다. 예를 들어, 특정 메소드의 동작을 변경하고자 할 때 외부 클래스 전체를 수정하는 대신 해당 기능을 처리하는 내부 클래스만을 수정하면 됩니다.
- 내부 클래스에 있는 private 변수에 외부 클래스에서도 접근할 수 있다. 내부 클래스의 private 변수는 외부 클래스의 private 변수로 간주한다.
2. Thymeleaf의 레이아웃 사용을 위한 build.gradle Dependencies 추가
mvnrepository > Thymeleaf Layout Dialect » 3.0.0
링크 https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.0.0'
3. @ModelAttribute
@ModelAttribute는 스프링 MVC에서 매우 유용한 애너테이션 중 하나입니다. 이 애너테이션은 컨트롤러에서 모델 데이터를 뷰로 전달하거나, HTTP 요청 파라미터를 객체에 바인딩할 때 사용됩니다. @ModelAttribute는 크게 두 가지 방식으로 사용될 수 있습니다.
3.1 메소드 레벨에서 사용
@ModelAttribute 애너테이션을 메소드에 적용하면, 해당 컨트롤러의 모든 핸들러 메소드가 실행되기 전에 먼저 실행됩니다. 이 메소드에서 반환하는 객체는 자동으로 모델에 추가되어 뷰에서 사용할 수 있습니다. 이 방법은 뷰에 전달되어야 할 공통 데이터를 처리할 때 특히 유용합니다.
@Controller
public class MyController {
@ModelAttribute("now")
public Date setUpMyData() {
return new Date();
}
@GetMapping("/showDate")
public String showDate() {
return "dateDisplay";
}
}
위 예제에서는 모든 요청 전에 setUpMyData() 메소드가 호출되며, "now"라는 이름으로 현재 날짜와 시간을 모델에 추가합니다. 이 모델 데이터는 뷰에서 사용될 수 있습니다.
3.2 핸들러 메소드 파라미터 레벨에서 사용
핸들러 메소드의 파라미터에 @ModelAttribute를 사용하면, HTTP 요청 파라미터를 해당 파라미터의 객체에 자동으로 바인딩합니다. 이는 폼 데이터나 쿼리 파라미터를 객체로 매핑할 때 주로 사용됩니다.
@PostMapping("/register")
public String submitRegistration(@ModelAttribute User user) {
userService.save(user);
return "redirect:/home";
}
위 예제에서 submitRegistration 메소드는 POST 요청을 처리하고, 요청에 포함된 User 관련 데이터를 User 객체에 바인딩합니다. 이 객체는 메소드 내에서 바로 사용할 수 있습니다.
3.3 추가적인 특징 및 유의점
- @ModelAttribute를 사용하면 스프링의 바인딩과 검증 프로세스를 통해 요청 파라미터를 객체에 채울 수 있으며, 이 과정에서 타입 변환, 포맷팅, 검증 등이 수행될 수 있습니다.
- @ModelAttribute에 이름을 명시적으로 지정하지 않을 경우, 기본적으로 모델에 추가될 때 사용되는 이름은 클래스의 이름을 기반으로 합니다 (첫 글자를 소문자로 변환한 형태).
- 이 애너테이션은 뷰 템플릿과 밀접하게 연결되어 있으며, 타임리프(Thymeleaf)와 같은 템플릿 엔진에서 모델 데이터를 렌더링할 때 자주 사용됩니다.
@ModelAttribute를 이용하면, 모델 데이터 처리와 바인딩을 유연하고 효율적으로 관리할 수 있어 스프링 MVC에서 데이터 처리의 복잡성을 크게 줄일 수 있습니다.
4. ORM (Object-Relational Mapping)
- ORM은 데이터베이스의 레코드를 객체 지향 언어에서 사용하는 객체로 매핑하는 프로그래밍 기법입니다. 즉, 데이터베이스의 테이블을 객체로, 레코드를 객체의 인스턴스로, 테이블의 필드를 객체의 속성으로 변환하여, 프로그래머가 SQL을 직접 작성하지 않고도 데이터베이스를 관리할 수 있게 도와줍니다.
- ORM의 주요 이점은 다음과 같습니다:
- 개발 속도 향상: 데이터베이스 쿼리를 직접 작성하지 않고도 데이터를 관리할 수 있어, 개발자가 보다 높은 수준에서 데이터에 접근할 수 있습니다.
- 코드 가독성 및 유지 보수성 향상: 데이터베이스 연산을 객체의 메소드로 캡슐화하여 데이터베이스와 상호작용하는 코드를 단순화합니다.
- 데이터베이스 독립성: 특정 데이터베이스 SQL 문법에 종속되지 않고, 다양한 데이터베이스 시스템에 대해 동일한 코드 베이스를 유지할 수 있습니다.
5. JPA (Java Persistence API)
- JPA는 자바 플랫폼을 위한 ORM 솔루션을 제공하는 API 스펙입니다. JPA는 데이터베이스 테이블과 자바 객체 간의 매핑을 관리하며, 개발자가 직접 SQL을 작성하지 않고도 데이터베이스를 통해 객체를 저장, 검색, 삭제 및 갱신할 수 있는 방법을 제공합니다.
- JPA는 다음과 같은 주요 기능을 제공합니다:
- 엔티티 매핑: 데이터베이스의 테이블을 자바 클래스에 매핑합니다. 이 클래스를 엔티티 클래스라고 하며, JPA가 관리합니다.
- JPQL (Java Persistence Query Language): SQL과 유사하지만 데이터베이스 테이블이 아닌 엔티티 객체를 대상으로 쿼리를 작성합니다.
- 트랜잭션 관리: 데이터의 일관성과 무결성을 유지하면서 데이터베이스 작업을 수행할 수 있습니다.
- 캐싱: 성능 최적화를 위해 일부 데이터를 캐시하여 반복적인 데이터베이스 접근을 줄입니다.
5.1 JPA 구현체
- JPA는 스펙이므로, 이를 구현한 여러 구현체가 존재합니다. 대표적인 JPA 구현체로는 Hibernate, EclipseLink, OpenJPA 등이 있습니다. 이 중 Hibernate는 가장 널리 사용되는 JPA 구현체 중 하나로, 많은 기능과 풍부한 문서를 제공합니다.
5.2 JPA 사용 예
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// getters and setters
}
public class UserService {
@PersistenceContext
private EntityManager em;
public User findUserById(Long id) {
return em.find(User.class, id);
}
public void saveUser(User user) {
em.persist(user);
}
}
- 위 코드는 JPA를 사용하여 간단한 엔티티를 정의하고 기본적인 CRUD 작업을 수행하는 예를 보여줍니다. EntityManager를 통해 데이터베이스 작업을 수행하며, 엔티티의 생명주기를 관리합니다.
- ORM과 JPA를 이용하면 객체 지향적인 방식으로 데이터베이스 관리가 가능하여, 자바 개발자가 SQL과 데이터베이스 구조에 대한 걱정 없이 애플리케이션 개발에 더욱 집중할 수 있습니다.
6. 엔티티(Entity)
- Entity는 Java Persistence API (JPA)에서 매우 중요한 개념입니다. JPA를 사용할 때, 데이터베이스 테이블을 객체 지향 모델로 표현하기 위해 클래스를 정의하는데, 이런 클래스들을 엔티티(Entity)라고 합니다. 엔티티 클래스는 데이터베이스의 테이블에 대응되며, 테이블의 각 레코드는 엔티티의 인스턴스 하나로 매핑됩니다.
6.1 엔티티의 특징:
- @Entity 애너테이션: 클래스가 엔티티임을 지정하고 JPA가 관리하도록 합니다.
- 식별자 (Primary Key): 모든 엔티티는 식별자를 가져야 하며, 이는 데이터베이스 테이블의 기본 키(Primary Key)에 대응됩니다. JPA에서는 @Id 애너테이션을 사용하여 식별자 필드를 명시합니다.
- 테이블 매핑: 기본적으로 엔티티 이름은 데이터베이스 테이블 이름으로 사용되지만, @Table 애너테이션을 사용하여 다른 테이블 이름으로 매핑할 수 있습니다.
- 컬럼 매핑: 필드는 @Column 애너테이션을 사용하여 데이터베이스 테이블의 컬럼에 매핑될 수 있습니다. 이 애너테이션은 필수는 아니며, 사용하지 않을 경우 필드 이름이 컬럼 이름으로 사용됩니다.
- 관계 매핑: 다른 엔티티와의 관계를 @OneToMany, @ManyToOne, @ManyToMany, @OneToOne 등의 애너테이션으로 정의할 수 있습니다.
6.2 엔티티 예제:
import javax.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name")
private String name;
@Column(name = "user_email")
private String email;
// 생성자, 게터, 세터 등
}
- 위 예제에서 User 클래스는 users라는 데이터베이스 테이블에 매핑됩니다. id, name, email 필드는 각각 테이블의 컬럼에 해당합니다. @Id 애너테이션으로 id 필드가 식별자임을 나타내고, @GeneratedValue를 사용해 데이터베이스가 ID를 자동으로 생성하도록 설정합니다.
- 엔티티를 사용함으로써, 개발자는 객체 지향 프로그래밍을 통해 데이터베이스를 더 직관적으로 다룰 수 있게 되며, SQL 쿼리 작성의 복잡성을 크게 줄일 수 있습니다. JPA 구현체는 이 엔티티 정보를 바탕으로 필요한 SQL을 자동으로 생성하고 실행해, 데이터베이스와의 상호작용을 처리합니다.
7. @GeneratedValue
- @GeneratedValue 애너테이션은 주로 엔티티의 식별자(기본 키) 필드에 사용되며, 해당 필드의 값이 어떻게 생성될지 정의하는 데 사용됩니다. strategy 속성은 식별자 값 생성 방법을 결정하는 데 사용되며, GenerationType 열거형은 다음 네 가지 전략을 제공합니다:
- IDENTITY
- 데이터베이스의 IDENTITY 컬럼을 사용하여 식별자를 생성합니다.
- 각 데이터베이스 행이 삽입될 때, 데이터베이스가 자동으로 고유 식별자를 생성합니다.
- 주로 MySQL, SQL Server, PostgreSQL에서 지원됩니다.
- 이 전략을 사용할 때, JPA는 엔티티를 데이터베이스에 저장한 후 생성된 ID를 읽어와야 하기 때문에, 엔티티 삽입이 조금 느릴 수 있습니다.
- SEQUENCE
- 데이터베이스의 시퀀스를 사용하여 식별자를 생성합니다.
- @SequenceGenerator 애너테이션과 함께 사용되어, 어떤 시퀀스를 사용할 것인지, 시퀀스 이름, 초기값, 증가 크기 등을 명시할 수 있습니다.
- 주로 Oracle, PostgreSQL, DB2 등에서 사용됩니다.
- 시퀀스는 성능 면에서 IDENTITY보다 유리할 수 있으며, JPA 구현체는 필요에 따라 미리 여러 개의 ID 값을 가져와 성능을 최적화할 수 있습니다.
- TABLE
- 특정 데이터베이스 테이블을 사용하여 식별자를 생성합니다.
- @TableGenerator 애너테이션과 함께 사용되어, 식별자를 저장할 테이블, 컬럼 이름, 값의 저장 및 업데이트 방식 등을 명시할 수 있습니다.
- 모든 데이터베이스에서 사용할 수 있지만, 성능이 느릴 수 있습니다.
- 다수의 애플리케이션과 서버가 동시에 같은 테이블을 사용하여 식별자를 생성할 때 사용하기 적합합니다.
- AUTO
- JPA 구현체가 특정 데이터베이스에 적합한 전략(IDENTITY, SEQUENCE, TABLE)을 선택하도록 합니다.
- 개발자가 명시적으로 식별자 생성 전략을 결정하지 않을 때 사용되며, 가장 유연한 방식입니다.
- 특정 데이터베이스에 최적화된 전략을 자동으로 선택하므로, 다양한 데이터베이스에 대한 호환성이 좋습니다.
- 각 전략의 선택은 사용하는 데이터베이스, 애플리케이션의 요구 사항, 성능 고려사항 등에 따라 달라질 수 있습니다. 일반적으로, IDENTITY는 단순하고 효율적인 방식이지만, SEQUENCE나 TABLE을 사용할 경우 더 세밀한 제어가 가능합니다.
8. @Id의 주요 용도
- 고유 식별자 설정: 엔티티의 각 인스턴스를 고유하게 식별할 수 있는 식별자를 정의합니다. 이는 데이터베이스의 각 행을 유일하게 매칭시키는 역할을 합니다. 테이블의 기본키로 사용한다.
- 데이터 접근과 조작의 기준 제공: JPA는 @Id로 표시된 필드를 사용하여 엔티티를 저장, 조회, 업데이트, 삭제하는 기준으로 활용합니다.
9. @MappedSuperclass
- @MappedSuperclass 애너테이션은 Java Persistence API (JPA)에서 사용되며, 상속 관계에서 공통적인 매핑 정보(필드 또는 메서드)를 상속받아 사용할 수 있게 해주는 역할을 합니다. 이 애너테이션은 데이터베이스 테이블과 직접 매핑되지 않으며, 다른 엔티티 클래스에서 상속받아 사용될 수 있는 상태 정보와 매핑 정보를 정의하는 데 사용됩니다.
9.1 주요 특징 및 사용법
- 테이블에 직접 매핑되지 않음: @MappedSuperclass로 표시된 클래스는 직접적으로 데이터베이스 테이블에 매핑되지 않습니다. 이 클래스는 데이터베이스 테이블과 연결되는 것이 아니라, 상속받는 엔티티 클래스에 매핑 정보를 제공합니다.
- 상속받은 클래스에서 사용: 이 애너테이션을 사용한 클래스는 다른 엔티티 클래스에 의해 상속되어 그 매핑 정보가 활용됩니다. 상속받은 클래스들은 @MappedSuperclass에 정의된 매핑 정보를 그대로 상속받아 자신의 테이블 매핑 정보로 사용할 수 있습니다.
- 공통 매핑 정보 정의: 공통된 속성(예: id, 생성시간, 수정시간 등)이 필요한 엔티티들에 대해 중복 코드를 줄이고 코드의 재사용성을 높일 수 있습니다.
10. @EnableJpaAuditing
@EnableJpaAuditing 애너테이션은 Spring Data JPA에서 제공하는 기능으로, 엔티티의 생성, 수정 시간을 자동으로 관리할 수 있도록 도와줍니다. 이 애너테이션은 주로 스프링 부트 어플리케이션의 설정 클래스에 붙여 사용되며, JPA 엔티티의 이벤트를 감지하고 반응하여 자동으로 값을 업데이트하게 만드는 데 필수적입니다.
주요 기능
- 생성 및 수정 시간 자동 기록: @CreatedDate와 @LastModifiedDate 애너테이션을 사용한 필드에 자동으로 현재 시간을 할당합니다.
- 생성자 및 수정자 정보 자동 기록: @CreatedBy와 @LastModifiedBy 애너테이션을 사용한 필드에 자동으로 현재 사용자의 정보를 할당합니다.
활성화 방법
@EnableJpaAuditing 애너테이션은 보통 스프링 부트의 메인 클래스나 설정 클래스에 추가됩니다.
11. Board, BaseEntity 클래스 예제
// @MappedSuperclass 애너테이션은 이 클래스가 엔티티 클래스가 아니며,
// 다른 엔티티 클래스가 이 클래스를 상속할 때, 이 클래스의 매핑 정보를 상속받도록 지정합니다.
@MappedSuperclass
// @EntityListeners 애너테이션은 엔티티의 생명주기 이벤트를 처리하기 위한 리스너를 지정합니다.
// AuditingEntityListener를 지정하여, 생성 및 수정 날짜를 자동으로 관리하게 합니다.
@EntityListeners(value ={AuditingEntityListener.class})
// @Getter 애너테이션은 Lombok 라이브러리를 사용하여, 이 클래스의 모든 필드에 대한 getter 메서드를 자동으로 생성합니다.
@Getter
public class BaseEntity {
// @CreatedDate 애너테이션은 JPA가 엔티티가 처음 저장될 때 현재 날짜/시간을 자동으로 설정하도록 합니다.
@CreatedDate
// @Column 애너테이션은 데이터베이스 테이블의 컬럼에 대한 세부 설정을 제공합니다.
// 여기서는 컬럼 이름을 "regDate"로 지정하고, 이 컬럼이 업데이트되지 않도록 설정합니다.
@Column(name="regDate", updatable=false)
private LocalDateTime regDate;
// @LastModifiedDate 애너테이션은 엔티티의 내용이 업데이트될 때마다 현재 날짜/시간을 자동으로 설정하도록 합니다.
@LastModifiedDate
// 여기서는 컬럼 이름을 "moddate"로 지정합니다.
@Column(name="moddate")
private LocalDateTime modDate;
}
// @Entity 애너테이션은 이 클래스가 JPA 엔티티임을 나타냅니다.
@Entity
public class Board extends BaseEntity{
// @Id 애너테이션은 이 필드가 데이터베이스 테이블의 기본 키(primary key) 역할을 함을 나타냅니다.
@Id
// @GeneratedValue 애너테이션은 기본 키의 값을 자동으로 생성하는 전략을 지정합니다.
// 여기서는 IDENTITY 전략을 사용하여, 데이터베이스가 자동으로 고유 ID를 생성하도록 합니다.
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long bno;
@Column(length = 500, nullable = false)
private String title;
@Column(length = 2000, nullable = false)
private String content;
@Column(length = 50, nullable = false)
private String writer;
}
12. JpaRepository 예제
public interface BoardReposirory extends JpaRepository<Board, Long> {}
- 인터페이스로 만들고 JpaRepository를 상속 받으면 해당 Jpa에 구현된 메서드들을 사용할 수 있다.
- 제네릭으로 첫번째 인자엔 DB에 생성할 Entity 클래스명을 입력하고, 두번째 인자엔 기본 키 타입을 입력한다.
- Jpa는 이 글 5번에서 알 수 있듯이, 따로 SQL을 작성하지 않고 데이터베이스를 통해 객체를 저장, 검색, 삭제 및 갱신할 수 있는 방법을 제공한다.
13. BoardRepositoryTests 예제
@SpringBootTest
@Log4j2
public class BoardRepositoryTests {
@Autowired
private BoardRepository boardRepository;
@Test
public void testSave() {
IntStream.rangeClosed(1,100).forEach(i -> {
Board board = Board.builder()
.title("title____"+i)
.content("content____"+i)
.writer("Writer"+i%10)
.build();
boardRepository.save(board);
});
}
@Test
public void testFindOne() {
Long bno = 77L; //몇번 bno을 읽어올까요?
Optional<Board> result = boardRepository.findById(bno); //Optional 컬렉션은 알아서 Exception 처리를 해줌. Board가 있으면 저장해서 반환함.
Board board = result.orElseThrow(); //있으면 주고 없으면 말고
log.info(board);
}
@Test
public void testFindAll() {
// 저장된 모든 Board 객체를 조회하여 각 객체의 정보를 로그로 출력
List<Board> boards = boardRepository.findAll();
boards.forEach(board -> {
log.info(board.toString());
});
}
}
댓글