EntityManager
JPA(Java Persistence API)의 핵심 인터페이스로, 엔티티를 데이터베이스와 연동하여 관리합니다. 주로 엔티티의 생명주기(생성, 읽기, 업데이트, 삭제)를 관리하고, 쿼리를 실행하며 트랜잭션을 처리하는 역할을 합니다.
- 클라이언트당 하나씩 생성
@Getter
@Setter
@ToString
@Entity
public class User {
@Id
private String id;
private String password;
private String name;
private String email;
private Date inDate;
private Date upDate;
}
@SpringBootApplication
public class TestApplication implements CommandLineRunner {
@Autowired
EntityManagerFactory emf;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(TestApplication.class);
app.setWebApplicationType(WebApplicationType.NONE);
app.run(args);
}
@Override
public void run(String... args) throws Exception {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
User user = new User();
user.setId("aaa");
user.setPassword("1234");
user.setName("Lee");
user.setEmail("aaa@aaa.com");
user.setInDate(new Date());
user.setUpDate(new Date());
/* 트랜잭션 시작 */
tx.begin();
/* 저장 */
em.persist(user);
/* 같은 엔티티를 여러번 저장해도 한번만 INSERT 됨 */
em.persist(user);
/* 변경 */
user.setPassword("4321");
user.setEmail("bbb@bbb.com");
tx.commit();
/* 조회 */
/* DB에 있는 KEY , em에 있으면 DB 조회 안함 */
User user2 = em.find(User.class, "aaa");
System.out.println("user2 = " + user2);
/* DB에 없는 KEY , em에 없으면 DB 조회. user3은 null 발생 */
User user3 = em.find(User.class, "bbb");
System.out.println("user3 = " + user3);
/* 삭제 */
tx.begin();
em.remove(user);
tx.commit();
}
}
@Getter
@Setter
@ToString
@Entity
public class Board {
@Id
@GeneratedValue
private Long bno;
private String title;
private String writer;
private String content;
private Long viewCnt;
@Temporal(value = TemporalType.TIMESTAMP)
private Date inDate;
@Temporal(value = TemporalType.TIMESTAMP)
private Date upDate;
}

import org.springframework.data.repository.CrudRepository;
/* Spring Data JPA 가 CrudRepository 를 알아서 구현해준다 */
public interface BoardRepository extends CrudRepository<Board, Long> {}
package com.example.test;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
/* order 애너테이션을 사용하기위해 작성 */
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class BoardRepositoryTest {
@Autowired
private BoardRepository boardRepo;
@Test
@Order(4)
public void deleteTest(){
boardRepo.deleteById(1L); /*1 번 게시물 삭제 */
/* 못찾을경우 null 반환 */
Board board = boardRepo.findById(1L).orElse(null);
/* 삭제되었으니까 null 이여야함 */
assertTrue(board==null);
}
@Test
@Order(3)
public void updateTest(){
Board board = boardRepo.findById(1L).orElse(null);
assertTrue(board!=null);
board.setTitle("modified Title");
boardRepo.save(board);
Board board2 = boardRepo.findById(1L).orElse(new Board());
assertTrue(board.getTitle().equals(board2.getTitle()));
}
@Test
@Order(2) /* 2번째로 Test 실행 */
public void selectTest(){
/* 값이 없을때 예외 발생*/
/*Board board = boardRepo.findById(1L).get();*/
/* 값이 없을때 null 반환 */
Board board = boardRepo.findById(1L).orElse(null);
assertTrue(board!=null);
}
@Test
@Order(1) /* 1번째로 Test 실행 */
/* Insert 진행후 select 진행 */
public void insertTest(){
Board board = new Board();
board.setBno(1L);
board.setTitle("Test Title");
board.setContent("This is Test");
board.setWriter("aaa");
board.setViewCnt(0L);
board.setInDate(new Date());
board.setUpDate(new Date());
boardRepo.save(board);
}
}
| 쿼리종류 | 의미 |
|---|---|
| JPQL | DB 테이블이 아닌 entity 대상으로 쿼리를 작성. SQL과 유사하다. (JPA + SQL) |
| "SELECT b FROM Board b Where b.title =?1" (대소문자구별함) | |
| 쿼리메서드 | 메서드 이름으로 JPQL을 자동생성 |
| LIST list = BoardRepofindByTitleAndwriter("title","writer1"); | |
| JPA Criteria | JPQL을 메서드의 조합으로 작성 (JPA표준. 긹고 읽기어려워 불편함) API |
| cq.select(b).where(cb.equal(b.get("title"), | |
| Querydsl | JPQL을 메서드의 조합으로 작성. Criteria보다 간결. 오픈소스 |
| List list = queryFactory.selectFrom(board).where(board.title.eq("title1")).fetch(); | |
| Native SQL | JPQL 대신 SQL을 직접 작성. 복잡한 SQL 작성가능 @Query |
Repository에 메서드이름을 규칙에 맞게 작성하면 Spring Data가 메서드 이름만보고 자동으로 쿼리를 만들어준다. (= 메서드이름이 규칙)
예) find + (entity명) + by + 컬럼이름

public interface BoardRepository extends CrudRepository<Board, Long> {
/* SELECT COUNT(*) FROM BOARD WHERE WRITER = :writer */
int countAllByWriter(String writer);
/* SELECT * FROM BOARD WHERE WRITER = :writer */
List<Board> findByWriter(String writer);
/* SELECT * FROM BOARD WHERE TITLE = :title AND WRITER = :writer */
List<Board> findByTitleAndWriter(String title, String writer);
/* DELETE FROM BOARD WHERE WRITER = :writer */
/* 여러 사용자가 동시에 데이터를 삭제하려고 할 때 충돌이 발생할 수 있다 */
/* 여러 행을 삭제하는 작업 중 일부가 성공하고 나머지가 실패하면 데이터 불일치가 발생할수있다 */
@Transactional /* Tx 필수 */
int deleteByWriter(String writer);
}
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class BoardRepositoryTest2 {
@Autowired
BoardRepository boardRepo;
/* 매 테스트마다 테스트 데이터를 넣는다 */
@BeforeEach
public void testData(){
for(int i=1; i<=100; i++){
Board board = new Board();
board.setBno((long)i);
board.setTitle("title"+i);
board.setContent("content"+i);
board.setWriter("writer"+ (i%5)); /* writer0~4 까지*/
board.setViewCnt((long)(Math.random()*100)); /* cnt는 0~99까지 */
board.setInDate(new Date());
board.setUpDate(new Date());
boardRepo.save(board);
}
}
@Test
void countAllByWriter() {
/* writer1이 작성한 게시글을 count */
assertTrue(boardRepo.countAllByWriter("writer1")==20);
}
@Test
void findByWriter() {
/* writer1을 찾는다 */
List<Board> list = boardRepo.findByWriter("writer1");
assertTrue(list.size()==20);
list.forEach(System.out::println);
}
@Test
void findByTitleAndWriter() {
}
@Test
void deleteByWriter() {
assertTrue(boardRepo.deleteByWriter("writer1")==20);
List<Board> list = boardRepo.findByWriter("writer1");
assertTrue(list.size()==0);
}
}





public interface BoardRepository extends CrudRepository<Board, Long> {
@Query("SELECT b fROM Board b") /* JPQL 명칭 대소문자 구분에 주의 */
List<Board> findAllBoard(); /* 메서드 이름은 아무거나해도 상관없음 */
@Query("SELECT b FROM Board b WHERE b.title=:title AND b.writer=:writer") /* 매개변수 이름 으로 조회 */
List<Board> findByTitleAndWriter2(String title, String writer);
@Query(value = "SELECT * FROM BOARD", nativeQuery = true) /* SQL문 */
List<Board> findAllBoardBySQL();
@Query(value = "SELECT title, writer FROM BOARD", nativeQuery = true) /* SQL문 일부만 SELECT */
List<Object[]> findAllBoardBySQL2();
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class BoardRepositoryTest3 {
@Autowired
public EntityManager em;
@Autowired
BoardRepository boardRepo;
/* 매 테스트마다 테스트 데이터를 넣는다 */
@BeforeEach
public void testData(){
for(int i=1; i<=100; i++){
Board board = new Board();
board.setBno((long)i);
board.setTitle("title"+i);
board.setContent("content"+i);
board.setWriter("writer"+ (i%5)); /* writer0~4 까지*/
board.setViewCnt((long)(Math.random()*100)); /* cnt는 0~99까지 */
board.setInDate(new Date());
board.setUpDate(new Date());
boardRepo.save(board);
}
}
@Test
@DisplayName("@Query로 JPQL작성 테스트")
public void queryAnnoTest() {
List<Board> list = boardRepo.findAllBoard();
assertTrue(list.size()==100);
}
@Test
@DisplayName("@Query로 JPQL작성 테스트 - 매개변수 이름")
public void findByTitleAndWriter2() {
List<Board> list = boardRepo.findByTitleAndWriter2("title1", "writer1");
assertTrue(list.size()==1);
}
@Test
@DisplayName("@Query로 네이티브 쿼리(SQL)작성 테스트")
public void nativeQueryTest() {
List<Board> list = boardRepo.findAllBoardBySQL();
assertTrue(list.size()==100);
}
@Test
@DisplayName("@Query로 네이티브 쿼리(SQL)작성 테스트 - 일부만 SELECT")
public void nativeQueryTest2() {
List<Object[]> list = boardRepo.findAllBoardBySQL2();
assertTrue(list.size()==100);
}
@Test
@DisplayName("createQuery로 JPQL작성 테스트")
public void createQueryTest() {
String query = "SELECT b FROM Board b"; /* Board를 b로 저장하고 b의 전부를 조회 */
TypedQuery<Board> tQuery = em.createQuery(query, Board.class);
List<Board> list = tQuery.getResultList();
assertTrue(list.size()==100);
}
}



1. pom.xml 파일에 dependency 의존성 추가, 플러그인 추가
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>Test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Test</name>
<description>Test</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- querydsl S-->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>5.0.0</version>
<classifier>jakarta</classifier>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>5.0.0</version>
<classifier>jakarta</classifier>
</dependency>
<!-- querydsl E-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<!-- 코드를 해제할경우 QUser와 QBoard가 두번생성됨 -->
<!-- <outputDirectory>target/generated-sources/java</outputDirectory>-->
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
maven Test > comfile 더블클릭해서 플러그인 설치

프로젝트 구조로 이동 > generated-source를 파란색인 소스폴더로 바꾼다

아래처럼 QBoard와 QUser가 정상적으로 불러왔는지 체크

TDD 코드 작성
package com.example.test;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import jakarta.persistence.EntityManager;
import java.util.Date;
import static com.example.test.QBoard.board;
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class BoardRepositoryTest4 {
@Autowired
public EntityManager em;
@Autowired
BoardRepository boardRepo;
/* 매 테스트마다 테스트 데이터를 넣는다 */
@BeforeEach
public void testData(){
for(int i=1; i<=100; i++){
Board board = new Board();
board.setBno((long)i);
board.setTitle("title"+i);
board.setContent("content"+i);
board.setWriter("writer"+ (i%5)); /* writer0~4 까지*/
board.setViewCnt((long)(Math.random()*100)); /* cnt는 0~99까지 */
board.setInDate(new Date());
board.setUpDate(new Date());
boardRepo.save(board);
}
}
@Test
@DisplayName("querydsl로 쿼리 작성 테스트1 - 간단한 쿼리 작성")
public void querydslTest1() {
// com.example.test.QBoard board = com.example.test.QBoard.board;
/* 1. JPAQueryFactory를 생성 */
JPAQueryFactory qf = new JPAQueryFactory(em);
/* 2. 쿼리 작성 */
/* qf.selectForm(QBoard board) */
JPAQuery<Board> query = qf.selectFrom(board)
/* board의 title이 title1 과 equals 한지 */
.where(board.title.eq("title1"));
/* 3.쿼리 실행 */
List<Board> list = query.fetch();
list.forEach(System.out::println);
}
}