728x90

마지막으로 Java NIO non-blocking으로 만든 서버에서도 문제가 있었다.

accept 할 때마다 조건문으로 확인을 하고 Thread.sleep을 걸어주면, 그만큼 리소스의 낭비가 생기기 때문이다.

그리고 sleep 중간에 요청이 들어오면, sleep이 끝날 때까지 대기를 하기 때문에 그만큼 딜레이가 생긴다.

 

이런 이벤트들을 계속 확인하지 않고, 여러 이벤트를 추적할 수 있도록 변경해보자.

이번 내용은 네트워크 프로그래밍에서 배웠던 내용을 C에서 Java로 변경한 느낌일 것이다.

 

 

Selector 이론

SelectableChannel

 

selectableChannel에는 register 함수를 제공한다.

register는 Selector에 channel을 등록할 수 있다.

public abstract class SelectableChannel
    extends AbstractInterruptibleChannel
    implements Channel
{

    public abstract SelectableChannel configureBlocking(boolean block)
        throws IOException;

    public final SelectionKey register(Selector sel, int ops)
        throws ClosedChannelException
    {
        return register(sel, ops, null);
    }
}

 

 

Selector

 

여러 Channel의 이벤트를 등록하고 준비된 이벤트를 모아서 조회할 수 있도록 한다.

public abstract class Selector implements Closable{
    public abstract int select() throws IOException;
    public abstract Set<SelectionKey> selectedKEys();
}

 

 

Selector를 만들고, channel에 selector와 관심 있는 이벤트를 등록한다.

channel의 register를 이용하면 내부 함수에서 다시 selector의 register를 호출하기 때문에, channel에 등록하더라도 selector에서 이벤트를 받을 수 있다.

관심있는 이벤트가 ops인데, 무슨 이벤트인지에 대한 정보이다.

이벤트들은 다음과 같다.

  • OP_READ: Channel에 읽기 준비가 완료되었다.
  • OP_WRITE: Channel에 쓸 준비가 완료되었다.
  • OP_ACCEPT: serverSocketChannel에서 accept 할 준비가 완료되었다.
  • OP_CONNECT: socketChannel에서 connect 할 준비가 완료되었다.

 

이렇게 해서 Accept를 등록하려는 코드는 다음과 같다.

serverChannel.register(selector, SelectionKey.OP_ACCEPT);

 

 

등록이 끝나면 이제 Accept를 대기할 텐데, select를 사용하면 준비가 될 때까지 쓰레드 blocking이 된다.

 

while(true){
    selector.select();
    
    var selectedKEys = selector.selectedKeys().iterator();
    
    while(selectedKeys.hasNext()){
    	var key = selectedKeys.next();
        /////
        selectedKeys.remove();
    }
}

 

select에서 준비가 완료된 작업들이 발견된다면, 다음 line으로 이동한다.

준비가 완료된 작업을 가져올 때는 selectedKeys를 사용한다.

 

여기서 SelectionKey를 iterator로 가져오게 되는데, SelectionKey는 다음과 같다.

public abstract class SelectionKey {

    public abstract SelectableChannel channel();

    public abstract Selector selector();

    public abstract int interestOps();

    public final boolean isReadable() {
        return (readyOps() & OP_READ) != 0;
    }

    public final boolean isWritable() {
        return (readyOps() & OP_WRITE) != 0;
    }

    public final boolean isConnectable() {
        return (readyOps() & OP_CONNECT) != 0;
    }

    public final boolean isAcceptable() {
        return (readyOps() & OP_ACCEPT) != 0;
    }

}

 

 

SelectionKey는 channel과 selector, 발생한 이벤트의 정보를 가지고 있는 객체이다.

여기에 모든 정보가 있기 때문에, 이거만 있으면 추가적인 정보를 넘길 필요가 없을 것이다.

 

이렇게 accept를 알아보았고, read인 경우에는 해당 이벤트를 받아 동일하게 처리하면 될 것이다.

 

 

Selector 실습

이제 Selector를 사용하여 더 최적화를 해보도록 하자.

저번에 작성했던 NIO 서버를 변경할 것이다.

@Slf4j
public class SelectorServer {

    private static ExecutorService executorService = Executors.newFixedThreadPool(50);

    @SneakyThrows
    public static void main(String[] args) {
        log.info("start");
        try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            Selector selector = Selector.open()){

            serverSocketChannel.bind(new InetSocketAddress("localhost", 8080));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while(true){

                selector.select();

                Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();

                while(selectedKeys.hasNext()){
                    SelectionKey key = selectedKeys.next();
                    selectedKeys.remove();

                    if(key.isAcceptable()){
                        SocketChannel clientSocket = ((ServerSocketChannel)key.channel()).accept();
                        clientSocket.configureBlocking(false);
                        clientSocket.register(selector, SelectionKey.OP_READ);
                    }else if(key.isReadable()){
                        SocketChannel clientSocket = (SocketChannel) key.channel();

                        String responseBody = handleRequest(clientSocket);
                        sendResponse(clientSocket, responseBody);
                    }
                }

            }
        }
    }

    @SneakyThrows
    private static String handleRequest(SocketChannel clientSocket) {
        ByteBuffer requestByteBuffer = ByteBuffer.allocateDirect(1024);
        clientSocket.read(requestByteBuffer);

        requestByteBuffer.flip();
        String requestBody = StandardCharsets.UTF_8.decode(requestByteBuffer).toString();
        log.info("request: {}", requestBody);

        return requestBody;
    }

    @SneakyThrows
    public static void sendResponse(SocketChannel clientSocket, String requestBody){
        CompletableFuture.runAsync(() -> {

            try {
                Thread.sleep(10);
                String content = "received: " + requestBody;
                ByteBuffer responseByteBuffer = ByteBuffer.wrap(content.getBytes());
                clientSocket.write(responseByteBuffer);
                clientSocket.close();
            } catch (Exception e) {
            }
        }, executorService);

    }
}

 

여기서 Accept 이벤트와 Read 이벤트를 처리하고 있는 것을 볼 수 있다.

각각의 이벤트 키로 해당 이벤트를 넣어 처리를 하며, 다른 쓰레드에서 비동기적으로 마지막 메서드를 실행하는 것을 볼 수 있다.

 

 

'백엔드 > 리액티브 프로그래밍' 카테고리의 다른 글

Java AIO  (0) 2024.03.19
Java IO Server 서버를 NIO로 변경  (0) 2024.03.19
Java NIO  (0) 2024.03.18
JAVA IO  (0) 2024.03.18
Mutiny  (0) 2024.03.14
728x90

지금까지 배운 것을 바탕으로 서버를 만들고, 차례로 성능을 높여보자.

 

 

우선 간단한 기본 서버이다.

 

@Slf4j
public class JavaIOServer {

    @SneakyThrows
    public static void main(String[] args) {
        log.info("start");

        try(ServerSocket serverSocket = new ServerSocket()){
            serverSocket.bind(new InetSocketAddress("localhost", 8080));

            while(true){

                Socket clientSocket = serverSocket.accept();

                byte[] requestBytes = new byte[1024];
                InputStream inputStream = clientSocket.getInputStream();
                inputStream.read(requestBytes);

                log.info("request: {}", new String(requestBytes).trim());

                OutputStream outputStream = clientSocket.getOutputStream();
                String response = "I am Server";
                outputStream.write(response.getBytes());
                outputStream.flush();
            }

        }
    }
}

 

간단한 서버이다.

while(true)를 돌며 계속 응답을 받고, 받은 후에 클라이언트에게 I am Server라는 값을 준다.

 

클라이언트이다.

요청을 보내고, 응답을 받은 후 종료된다.

@Slf4j
public class JavaIOClient {

    public static void main(String[] args) {
        try(Socket socket = new Socket()){
            socket.connect(new InetSocketAddress("localhost", 8080));

            var outputStream = socket.getOutputStream();
            String requestBody = "I am Seungkyu client";
            outputStream.write(requestBody.getBytes());
            outputStream.flush();

            InputStream inputStream = socket.getInputStream();
            byte[] responseBytes = new byte[1024];
            inputStream.read(responseBytes);

            log.info("result: {}", new String(responseBytes).trim());


        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }
}

이런 식으로 서버에 계속 요청이 오는 것을 볼 수 있다.

 

이제 서버를 NIO로 변경해보자.

@Slf4j
public class JavaNIOBlockingServer {

    @SneakyThrows
    public static void main(String[] args) {
        log.info("start");

        try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {

            serverSocketChannel.bind(new InetSocketAddress("localhost", 8080));

            while(true){

                SocketChannel socketChannel = serverSocketChannel.accept();

                ByteBuffer requestByteBuffer = ByteBuffer.allocateDirect(1024);
                socketChannel.read(requestByteBuffer);
                requestByteBuffer.flip();
                String requestBody = StandardCharsets.UTF_8.decode(requestByteBuffer).toString();
                log.info("request: {}", requestBody);

                ByteBuffer responseByteBuffer = ByteBuffer.wrap("I am Server".getBytes());
                socketChannel.write(responseByteBuffer);
                socketChannel.close();
            }

        }
    }
}

 

Channel을 이용하여 데이터를 읽고 쓰며, 버퍼를 사용하는 것을 볼 수 있다.

이렇게 속도를 높이겠지만, 아직도 accept에서 Blocking이 발생하고 있다.

 

그렇기에 이거를 Non-Blocking으로 바꿔보자

@Slf4j
public class JavaNIONonBlockingServer {

    @SneakyThrows
    public static void main(String[] args) {
        log.info("start");

        try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {

            serverSocketChannel.bind(new InetSocketAddress("localhost", 8080));
            serverSocketChannel.configureBlocking(false);

            while(true){

                SocketChannel socketChannel = serverSocketChannel.accept();

                if(socketChannel == null){
                    Thread.sleep(100);
                    continue;
                }

                ByteBuffer requestByteBuffer = ByteBuffer.allocateDirect(1024);
                while(socketChannel.read(requestByteBuffer) == 0){
                    log.info("reading");
                }
                socketChannel.read(requestByteBuffer);
                requestByteBuffer.flip();
                String requestBody = StandardCharsets.UTF_8.decode(requestByteBuffer).toString();
                log.info("request: {}", requestBody);

                ByteBuffer responseByteBuffer = ByteBuffer.wrap("I am Server".getBytes());
                socketChannel.write(responseByteBuffer);
                socketChannel.close();
            }
        }
    }
}

 

configureBlocking에 false를 주어 Non-Blocking으로 동작하게 했다.

하지만 socketChannel이 null 일 때 Thread.sleep을 하기에 좋은 방법은 아닌 것 같다.

또한 requestBuffer에도 데이터가 있는지를 계속 체크 해주어야 하기 때문에, 이 부분도 수정이 필요해보인다.

 

이거를 1000개의 요청으로 얼마나 걸리는지 테스트를 해보도록 하자.

테스트 용도의 코드는 다음과 같다.

@Slf4j
public class JavaIOMultiClient {
    private static ExecutorService executorService = Executors.newFixedThreadPool(50);

    @SneakyThrows
    public static void main(String[] args) {
        log.info("start main");

        List<CompletableFuture<Void>> futures = new ArrayList<>();
        long start = System.currentTimeMillis();

        for (int i = 0; i < 1000; i++) {
            var future = CompletableFuture.runAsync(() -> {
                try (Socket socket = new Socket()) {
                    socket.connect(new InetSocketAddress("localhost", 8080));

                    OutputStream out = socket.getOutputStream();
                    String requestBody = "This is client";
                    out.write(requestBody.getBytes());
                    out.flush();

                    InputStream in = socket.getInputStream();
                    byte[] responseBytes = new byte[1024];
                    in.read(responseBytes);
                    log.info("result: {}", new String(responseBytes).trim());
                } catch (Exception e) {}
            }, executorService);

            futures.add(future);
        }

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        executorService.shutdown();
        log.info("end main");
        long end = System.currentTimeMillis();
        log.info("duration: {}", (end - start) / 1000.0);
    }
}

 

50개의 쓰레드로 1000개의 요청을 한다.

 

이렇게 실행해서 시간을 측정해보니, 약 12초가 나왔다.

 

여기서 코드를 수정해서 속도를 높일 수는 없을까?

 

서버에서 처리하는 코드를 CompletableFuture를 이용해 별개의 쓰레드에서 처리하도록 했다.

@Slf4j
public class JavaNIONonBlockingServer {
    @SneakyThrows
    public static void main(String[] args) {
        log.info("start main");
        try (ServerSocketChannel serverSocket = ServerSocketChannel.open()) {
            serverSocket.bind(new InetSocketAddress("localhost", 8080));
            serverSocket.configureBlocking(false);

            while (true) {
                SocketChannel clientSocket = serverSocket.accept();
                if (clientSocket == null) {
                    Thread.sleep(100);
                    continue;
                }

                CompletableFuture.runAsync(() -> {
                    handleRequest(clientSocket);
                });
            }
        }
    }

    @SneakyThrows
    private static void handleRequest(SocketChannel clientSocket) {
        ByteBuffer requestByteBuffer = ByteBuffer.allocateDirect(1024);
        while (clientSocket.read(requestByteBuffer) == 0) {
            log.info("Reading...");
        }
        requestByteBuffer.flip();
        String requestBody = StandardCharsets.UTF_8.decode(requestByteBuffer).toString();
        log.info("request: {}", requestBody);

        Thread.sleep(10);

        ByteBuffer responeByteBuffer = ByteBuffer.wrap("This is server".getBytes());
        clientSocket.write(responeByteBuffer);
        clientSocket.close();
    }
}

 

이렇게 서버를 실행해보니 시간이 많이 준 것을 볼 수 있었다.

 

'백엔드 > 리액티브 프로그래밍' 카테고리의 다른 글

Java Selector  (0) 2024.03.20
Java AIO  (0) 2024.03.19
Java NIO  (0) 2024.03.18
JAVA IO  (0) 2024.03.18
Mutiny  (0) 2024.03.14
728x90

Java에서 NIO에 대해 알아보자.

 

java New Input/Output의 줄인말이며, 파일과 네트워크에 데이터를 읽고 쓸 수 있는 API를 제공한다.

buffer기반이며, non-blocking을 지원한다.

 

java IO는 InputStream, OutputStream을 사용해서 방향이 정해져있었다면, NIO는 Channel을 이용해 읽고 쓰기가 한번에 가능하다.

또한 NIO는 아예 시작부터 buffer의 단위로 데이터를 주고 받는다.

 

Channel, Buffer

데이터를 읽을 때는 Channel read()를 사용하여 버퍼에 데이터를 저장한다.

데이터를 쓸 때는 Channel write()를 사용하여 버퍼에 데이터를 쓴다.

 

이 버퍼에서도 ByteBuffer부터 CharBuffer까지 다양한 타입의 버퍼가 존재한다.

 

Buffer의 위치

버퍼의 현재 커서를 나타내는 다양한 속성이 있다.

capacity: Buffer의 크기이다. Buffer 생성시에 결정된다.

position: Buffer의 현재 위치이다. 버퍼에서 데이터를 읽거나 쓸 때 시작하는 위치이며, 1Byte가 추가될 때마다 1씩 증가한다.

limit: Buffer에서 데이터를 읽거나 쓸 수 있는 마지막 위치이다. limit 이후로 데이터를 읽거나 쓰기 불가능하다.

mark: 현재 position의 위츠를 지정한다.

 

Buffer의 위치 메서드

flip: Buffer의 limit 위치를 현재 position으로 이동시키고, position을 0으로 리셋한다.

rewind: Buffer의 position 위치를 0으로 리셋. limit은 유지한다.

clear: Buffer의 limit 위치를 capacity 위치로 이동시키고, position을 0으로 리셋한다.

 

Buffer

Java IO는 JVM의 버퍼에만 접근이 가능하고, 시스템 자체에는 접근이 불가능했다.

하지만 Java NIO는 시스템 버퍼에도 접근이 가능하다.

NIO의 버퍼들이다.

DirectByteBuffer HeapByteBuffer
시스템 자체 메모리에 저장 JVM Heap 메모리에 저장
자체 메모리에서 JVM으로 복사를 하지 않기 때문에 속도가 빠르다. JVM으로 복사가 일어나므로 데이터를 읽고 쓰는 속도가 느리다.
allocate, deallocate가 느리다. gc에서 관리하기에 allocate, deallocate가 빠르다.

 

DirectByteBuffer는 allocateDirect() 함수로 생성이 가능하고, HeapByteBuffer는 allocate(), wrap() 함수로 생성 가능하다.

isDirect인지 DirectBuffer인지 아닌지 확인 가능하다.

 

FileChannel - Read

@Slf4j
public class FileChannelReadExample {

    public static void main(String[] args) throws IOException {
        log.info("start");

        var file = new File("path");

        try(var fileChannel = FileChannel.open(file.toPath())){
            var byteBuffer = ByteBuffer.allocateDirect(1024);
            fileChannel.read(byteBuffer);
            byteBuffer.flip();

            var result = StandardCharsets.UTF_8.decode(byteBuffer);
            log.info("result: {}", result);
        }
        log.info("end");
    }
}

 

allocateDirect로 커널을 사용하여 파일을 읽어온다.

NIO를 사용하기 때문에 FileChannel에서 읽고 쓰기가 가능하다.

 

아래는 ByteBuffer.wrap()으로 JVM버퍼를 사용하는 예제이다.

@Slf4j
public class FileChannelWriteExample {

    public static void main(String[] args) throws IOException {
        var file = new File("path");

        var mode = StandardOpenOption.WRITE;
        try(var fileChannel = FileChannel.open(file.toPath(), mode)){
            var buffer = ByteBuffer.wrap("Hi Seungkyu~~".getBytes());
            var result = fileChannel.write(buffer);
            log.info("result: {}", result);
        }
    }
}

 

SocketChannel

이제 배운것들로 소켓 서버와 클라이언트를 만들어보자.

 

우선 서버이다.

@Slf4j
public class ServerSocketChannelExample {

    public static void main(String[] args) throws IOException {

        log.info("start");

        try(var serverChannel = ServerSocketChannel.open()){
            var address = new InetSocketAddress("localhost", 8080);
            serverChannel.bind(address);

            try(var clientSocket = serverChannel.accept()){
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
                clientSocket.read(byteBuffer);
                byteBuffer.flip();

                var request = new String(byteBuffer.array()).trim();
                log.info("request: {}", request);

                var response = "I am Server";
                var responseBuffer = ByteBuffer.wrap(response.getBytes());
                clientSocket.write(responseBuffer);
                responseBuffer.flip();
            }

        }

        log.info("end");
    }
}

 

Direct 버퍼를 만들어서 읽어온다.

Socket에서도 read와 flip을 사용해서 읽어노는 것을 볼 수 있다.

 

다음은 클라이언트이다.

@Slf4j
public class ClientSocketChannelExample {

    public static void main(String[] args) throws IOException {
        log.info("start");

        try(var socketChannel = SocketChannel.open()){
            var address = new InetSocketAddress("localhost", 8080);
            var connected = socketChannel.connect(address);
            log.info("connected: {}", connected);

            String request = "I am Client";
            ByteBuffer requestBuffer = ByteBuffer.wrap(request.getBytes());
            socketChannel.write(requestBuffer);
            requestBuffer.clear();
            
            ByteBuffer responseBuffer = ByteBuffer.allocateDirect(1024);
            while (socketChannel.read(responseBuffer) > 0){
                responseBuffer.flip();
                log.info("response: {}", StandardCharsets.UTF_8.decode(responseBuffer));
                responseBuffer.clear();
            }
        }
        log.info("end");
    }
}

 

 

'백엔드 > 리액티브 프로그래밍' 카테고리의 다른 글

Java AIO  (0) 2024.03.19
Java IO Server 서버를 NIO로 변경  (0) 2024.03.19
JAVA IO  (0) 2024.03.18
Mutiny  (0) 2024.03.14
RxJava Reactor  (2) 2024.03.14
728x90

이번엔 Map을 mapping 해본다.

 

이런 테이블이 있다고 해보자.

import jakarta.persistence.*;

import java.util.HashMap;
import java.util.Map;

@Entity
@Table(name = "book")
public class book {
    @Id
    private String id;
    private String title;
    private String content;
    @ElementCollection
    @CollectionTable(
            name = "book_prop",
            joinColumns = @JoinColumn(name="book_id")
    )
    @MapKeyColumn(name = "name")
    @Column(name = "value")
    private Map<String, String> props = new HashMap<>();

    protected book(){}

    public book(String id, String title, String content, Map<String, String> props) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.props = props;
    }
    
    //getter... setter...
}

이렇게 @CollectionTable로 저장할 테이블을 지정을 해주고

Map에 들어간 데이터를 어떤 column에 넣어줄지 PK는 @MapKeyColumn에, 그냥 데이터는 @Column으로 지정을 해준다.

 

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

import java.util.HashMap;
import java.util.Map;

public class book_main {

    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction entityTransaction = entityManager.getTransaction();

        Map<String, String> props1 = new HashMap<>();
        Map<String, String> props2 = new HashMap<>();

        props1.put("name1", "value1");
        props1.put("name2", "value2");
        book book1 = new book("1", "이름", "내용", props1);
        book book2;

        try{
            entityTransaction.begin();
            entityManager.persist(book1);
            entityTransaction.commit();
        }catch(NullPointerException exception){
            throw new NullPointerException();
        }

        try{
            book2 = entityManager.find(book.class, "1");
        }catch(NullPointerException exception){
            throw new NullPointerException();
        }

        System.out.println("book: " + book2);
        System.out.println("props: " + book2.getProps());
    }
}

이렇게 추가를 하고 조회를 하면

정상 작동하는 것을 볼 수 있다.

 

늘 그렇듯이 이번엔 Embeddable 타입이다.

이런 테이블이 있다고 하고, 여기서 PropValue를 Embeddable로 만들 생각이다.

우선 Embeddable 타입을 먼저 작성한다.

import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Embeddable;

@Embeddable
@Access(AccessType.FIELD)
public class PropValue {
    private String value;
    private boolean enabled;

    public PropValue(String value, boolean enabled) {
        this.value = value;
        this.enabled = enabled;
    }

    protected PropValue () {}
	
    //getter...setter...
    
    @Override
    public String toString() {
        return "PropValue{" +
                "value='" + value + '\'' +
                ", enabled=" + enabled +
                '}';
    }
}

이렇게 항상 작성하던 @Embeddable을 작성하고

import jakarta.persistence.*;

import java.util.HashMap;
import java.util.Map;

@Entity
@Table(name = "book")
public class book2 {
    @Id
    private String id;
    private String title;
    private String content;
    @ElementCollection
    @CollectionTable(
            name = "book_prop",
            joinColumns = @JoinColumn(name = "book_id")
    )
    @MapKeyColumn(name = "name")
    private Map<String, PropValue> props = new HashMap<>();

	//getter...setter...
    
    @Override
    public String toString() {
        return "book2{" +
                "id='" + id + '\'' +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", props=" + props +
                '}';
    }
}

@MapKeyColumn을 이용해서 그랬던 것처럼 작성을 해준다.

여기에 Map의 타입에 <PK, @Embeddable>을 넣어서 작성해준다.

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

import java.util.HashMap;
import java.util.Map;

public class book_main2 {

    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction entityTransaction = entityManager.getTransaction();

        Map<String, PropValue> Propvalues = new HashMap<>();

        PropValue propValue1 = new PropValue("value1", true);
        PropValue propValue2 = new PropValue("value2", true);

        Propvalues.put("String1", propValue1);
        Propvalues.put("String2", propValue2);

        book2 book1 = new book2("id1", "title1", "content1", Propvalues);
        book2 book2;

        try{
            entityTransaction.begin();
            entityManager.persist(book1);
            entityTransaction.commit();
        }catch (NullPointerException exception){
            throw new NullPointerException();
        }

        try{
            book2 = entityManager.find(book2.class, "id1");
            System.out.println(book2);
        }catch(NullPointerException exception){
            throw new NullPointerException();
        }finally {
            entityManager.close();
        }

        entityManagerFactory.close();

    }
}

이렇게 동작하는 것을 볼 수 있다.

'백엔드 > JPA' 카테고리의 다른 글

JPA 10장 (영속 컨텍스트와 LifeCycle)  (0) 2023.03.22
JPA 8장 (List collection mapping)  (0) 2023.03.18
JPA 7장 (Set collection mapping)  (0) 2023.03.18
JPA 6장 (@Embeddable)  (0) 2023.03.17
JPA 5장 (Entity 식별자 생성 방식)  (0) 2023.03.16
728x90

일단 바로 만들어보고 시작하자.

 

intellij에서 JDK17로 Maven 프로젝트를 생성한다.

그리고 pom.xml을 다음과 같이 작성한다.

<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jpa-basic</groupId>
    <artifactId>JPA-01</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <hibernate.verstion>6.0.0.FINAL</hibernate.verstion>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.verstion}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-hikaricp</artifactId>
            <version>${hibernate.verstion}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>
    </dependencies>

</project>

우선 이렇게 작성해주고 src/main/resources/META-INF에 가서는 persistence.xml을 만든 후

<?xml version="1.0" encoding="utf-8" ?>

<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence
                https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
             version="3.0">

    <persistence-unit name="jpabegin" transaction-type="RESOURCE_LOCAL">
        <class>jpabasic.reserve.domain.User</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="jakarta.persistence.jdbc.url"
                      value="jdbc:mysql://localhost/jpabegin?characterEncoding=utf8"/>
            <property name="jakarta.persistence.jdbc.user" value="jpauser"/>
            <property name="jakarta.persistence.jdbc.password" value="12345678"/>

            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
            <property name="hibernate.hikari.poolName" value="pool"/>
            <property name="hibernate.hikari.maximumPoolSize" value="10"/>
            <property name="hibernate.hikari.minimumIdle" value="10"/>
            <property name="hibernate.hikari.connectionTimeout" value="1000"/>
        </properties>
    </persistence-unit>
</persistence>

이렇게 작성을 해준다.

MySQL을 사용하고 사용자 정보를 저렇게 사용하기 때문에 저렇게 만든 것이다.

 

이 상태로 바로 class를 작성하러 가보자.

JDBC를 공부했으니 어느정도는 이해가 될 수도 있다.

package jpabasic.reserve.domain;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "user")
public class User {
    @Id
    private String email;
    private String name;
    private String create_date;

    public User(String email, String name, String create_date) {
        this.email = email;
        this.name = name;
        this.create_date = create_date;
    }

    protected User() {
        
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCreate_date() {
        return create_date;
    }

    public void setCreate_date(String create_date) {
        this.create_date = create_date;
    }
}

클래스 위에 @Entity annotation으로 요소임을 알리고, @Table로 테이블을 지정하며, @Id로 식별자들을 지정해준다.

그리고 당연히 생성자와 Getter, Setter를 만들어준다.

 

import jakarta.persistence.*;
import jpabasic.reserve.domain.User;
import java.time.LocalDateTime;

public class UserSaveMain {
    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();

        try{
            transaction.begin();
            User user = new User("hello@gmail.com", "hello", LocalDateTime.now());
            entityManager.persist(user);
            transaction.commit();
        } catch(Exception exception){
            exception.printStackTrace();
            transaction.rollback();
        }finally {
            entityManager.close();
        }

        entityManager.close();
    }
}

 

우선 EntityManagerFactory를 생성한다.

이 때 xml에서 작성했던 jpabegin 이름을 이용한다.

 

persist를 사용하면 parameter로 넘긴 객체가 DB에 저장이 된다.

실행을 해보면 잘 들어가는 것을 볼 수 있다.

당연히 한 번 더 추가하면 pk가 겹치기 때문에 추가할 수 없다고 나온다.

 

이번엔 조회다.

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
import jpabasic.reserve.domain.User;

public class UserGetMain {
    public static void main(String[] args) {

        String find_id = "hello@gmail.com";

        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");

        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();

        try{
            transaction.begin();
            User user = entityManager.find(User.class, find_id);
            if (user == null) System.out.println("User 없음");
            else {
                System.out.printf("email : %s, name = %s, createDate = %s\n", user.getEmail(), user.getName(), user.getCreate_date());
            }
            transaction.commit();
        }catch (Exception exception){
            exception.printStackTrace();
            transaction.rollback();
        }finally {
            entityManager.close();
        }
        entityManager.close();
    }
}

entityManager에서 find로 찾아온다.

parameter는 해당 클래스와 PK이다.

실행을 해보면 정상적으로 조회되는 것을 볼 수 있다.

 

이번엔 수정이다.

import jakarta.persistence.*;
import jpabasic.reserve.domain.User;
import java.time.LocalDateTime;

public class UserUpdateMain {
    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();

        try{
            transaction.begin();
            User user = entityManager.find(User.class, "hello@gmail.com");
            if(user == null) System.out.println("User 없음");
            else{
                user.setName("hi");
            }
            transaction.commit();
        } catch(Exception exception){
            exception.printStackTrace();
            transaction.rollback();
        }finally {
            entityManager.close();
        }

        entityManager.close();
    }
}

수정은 조회를 한 후 해당 클래스의 데이터를 변경하고 commit을 수행하면 된다.

transaction 범위 내에서 수정해야 한다.

이렇게 정상적으로 수행되는 것을 볼 수 있다.

'백엔드 > JPA' 카테고리의 다른 글

JPA 6장 (@Embeddable)  (0) 2023.03.17
JPA 5장 (Entity 식별자 생성 방식)  (0) 2023.03.16
JPA 4장 (Entity에 대하여)  (0) 2023.03.16
JPA 3장 (간단한 CRUD 구현해보기)  (0) 2023.03.16
JPA 2장 (영속 컨텍스트)  (0) 2023.03.15
728x90

이제 JDBC를 이용해서 데이터를 다뤄보자.

 

  • 데이터 수정하기

우선 MySQL로 가보자.

NOTICE 테이블에서

ID와 REGDATE, HIT는 사용자가 입력하는 정보는 아니다.

REGDATE는 현재 시각을 넣고, HIT는 조회할 때마다 하나씩 증가하며 ID는 1부터 시작해서 하나씩 증가할 것이다.

그렇기 때문에 일단 ID에 자동 증가를 설정해두고, HIT에 디폴트를 0으로 설정해준다.

 

그 후에 테스트로

INSERT INTO NOTICE(
    TITLE,
    WRITER_ID,
    CONTENT,
    REGDATE,
    FILES
) VALUES (
	"SEUNGKYU",
    "SK",
    "AAA",
    NOW(),
    ""
);

해당 값을 넣은 후 데이터가 잘 들어가는 지 테스트 해본다.

이렇게 잘 들어간다.

auto_increment를 다시 1로 설정해주고 JDBC로 넘어간다.

 

Program1의 코드를 그대로 복사해서 Program2를 만들었다.

기본적인 내용들은 크게 변하지는 statement를 PreparedStatement, executeQuery를 executeUpdate로 변경해주어야 한다.

해당 코드들은 조회에서만 사용하는 코드들이다.

 

executeUpdate는 조회가 아니가 때문에 결과 집합을 반환하지 않고, 업데이트 된 데이터의 개수를 반환한다.

 

우선 sql 문장부터 만들어 본다면

import java.sql.*;

public class Program2 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/jdbc";
        String user = "root";
        String passwd = "12345678";

        String title = "seungkyubook";
        String writer_id = "sk";
        String content = "aaa";
        String files = "";

        String sql = "INSERT INTO NOTICE (TITLE, WRITER_ID, CONTENT, REGDATE, FILES) VALUES ()";



        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        Statement statement = connection.createStatement();

        int result = statement.executeUpdate(sql);
        
        
        statement.close();
        connection.close();
    }
}

 

저렇게 만든 후 VALUES 안에 우리가 넣고 싶어하느 데이터들을 +로 연결해주어야 했다.

이렇게 이어 붙이기 위해서는 문자열에는 '를 또 달아주기도 해야한다.

너무 귀찮고 실수도 많이 일어나는 부분이다.

 

다행이게도 JDBC에서 이 부분을 도와준다.

방법은

import java.sql.*;

public class Program2 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/jdbc";
        String user = "root";
        String passwd = "12345678";

        String title = "seungkyubook";
        String writer_id = "sk";
        String content = "aaa";
        String files = "";

        String sql = "INSERT INTO NOTICE (TITLE, WRITER_ID, CONTENT, REGDATE, FILES) VALUES (?, ?, ?, NOW(),?)";

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.close();
        connection.close();
    }
}

이렇게 문자열이 추가될 부분을 그냥 ?로 놔둔다.

이 부분들을 PreparedStatement가 해결해준다.

실행할 때 sql을 넘기는 것이 아니라 미리 준비해 둔다는 말이다.

 

        preparedStatement.setString(1, title);
        preparedStatement.setString(2, writer_id);
        preparedStatement.setString(3, content);
        preparedStatement.setString(4, files);

그러고 PreparedStatement의 set???(index, data)로 물음표를 채워준다.

여기서 조심할 점은 index가 0부터 시작하는 것이 아니라 1부터 시작한다는 점이다.

당연히 정수값을 넣으려면 setInt() 이런 느낌이다.

int result = preparedStatement.executeUpdate();

문장만 준비해두고 executeUpdate를 실행하면 업데이트가 된다.

이미 sql을 준비해두었기 때문에 sql을 넘기지 않는다.

import java.sql.*;

public class Program2 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/jdbc";
        String user = "root";
        String passwd = "12345678";

        String title = "seungkyubook";
        String writer_id = "sk";
        String content = "aaa";
        String files = "";

        String sql = "INSERT INTO NOTICE (TITLE, WRITER_ID, CONTENT, REGDATE, FILES) VALUES (?, ?, ?, NOW(),?)";

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setString(1, title);
        preparedStatement.setString(2, writer_id);
        preparedStatement.setString(3, content);
        preparedStatement.setString(4, files);

        int result = preparedStatement.executeUpdate();

        Statement statement = connection.createStatement();

        ResultSet resultSet = statement.executeQuery("SELECT * FROM NOTICE");

        while(resultSet.next()){
            int result_id = resultSet.getInt("ID");
            String result_title = resultSet.getString("TITLE");
            String result_writer_id = resultSet.getString("WRITER_ID");
            String result_content = resultSet.getString("CONTENT");
            Date result_regdate = resultSet.getDate("REGDATE");
            int result_hit = resultSet.getInt("HIT");
            String result_files = resultSet.getString("FILES");

            System.out.printf("id: %d, title: %s, write_id: %s, content: %s, regDate: %s, hit: %d, files: %s\n",
                    result_id, result_title, result_writer_id, result_content, result_regdate, result_hit, result_files);
        }

        preparedStatement.close();
        connection.close();
    }
}

최종적으로 작성한 코드이다.

데이터를 추가하고 바로 조회할 수 있도록 작성해보았다.

 

실행을 해보면

이렇게 잘 추가되는 것을 볼 수 있다.

 

  • 데이터 수정하기

수정은 그냥 PreparedStatement를 수정에 맞게 수정하면 된다.

import java.sql.*;

public class Program3 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/jdbc";
        String user = "root";
        String passwd = "12345678";

        int id = 6;
        String title = "seungkyu!!!";
        String writer_id = "sk";
        String content = "aaa";
        String files = "";

        String sql = "UPDATE NOTICE SET TITLE = ?, WRITER_ID = ?, CONTENT = ?, REGDATE = NOW(), FILES = ? WHERE ID = ?";

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setString(1, title);
        preparedStatement.setString(2, writer_id);
        preparedStatement.setString(3, content);
        preparedStatement.setString(4, files);
        preparedStatement.setInt(5, id);

        int result = preparedStatement.executeUpdate();

        Statement statement = connection.createStatement();

        ResultSet resultSet = statement.executeQuery("SELECT * FROM NOTICE");

        while(resultSet.next()){
            int result_id = resultSet.getInt("ID");
            String result_title = resultSet.getString("TITLE");
            String result_writer_id = resultSet.getString("WRITER_ID");
            String result_content = resultSet.getString("CONTENT");
            Date result_regdate = resultSet.getDate("REGDATE");
            int result_hit = resultSet.getInt("HIT");
            String result_files = resultSet.getString("FILES");

            System.out.printf("id: %d, title: %s, write_id: %s, content: %s, regDate: %s, hit: %d, files: %s\n",
                    result_id, result_title, result_writer_id, result_content, result_regdate, result_hit, result_files);
        }

        preparedStatement.close();
        connection.close();
    }
}

 

  • 데이터 삭제하기
import java.sql.*;

public class Program4 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/jdbc";
        String user = "root";
        String passwd = "12345678";

        int id = 6;

        String sql = "DELETE FROM NOTICE WHERE id = ?";

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setInt(1, id);


        preparedStatement.executeUpdate();

        Statement statement = connection.createStatement();

        ResultSet resultSet = statement.executeQuery("SELECT * FROM NOTICE");

        while(resultSet.next()){
            int result_id = resultSet.getInt("ID");
            String result_title = resultSet.getString("TITLE");
            String result_writer_id = resultSet.getString("WRITER_ID");
            String result_content = resultSet.getString("CONTENT");
            Date result_regdate = resultSet.getDate("REGDATE");
            int result_hit = resultSet.getInt("HIT");
            String result_files = resultSet.getString("FILES");

            System.out.printf("id: %d, title: %s, write_id: %s, content: %s, regDate: %s, hit: %d, files: %s\n",
                    result_id, result_title, result_writer_id, result_content, result_regdate, result_hit, result_files);
        }

        preparedStatement.close();
        connection.close();
    }
}

늘 그렇듯이 DELETE로 바꾸어주면 된다.

이렇게 모든 데이터가 지워진 것을 볼 수 있다.

 

  • CRUD 서비스 구현

배웠던 조회, 입력, 수정, 삭제를 이용하여 서비스를 만들어보자.

 

NOTICE를 대상으로 하기 때문에 패키지를 만들고 진행할 예정이다.

우선 데이터를 가져오고 저장할 NOTICE 클래스를 만들어보자.

package Service;

import java.util.Date;

public class Notice {
    private int id;
    private String title;
    private String writerId;
    private String content;
    private Date regDate;
    private int hit;
    private String files;

    public Notice(int id, String title, String writerId, String content, Date regDate, int hit, String files) {
        this.id = id;
        this.title = title;
        this.writerId = writerId;
        this.content = content;
        this.regDate = regDate;
        this.hit = hit;
        this.files = files;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getWriterId() {
        return writerId;
    }

    public void setWriterId(String writerId) {
        this.writerId = writerId;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getRegDate() {
        return regDate;
    }

    public void setRegDate(Date regDate) {
        this.regDate = regDate;
    }

    public int getHit() {
        return hit;
    }

    public void setHit(int hit) {
        this.hit = hit;
    }

    public String getFiles() {
        return files;
    }

    public void setFiles(String files) {
        this.files = files;
    }
}

당연히 Private로 멤버들을 선언해주고, Getter와 Setter를 만들어준다.

 

전에 만들었던 조회 부분을 살짝 수정해서 getList를 완성해주었다.

package Service;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class NoticeService {

    String driver = "com.mysql.cj.jdbc.Driver";
    String url = "jdbc:mysql://localhost:3306/jdbc";
    String user = "root";
    String passwd = "12345678";

    public List<Notice> getList() throws ClassNotFoundException, SQLException {

        List<Notice> list = new ArrayList<Notice>();

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        Statement statement = connection.createStatement();

        ResultSet resultSet = statement.executeQuery("SELECT * FROM NOTICE");

        while(resultSet.next()){
            int id = resultSet.getInt("ID");
            String title = resultSet.getString("TITLE");
            String writer_id = resultSet.getString("WRITER_ID");
            Date regDate = resultSet.getDate("REGDATE");
            String content = resultSet.getString("CONTENT");
            int hit = resultSet.getInt("HIT");
            String files = resultSet.getString("FILES");

            Notice notice = new Notice(id, title, writer_id, content, regDate, hit, files);

            list.add(notice);
        }
        resultSet.close();
        statement.close();
        connection.close();

        return list;
    }
}

다른 파일들도 복사한 후 코드를 약간 수정해서 메서드들을 작성했다.

package Service;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class NoticeService {

    String driver = "com.mysql.cj.jdbc.Driver";
    String url = "jdbc:mysql://localhost:3306/jdbc";
    String user = "root";
    String passwd = "12345678";

    public List<Notice> getList() throws ClassNotFoundException, SQLException {

        List<Notice> list = new ArrayList<>();

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        Statement statement = connection.createStatement();

        ResultSet resultSet = statement.executeQuery("SELECT * FROM NOTICE");

        while(resultSet.next()){
            int id = resultSet.getInt("ID");
            String title = resultSet.getString("TITLE");
            String writer_id = resultSet.getString("WRITER_ID");
            Date regDate = resultSet.getDate("REGDATE");
            String content = resultSet.getString("CONTENT");
            int hit = resultSet.getInt("HIT");
            String files = resultSet.getString("FILES");

            Notice notice = new Notice(id, title, writer_id, content, regDate, hit, files);

            list.add(notice);
        }
        resultSet.close();
        statement.close();
        connection.close();

        return list;
    }

    public int insert(Notice notice) throws ClassNotFoundException, SQLException {
        String sql = "INSERT INTO NOTICE (TITLE, WRITER_ID, CONTENT, REGDATE, FILES) VALUES (?, ?, ?, NOW(),?)";

        String title = notice.getTitle();
        String writerId = notice.getWriterId();
        String content = notice.getContent();
        String files = notice.getFiles();

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setString(1, title);
        preparedStatement.setString(2, writerId);
        preparedStatement.setString(3, content);
        preparedStatement.setString(4, files);

        int result = preparedStatement.executeUpdate();


        preparedStatement.close();
        connection.close();

        return result;
    }

    public int update(Notice notice) throws ClassNotFoundException, SQLException {
        String sql = "UPDATE NOTICE SET TITLE = ?, WRITER_ID = ?, CONTENT = ?, REGDATE = NOW(), FILES = ? WHERE ID = ?";

        int id = notice.getId();
        String title = notice.getTitle();
        String writerId = notice.getWriterId();
        String content = notice.getContent();
        String files = notice.getFiles();

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setString(1, title);
        preparedStatement.setString(2, writerId);
        preparedStatement.setString(3, content);
        preparedStatement.setString(4, files);
        preparedStatement.setInt(5, id);

        int result = preparedStatement.executeUpdate();

        preparedStatement.close();
        connection.close();

        return result;
    }

    public int delete(int id) throws ClassNotFoundException, SQLException {
        String sql = "DELETE FROM NOTICE WHERE id = ?";

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, passwd);
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, id);

        int result = preparedStatement.executeUpdate();

        Statement statement = connection.createStatement();

        preparedStatement.close();
        connection.close();

        return result;
    }
}

'백엔드 > JDBC' 카테고리의 다른 글

JDBC 1일차  (0) 2023.03.12
728x90
  • JDBC란 무엇인가?

JDBC(Java Database Connectivity)는 자바에서 DB에 접속할 수 있도록 하는 자바의 API이다. JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다.

자바와 DBMS 사이에서 통역을 해주는 통역사의 역할을 수행한다.

JDBC를 사용하면 개발자는 DBMS를 신경쓰지 않고 JDBC만을 사용해서 개발을 할 수 있게 된다.

JDBC를 사용하기 위해서는 해당 DBMS의 JDBC 드라이버를 사용해야 한다.

 

 

  • JDBC Driver

mac을 사용하고 있기 때문에

 

https://dev.mysql.com/downloads/connector/j/

 

MySQL :: Download Connector/J

MySQL Connector/J 8.0 is highly recommended for use with MySQL Server 8.0, 5.7 and 5.6. Please upgrade to MySQL Connector/J 8.0.

dev.mysql.com

해당 사이트에서

Platform Independent를 선택해서 다운 받아준다.

 

다운로드 받은 후 압축을 풀어둔다.

 

이제 실습을 하기 위한 Intellij를 열어주자.

새로운 프로젝트를 생성하고 Project Structure에서 다운로드 받고 압축을 해제했던 connector를 추가해준다.

OK를 누르면 이제 JDBC를 사용할 준비가 된 것이다.

 

  • JDBC 기본코드

우선은 MySQL에서 받은 드라이버를 로드해야 한다.

 

그렇기에 드라이버를 로드하는 것이 가장 첫번째 코드이다.

Class.forName("com.mysql.cj.jdbc.driver");

이 코드로 MySQL 드라이버를 로드하자.

 

이렇게 드라이버를 로드하면 메모리에 잡히게 된다.

 

이 드라이버를 연결해 연결 객체를 얻어야 한다.

Connection connection = DriverManager.getConnection(...);

현재 로컬에 있는 데이터베이스에 연결할 것이기 때문에

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/DB명", "user", "password");

이렇게 연결하도록 하자.

 

실행도구를 생성한다.

Statement statement = connection.createStatement();

 

쿼리를 실행하고 결과를 받아온다.

ResultSet resultSet = statement.executeQuery(sql);

 

현재 ResultSet은 파일의 시작(Before of File)을 가르키고 있으며, 이 ResultSet은 ResultSet.next를 이용하여 다음 정보들을 넘겨 받게 된다.

그러다가 파일의 마지막(End of File)을 가르키면 더 이상 데이터를 반환하지 않게 된다.

 

ResultSet.getXXX("열이름") 메서드를 사용하면 현재 ResultSet이 가르키고 있는 행의 열 이름에 해당하는 정보를 가져오게 된다.

 

바로 실습을 해보도록 하자.

참고로 이 부분에 대한 코드는 거의 고정이니 외워서 사용하도록 하자.

Class.forName("com.mysql.cj.jdbc.Driver");

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "12345678");

Statement statement = connection.createStatement();

ResultSet resultSet = statement.executeQuery("");
        
//
//
        
resultSet.close();
statement.close();
connection.close();

우선 DB에서 데이터를 가져오기 위해 데이터를 추가한다.

 

INSERT INTO NOTICE VALUES
(
    1,
    "hello",
    "sk",
    "aaa",
    SYSDATE(),
    0,
    ""
);

이렇게 NOTICE 테이블에 데이터를 대충 추가한 후

 

import java.sql.*;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        Class.forName("com.mysql.cj.jdbc.Driver");

        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "12345678");

        Statement statement = connection.createStatement();

        ResultSet resultSet = statement.executeQuery("SELECT * FROM NOTICE");



        if(resultSet.next()){
            String title = resultSet.getString("TITLE");
            System.out.println(title);
        }

        resultSet.close();
        statement.close();
        connection.close();
    }
}

ResultSet에서 다음 데이터를 가져온 후 title 열의 데이터를 출력해보면

 

이렇게 잘 출력이 되는 것을 볼 수 있다.

 

데이터를 하나 더 추가하여 while 문으로 모든 데이터를 출력한 모습이다.

당연히 MySQL과 마찬가지로 쿼리에 WHERE 문도 사용 할 수가 있다.

import java.sql.*;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        Class.forName("com.mysql.cj.jdbc.Driver");

        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "12345678");

        Statement statement = connection.createStatement();

        ResultSet resultSet = statement.executeQuery("SELECT * FROM NOTICE WHERE ID > 1");

        while(resultSet.next()){
            int id = resultSet.getInt("ID");
            String title = resultSet.getString("TITLE");
            String writer_id = resultSet.getString("WRITER_ID");
            Date regDate = resultSet.getDate("REGDATE");
            String content = resultSet.getString("CONTENT");
            int hit = resultSet.getInt("HIT");

            System.out.printf("id: %d, title: %s, write_id: %s, regDate: %s, content: %s, hit: %d\n", id, title, writer_id, regDate, content, hit);
        }

        resultSet.close();
        statement.close();
        connection.close();
    }
}

'백엔드 > JDBC' 카테고리의 다른 글

JDBC 2일차  (0) 2023.03.14

+ Recent posts