자바 기반 직렬 통신 도구 작성
하나:
우선,자바 직렬 통신 을 지원 하 는 jar 패 키 지 를 추가 로 다운로드 해 야 합 니 다.자바.com m 가 늙 었 고 64 비트 시스템 이 지원 되 지 않 기 때문에 Rxtx 라 는 jar 패 키 지 를 추천 합 니 다.
공식 다운로드 주소:http://fizzed.com/oss/rxtx-for-java(비고:FQ 가 있어 야 다운로드 할 수 있 습 니 다)
FQ 가 안 되 는 어린이 신발 은 여기 서 다운로드 할 수 있 습 니 다.
http://xiazai.jb51.net/201612/yuanma/javamfzrxtx(jb51.net).rar(32 위)
http://xiazai.jb51.net/201612/yuanma/javamfzrxtx(jb51.net).rar(64 위)
둘:
압축 해제 jar 패 키 지 를 다운로드 하고 Java Build Path 에서 도입:
포획 하 다.
주:실행 중 자바.lang.Unsatisfied LinkError 오류 가 발생 하면 rxtx 압축 해제 패키지 의 rxtx Parallel.dll,rxtx Serial.dll 두 파일 을 C:\Windows\\System 32 디 렉 터 리 에 복사 하면 이 오 류 를 해결 할 수 있 습 니 다.
3:
이 jar 패키지 의 사용 에 대해 저 는 SerialTool.java 클래스 를 썼 습 니 다.이 클래스 는 직렬 통신 에 관 한 간단 한 서 비 스 를 제공 합 니 다.코드 는 다음 과 같 습 니 다.
package serialPort;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import serialException.*;
/**
* , 、 , 、 ( )
* @author zhong
*
*/
public class SerialTool {
private static SerialTool serialTool = null;
static {
// ClassLoader SerialTool
if (serialTool == null) {
serialTool = new SerialTool();
}
}
// SerialTool , SerialTool
private SerialTool() {}
/**
* SerialTool
* @return serialTool
*/
public static SerialTool getSerialTool() {
if (serialTool == null) {
serialTool = new SerialTool();
}
return serialTool;
}
/**
*
* @return
*/
public static final ArrayList<String> findPort() {
//
Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
ArrayList<String> portNameList = new ArrayList<>();
// List List
while (portList.hasMoreElements()) {
String portName = portList.nextElement().getName();
portNameList.add(portName);
}
return portNameList;
}
/**
*
* @param portName
* @param baudrate
* @return
* @throws SerialPortParameterFailure
* @throws NotASerialPort
* @throws NoSuchPort
* @throws PortInUse
*/
public static final SerialPort openPort(String portName, int baudrate) throws SerialPortParameterFailure, NotASerialPort, NoSuchPort, PortInUse {
try {
//
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
// , timeout( )
CommPort commPort = portIdentifier.open(portName, 2000);
//
if (commPort instanceof SerialPort) {
SerialPort serialPort = (SerialPort) commPort;
try {
//
serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
} catch (UnsupportedCommOperationException e) {
throw new SerialPortParameterFailure();
}
//System.out.println("Open " + portName + " sucessfully !");
return serialPort;
}
else {
//
throw new NotASerialPort();
}
} catch (NoSuchPortException e1) {
throw new NoSuchPort();
} catch (PortInUseException e2) {
throw new PortInUse();
}
}
/**
*
* @param serialport
*/
public static void closePort(SerialPort serialPort) {
if (serialPort != null) {
serialPort.close();
serialPort = null;
}
}
/**
*
* @param serialPort
* @param order
* @throws SendDataToSerialPortFailure
* @throws SerialPortOutputStreamCloseFailure
*/
public static void sendToPort(SerialPort serialPort, byte[] order) throws SendDataToSerialPortFailure, SerialPortOutputStreamCloseFailure {
OutputStream out = null;
try {
out = serialPort.getOutputStream();
out.write(order);
out.flush();
} catch (IOException e) {
throw new SendDataToSerialPortFailure();
} finally {
try {
if (out != null) {
out.close();
out = null;
}
} catch (IOException e) {
throw new SerialPortOutputStreamCloseFailure();
}
}
}
/**
*
* @param serialPort SerialPort
* @return
* @throws ReadDataFromSerialPortFailure
* @throws SerialPortInputStreamCloseFailure
*/
public static byte[] readFromPort(SerialPort serialPort) throws ReadDataFromSerialPortFailure, SerialPortInputStreamCloseFailure {
InputStream in = null;
byte[] bytes = null;
try {
in = serialPort.getInputStream();
int bufflenth = in.available(); // buffer
while (bufflenth != 0) {
bytes = new byte[bufflenth]; // byte buffer
in.read(bytes);
bufflenth = in.available();
}
} catch (IOException e) {
throw new ReadDataFromSerialPortFailure();
} finally {
try {
if (in != null) {
in.close();
in = null;
}
} catch(IOException e) {
throw new SerialPortInputStreamCloseFailure();
}
}
return bytes;
}
/**
*
* @param port
* @param listener
* @throws TooManyListeners
*/
public static void addListener(SerialPort port, SerialPortEventListener listener) throws TooManyListeners {
try {
//
port.addEventListener(listener);
//
port.notifyOnDataAvailable(true);
//
port.notifyOnBreakInterrupt(true);
} catch (TooManyListenersException e) {
throw new TooManyListeners();
}
}
}
주:이 방법 에서 throw 의 Exception 은 모두 제 가 정의 한 Exception 입 니 다.이렇게 하 는 것 은 메 인 프로그램 에서 해당 하 는 처 리 를 편리 하 게 하기 위해 서 입 니 다.아래 에 Exception 하 나 를 붙 여 설명 하 겠 습 니 다.(내 모든 사용자 정의 Exception 을 serial Exception 가방 에 넣 는 것 을 주의 하 세 요)
package serialException;
public class SerialPortParameterFailure extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
public SerialPortParameterFailure() {}
@Override
public String toString() {
return " ! !";
}
}
사용자 정의 Exception 클래스 마다 toString()방법 을 다시 썼 습 니 다.주 프로그램 이 이 Exception 을 포착 한 후에 해당 하 는 오류 정 보 를 인쇄 할 수 있 습 니 다.그 중에서 serial Exception 패키지 에는 받 은 Exception 대상 의 오류 정 보 를 추출 하여 문자열 로 변환 하고 되 돌려 주 는 클래스 가 있 습 니 다.코드 는 다음 과 같 습 니 다.
package serialException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* Exception ;
* @author zhong
*
*/
public class ExceptionWriter {
/**
* Exception
* @param e Exception
* @return
*/
public static String getErrorInfoFromException(Exception e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
e.printStackTrace(pw);
return "\r
" + sw.toString() + "\r
";
} catch (Exception e2) {
return " ! , !";
} finally {
try {
if (pw != null) {
pw.close();
}
if (sw != null) {
sw.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
4:주 프로그램 클래스 의 사용,Client.java 에는 프로그램의 입구 주소(main 방법)가 포함 되 어 있 습 니 다.환영 인 터 페 이 스 를 표시 하고 DataView.java 클래스 를 호출 하여 실제 직렬 데 이 터 를 표시 하 는 역할 을 합 니 다.
Client.java 코드 는 다음 과 같 습 니 다.
package serialPort;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Label;
import java.awt.Panel;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JOptionPane;
import serialException.ExceptionWriter;
/**
*
* @author zhong
*
*/
public class Client extends Frame{
/**
*
*/
private static final long serialVersionUID = 1L;
/**
*
*/
public static final int WIDTH = 800;
/**
*
*/
public static final int HEIGHT = 620;
/**
* ( )
*/
public static final int LOC_X = 200;
/**
* ( )
*/
public static final int LOC_Y = 70;
Color color = Color.WHITE;
Image offScreen = null; //
// window icon( Windows icon , = =)
Toolkit toolKit = getToolkit();
Image icon = toolKit.getImage(Client.class.getResource("computer.png"));
//
DataView dataview = new DataView(this); // ( )
/**
*
* @param args //
*/
public static void main(String[] args) {
new Client().launchFrame();
}
/**
*
*/
public void launchFrame() {
this.setBounds(LOC_X, LOC_Y, WIDTH, HEIGHT); //
this.setTitle("CDIO "); //
this.setIconImage(icon);
this.setBackground(Color.white); //
this.addWindowListener(new WindowAdapter() {
//
public void windowClosing(WindowEvent arg0) {
//
System.exit(0); //
}
});
this.addKeyListener(new KeyMonitor()); //
this.setResizable(false); //
this.setVisible(true); //
new Thread(new RepaintThread()).start(); //
}
/**
*
*/
public void paint(Graphics g) {
Color c = g.getColor();
g.setFont(new Font(" ", Font.BOLD, 40));
g.setColor(Color.black);
g.drawString(" ", 45, 190);
g.setFont(new Font(" ", Font.ITALIC, 26));
g.setColor(Color.BLACK);
g.drawString("Version:1.0 Powered By:ZhongLei", 280, 260);
g.setFont(new Font(" ", Font.BOLD, 30));
g.setColor(color);
g.drawString("―――― Enter ――――", 100, 480);
// "―――― Enter ――――"
if (color == Color.WHITE) color = Color.black;
else if (color == color.BLACK) color = Color.white;
}
/**
*
*/
public void update(Graphics g) {
if (offScreen == null) offScreen = this.createImage(WIDTH, HEIGHT);
Graphics gOffScreen = offScreen.getGraphics();
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.white);
gOffScreen.fillRect(0, 0, WIDTH, HEIGHT); //
this.paint(gOffScreen); //
gOffScreen.setColor(c);
g.drawImage(offScreen, 0, 0, null); // “ ”
}
/*
*
*/
private class KeyMonitor extends KeyAdapter {
public void keyReleased(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ENTER) { // enter
setVisible(false); //
dataview.setVisible(true); //
dataview.dataFrame(); //
}
}
}
/*
* ( 250 )
*/
private class RepaintThread implements Runnable {
public void run() {
while(true) {
repaint();
try {
Thread.sleep(250);
} catch (InterruptedException e) {
// Dialog
String err = ExceptionWriter.getErrorInfoFromException(e);
JOptionPane.showMessageDialog(null, err, " ", JOptionPane.INFORMATION_MESSAGE);
System.exit(0);
}
}
}
}
}
캡 처 실행:주:실제 운행 과정 에서 맨 아래 에 있 는"Enter 키 를 누 르 면 메 인 화면 에 들 어 갑 니 다"는 반 짝 이 는 효과 가 있 습 니 다(일정 시간 마다 화면 을 다시 그 려 서 이 말 을 흰색 과 검은색 으로 반복 적 으로 교체 하여 실현 합 니 다).더 블 버퍼 방식 은 다시 그 릴 때 화면 이 반 짝 이 는 문 제 를 해결 하 는 데 유리 하 다.
DataView.java 코드 는 다음 과 같 습 니 다.(이 종 류 는 직렬 데 이 터 를 실시 간 으로 표시 하 는 데 사 용 됩 니 다)
간단 한 설명:
하드웨어 장 치 는 일정 시간 마다 직렬 포트 를 통 해 컴퓨터 에 데 이 터 를 보 냅 니 다.이 직렬 도 구 는 하드웨어 장치 에 성공 적 으로 연결 되 고 감청 을 추가 한 후에 데 이 터 를 받 을 때마다 데 이 터 를 분석 하고 인터페이스 를 업데이트 합 니 다.
사용 할 때 저 와 다른 수요 가 있 을 수 있 습 니 다.이 종 류 는 참고 만 하 십시오.실제 사용 할 때 데이터 디 스 플레이 인터페이스 와 데이터 분석 방식 을 다시 만들어 야 할 수도 있 습 니 다.
package serialPort;
import java.awt.Button;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Label;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;
import java.util.TooManyListenersException;
import javax.swing.JOptionPane;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import serialException.*;
/**
*
* @author Zhong
*
*/
public class DataView extends Frame {
/**
*
*/
private static final long serialVersionUID = 1L;
Client client = null;
private List<String> commList = null; //
private SerialPort serialPort = null; //
private Font font = new Font(" ", Font.BOLD, 25);
private Label tem = new Label(" ", Label.CENTER); //
private Label hum = new Label(" ", Label.CENTER); //
private Label pa = new Label(" ", Label.CENTER); //
private Label rain = new Label(" ", Label.CENTER); //
private Label win_sp = new Label(" ", Label.CENTER); //
private Label win_dir = new Label(" ", Label.CENTER); //
private Choice commChoice = new Choice(); // ( )
private Choice bpsChoice = new Choice(); //
private Button openSerialButton = new Button(" ");
Image offScreen = null; //
// window icon
Toolkit toolKit = getToolkit();
Image icon = toolKit.getImage(DataView.class.getResource("computer.png"));
/**
*
* @param client
*/
public DataView(Client client) {
this.client = client;
commList = SerialTool.findPort(); //
}
/**
* ;
* Label、 、 ;
*/
public void dataFrame() {
this.setBounds(client.LOC_X, client.LOC_Y, client.WIDTH, client.HEIGHT);
this.setTitle("CDIO ");
this.setIconImage(icon);
this.setBackground(Color.white);
this.setLayout(null);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent arg0) {
if (serialPort != null) {
//
SerialTool.closePort(serialPort);
}
System.exit(0);
}
});
tem.setBounds(140, 103, 225, 50);
tem.setBackground(Color.black);
tem.setFont(font);
tem.setForeground(Color.white);
add(tem);
hum.setBounds(520, 103, 225, 50);
hum.setBackground(Color.black);
hum.setFont(font);
hum.setForeground(Color.white);
add(hum);
pa.setBounds(140, 193, 225, 50);
pa.setBackground(Color.black);
pa.setFont(font);
pa.setForeground(Color.white);
add(pa);
rain.setBounds(520, 193, 225, 50);
rain.setBackground(Color.black);
rain.setFont(font);
rain.setForeground(Color.white);
add(rain);
win_sp.setBounds(140, 283, 225, 50);
win_sp.setBackground(Color.black);
win_sp.setFont(font);
win_sp.setForeground(Color.white);
add(win_sp);
win_dir.setBounds(520, 283, 225, 50);
win_dir.setBackground(Color.black);
win_dir.setFont(font);
win_dir.setForeground(Color.white);
add(win_dir);
//
commChoice.setBounds(160, 397, 200, 200);
// ,
if (commList == null || commList.size()<1) {
JOptionPane.showMessageDialog(null, " !", " ", JOptionPane.INFORMATION_MESSAGE);
}
else {
for (String s : commList) {
commChoice.add(s);
}
}
add(commChoice);
//
bpsChoice.setBounds(526, 396, 200, 200);
bpsChoice.add("1200");
bpsChoice.add("2400");
bpsChoice.add("4800");
bpsChoice.add("9600");
bpsChoice.add("14400");
bpsChoice.add("19200");
bpsChoice.add("115200");
add(bpsChoice);
//
openSerialButton.setBounds(250, 490, 300, 50);
openSerialButton.setBackground(Color.lightGray);
openSerialButton.setFont(new Font(" ", Font.BOLD, 20));
openSerialButton.setForeground(Color.darkGray);
add(openSerialButton);
//
openSerialButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//
String commName = commChoice.getSelectedItem();
//
String bpsStr = bpsChoice.getSelectedItem();
//
if (commName == null || commName.equals("")) {
JOptionPane.showMessageDialog(null, " !", " ", JOptionPane.INFORMATION_MESSAGE);
}
else {
//
if (bpsStr == null || bpsStr.equals("")) {
JOptionPane.showMessageDialog(null, " !", " ", JOptionPane.INFORMATION_MESSAGE);
}
else {
// 、
int bps = Integer.parseInt(bpsStr);
try {
//
serialPort = SerialTool.openPort(commName, bps);
//
SerialTool.addListener(serialPort, new SerialListener());
//
JOptionPane.showMessageDialog(null, " , !", " ", JOptionPane.INFORMATION_MESSAGE);
} catch (SerialPortParameterFailure | NotASerialPort | NoSuchPort | PortInUse | TooManyListeners e1) {
// Dialog
JOptionPane.showMessageDialog(null, e1, " ", JOptionPane.INFORMATION_MESSAGE);
}
}
}
}
});
this.setResizable(false);
new Thread(new RepaintThread()).start(); //
}
/**
*
*/
public void paint(Graphics g) {
Color c = g.getColor();
g.setColor(Color.black);
g.setFont(new Font(" ", Font.BOLD, 25));
g.drawString(" : ", 45, 130);
g.setColor(Color.black);
g.setFont(new Font(" ", Font.BOLD, 25));
g.drawString(" : ", 425, 130);
g.setColor(Color.black);
g.setFont(new Font(" ", Font.BOLD, 25));
g.drawString(" : ", 45, 220);
g.setColor(Color.black);
g.setFont(new Font(" ", Font.BOLD, 25));
g.drawString(" : ", 425, 220);
g.setColor(Color.black);
g.setFont(new Font(" ", Font.BOLD, 25));
g.drawString(" : ", 45, 310);
g.setColor(Color.black);
g.setFont(new Font(" ", Font.BOLD, 25));
g.drawString(" : ", 425, 310);
g.setColor(Color.gray);
g.setFont(new Font(" ", Font.BOLD, 20));
g.drawString(" : ", 45, 410);
g.setColor(Color.gray);
g.setFont(new Font(" ", Font.BOLD, 20));
g.drawString(" : ", 425, 410);
}
/**
*
*/
public void update(Graphics g) {
if (offScreen == null) offScreen = this.createImage(Client.WIDTH, Client.HEIGHT);
Graphics gOffScreen = offScreen.getGraphics();
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.white);
gOffScreen.fillRect(0, 0, Client.WIDTH, Client.HEIGHT); //
this.paint(gOffScreen); //
gOffScreen.setColor(c);
g.drawImage(offScreen, 0, 0, null); // “ ”
}
/*
* ( 30 )
*/
private class RepaintThread implements Runnable {
public void run() {
while(true) {
//
repaint();
//
commList = SerialTool.findPort();
if (commList != null && commList.size()>0) {
//
for (String s : commList) {
// , ( commList commChoice , )
boolean commExist = false;
for (int i=0; i<commChoice.getItemCount(); i++) {
if (s.equals(commChoice.getItem(i))) {
//
commExist = true;
break;
}
}
if (commExist) {
// ,
continue;
}
else {
//
commChoice.add(s);
}
}
//
for (int i=0; i<commChoice.getItemCount(); i++) {
// , ( commChoice commList , )
boolean commNotExist = true;
for (String s : commList) {
if (s.equals(commChoice.getItem(i))) {
commNotExist = false;
break;
}
}
if (commNotExist) {
//System.out.println("remove" + commChoice.getItem(i));
commChoice.remove(i);
}
else {
continue;
}
}
}
else {
// commList ,
commChoice.removeAll();
}
try {
Thread.sleep(30);
} catch (InterruptedException e) {
String err = ExceptionWriter.getErrorInfoFromException(e);
JOptionPane.showMessageDialog(null, err, " ", JOptionPane.INFORMATION_MESSAGE);
System.exit(0);
}
}
}
}
/**
*
* @author zhong
*
*/
private class SerialListener implements SerialPortEventListener {
/**
*
*/
public void serialEvent(SerialPortEvent serialPortEvent) {
switch (serialPortEvent.getEventType()) {
case SerialPortEvent.BI: // 10
JOptionPane.showMessageDialog(null, " ", " ", JOptionPane.INFORMATION_MESSAGE);
break;
case SerialPortEvent.OE: // 7 ( )
case SerialPortEvent.FE: // 9
case SerialPortEvent.PE: // 8
case SerialPortEvent.CD: // 6
case SerialPortEvent.CTS: // 3
case SerialPortEvent.DSR: // 4
case SerialPortEvent.RI: // 5
case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 2
break;
case SerialPortEvent.DATA_AVAILABLE: // 1
//System.out.println("found data");
byte[] data = null;
try {
if (serialPort == null) {
JOptionPane.showMessageDialog(null, " ! !", " ", JOptionPane.INFORMATION_MESSAGE);
}
else {
data = SerialTool.readFromPort(serialPort); // ,
//System.out.println(new String(data));
// ,
if (data == null || data.length < 1) { //
JOptionPane.showMessageDialog(null, " ! !", " ", JOptionPane.INFORMATION_MESSAGE);
System.exit(0);
}
else {
String dataOriginal = new String(data); //
String dataValid = ""; // ( * )
String[] elements = null; //
//
if (dataOriginal.charAt(0) == '*') { // * ,
dataValid = dataOriginal.substring(1);
elements = dataValid.split(" ");
if (elements == null || elements.length < 1) { //
JOptionPane.showMessageDialog(null, " , !", " ", JOptionPane.INFORMATION_MESSAGE);
System.exit(0);
}
else {
try {
// Label
/*for (int i=0; i<elements.length; i++) {
System.out.println(elements[i]);
}*/
//System.out.println("win_dir: " + elements[5]);
tem.setText(elements[0] + " ℃");
hum.setText(elements[1] + " %");
pa.setText(elements[2] + " hPa");
rain.setText(elements[3] + " mm");
win_sp.setText(elements[4] + " m/s");
win_dir.setText(elements[5] + " °");
} catch (ArrayIndexOutOfBoundsException e) {
JOptionPane.showMessageDialog(null, " , ! !", " ", JOptionPane.INFORMATION_MESSAGE);
System.exit(0);
}
}
}
}
}
} catch (ReadDataFromSerialPortFailure | SerialPortInputStreamCloseFailure e) {
JOptionPane.showMessageDialog(null, e, " ", JOptionPane.INFORMATION_MESSAGE);
System.exit(0); //
}
break;
}
}
}
}
캡 처 실행:전체 프로젝트 원본 패키지 다운로드:http://xiazai.jb51.net/201612/yuanma/javaserialMonitor(jb51.net).rar
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
JPA + QueryDSL 계층형 댓글, 대댓글 구현(2)이번엔 전편에 이어서 계층형 댓글, 대댓글을 다시 리팩토링해볼 예정이다. 이전 게시글에서는 계층형 댓글, 대댓글을 구현은 되었지만 N+1 문제가 있었다. 이번에는 그 N+1 문제를 해결해 볼 것이다. 위의 로직은 이...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.