일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 메이븐
- 프로그레시브웹앱
- springaop
- 생활코딩
- progressive web app
- 자바스크립트
- SpringMVC
- javascript
- js
- mybatis
- JavaScript 내장객체
- sqldeveloper
- 서브쿼리
- PWA
- javaprogramming
- CSS
- HTML
- sql
- tdd
- 자바프로그래밍
- 오라클
- TIL
- Oracle
- 프레임워크
- maven
- web
- 스프링
- TodayILearned
- 국비지원
- framework
- Today
- Total
1cm
자바 프로그래밍_Day_95_Framework:MyBatis 본문
2021. 12. 31
2021년의 마지막 날 휴가신청을 해서 주말에 수업을 들었다.
확실히 인강으로 들으니 멈출 수 있는 부분은 멈춰서 이해될 때까지 공부하고 넘어갈 수 있어서 좋은 것 같기도 하면서 그만큼 시간이 소요되는 게 조금 타이트하다는 느낌도 들었다.
뭐 아무튼 2022년 새해 첫 날을 공부로 시작해서 뿌듯했다.
수업
기존 JSP에서의 DAO에서는 connection 가져옴 -> 어떤 쿼리문을 수행시키느냐에 따라 공통적으로 PreparedStatement를 붙인다. -> 어떤 쿼리문이냐에 따라서 resultSet을 가져오는 것이었다면, MyBatis에서는 내부적으로 PreparedStatement 처리를 해주고, 리턴 타입을 지정하는 경우 자동으로 객체 생성 및 ResultSet 처리까지 해준다.
고로 MyBatis에서는 SQL쿼리문들을 따로 담는 파일들이 있고, 거기서 쿼리문을 불러서 쓰기 때문에 코드의 길이도 줄어들고, 쿼리문 오타의 염려도 줄어든다.
> 구글링으로 간단하게 찾아본 JDBC와 MyBatis의 차이점
JDBC | MyBatis |
- 직접적인 Connection을 닫고, 마지막에 close() 필수 - PreparedStatement 직접 생성 및 처리 - PreparedStatement의 setXXX() 등에 대한 모든 작업을 개발자가 처리해줘야함 - SELECT의 경우 직접 ResultSet 처리 |
- 자동으로 Connection을 닫고, 처리해준다. - MyBatis 내부적으로 PreparedStatement 처리가 가능하다. - #{prop}와 같이 속성을 지정하면 내부적으로 자동 처리된다. - 리턴 타입을 지정하는 경우 자동으로 객체 생성 및 ResultSet 처리가 가능하다. |
파라미터는 최대 두 개를 가져올 수 있음
1. 쿼리문이 존재하는 쿼리의 경로 지정 (네임스페이스)
2. 쿼리문을 실행할 때 특정 파라미터로 넘겨주는 것
> mybatis-config.xml 파일 내의 mappers 부분인데, mappers는 mapper파일들을 등록하는 영역인데 여러 개 등록할 수 있고, mappers에 등록된 mapper 파일에서만 SQL 쿼리문을 찾을 수 있다. (해당 mapper 파일에 해당하는 네임스페이스와 쿼리문 아이디로 불러옴)
> MemberServiceTest.java
- 멤버로 리스트 형태로 받아온다.
오류 처리 -> <Member>에 대한 오류는 class 생성으로 처리해주는데
src/main/java로 소스폴더 변경 후
com.kh.mybatis.member.model.vo 아래에 클래스가 생성될 수 있도록 위처럼 작성 후 finish 해준다.
그러면 위 경로에 Member.java 파일이 생성된 것을 확인할 수 있다.
vo와 lombok내용들은 이전에 mvc2에서 작업했던 거 복붙해줬다.
-> 이렇게 하면 <Member>에 대한 오류가 사라진다.
에러 없애기 후 jUnit으로 테스트를 진행해서 성공적으로 테스트 완료 되는 것을 확인할 수 있다.
> MemberService.java 파일의 import 구문에 static, getSession 추가 후
SqlSessionTemplate.getSession(); -> getSession();으로 변경해준다.
null을 리턴해주는 것을 members를 리턴할 수 있도록 코드 수정 후 테스트 진행
그 다음 mapper.xml에서 모든 멤버를 가지고 올 수 있는 쿼리문을 추가시켜준다.
별칭 부여로 컬럼명과 변수명을 mapping 시켜주었다. 위의 방법은 1번 방법이다.
2번 방법은 resultMap을 이용해서 명시적으로 컬럼명과 변수명을 mapping 시켜주는 방법
<resultMap type="xxx" id="xxx">
<id property="a" column="abc" />
<result property="userId" column="USER_ID" />
<result property="userPwd" column="USER_PWD" />
</resultMap>
이런식으로 resultMap 예시가 되어있다고 보면
id : 조회된 결과의 기본키
column : 실제로 조회된 컬럼명
property : mapping 되어야하는 객체의 필드명
- pk가 아닌 나머지 컬럼들은 resultMap 태그를 통해 mapping 해주면 되고, type은 클래스 POJO객체를 사용해야 한다.
- POJO란?
- Plain Old Java Object, 간단히 POJO는 말 그대로 해석을 하면 오래된 방식의 간단한 자바 오브젝트라는 말로서 Java EE 등의 중량 프레임워크들을 사용하게 되면서 해당 프레임워크에 종속된 "무거운" 객체를 만들게 된 것에 반발해서 사용되게 된 용어
resultMap의 type 속성은 풀 패키지 명을 적어주게 되면 비효율적이니 <typeAliases>로 별칭을 부여해주자.
driver.properties -> 프로젝트 하면서 설정파일, 필요한 리소스들은 resources 폴더 내에 저장한다.
resources는 항상 classpath 기준으로 찾는다.
maven repository 사이트에서 JUnit Jupiter Params 코드 복사해서 pom.xml에 추가해줬다.
@ParameterizedTest : 하나의 테스트 메소드로 여러 개의 파라미터에 대해 테스트 가능
@ValueSource(strings = {"mrhong", "admin2"}) : 개수만큼 테스트 진행
save
if문으로 mamber 객체의 pk no값이 0보다 크면 update, 작거나 같으면 insert
pk값이 없다라는 것은 데이터베이스에 저장이 되어있지 않다는 것
no값 세팅 없이 넘기면 멤버 데이터를 insert해주게 된다. -> Dao 객체에게 위임함
- insert
- update
예외처리는 내부에서 알아서 처리해준다. -> 비즈니스 로직에 더 집중 가능
오류나는 부분은 클릭해서 처리
세팅 해주고 set id/password/name 이외의 값들은 모두 NULL 로 입력이 된다.(NOT NULL 제약조건인거 빼고 채워짐)
useGeneratedKey : insert됨과 동시에 생성된 키를 가져오게 하는 속성
keyColumn : 데이터베이스에 insert될 때 key에 해당하는 컬럼명 (INSERT INTO MEMBER(NO, …, …. ))
keyProperty : 멤버의 no값 (member.java)
실습 코드
> 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 : maven 프로젝트 구성을 위해 사용되는 메이븐 버전 정보
-->
<modelVersion>4.0.0</modelVersion>
<!-- 프로젝트 정보 설정 -->
<!-- groupId : 조직의 고유한 Id를 정의한다. -->
<groupId>com.kh</groupId>
<artifactId>mybatis</artifactId>
<!-- 프로젝트의 버전 : 배포시 Release라고 지정되어 배포된다. -->
<version>0.0.1-SNAPSHOT</version>
<!-- 프로젝트 이름 -->
<name>mybatis</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<!-- 라이브러리 속성 설정 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<!-- 라이브러리 의존성 설정 -->
<dependencies>
<!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc6 -->
<!-- DB 관련 라이브러리 -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!-- lombok 라이브러리 -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<!-- scope : 라이브러리가 실행될 시점 설정 / provided : 컴파일할 때만 사용되며, 배포시에는 사용되지 않음 -->
<scope>provided</scope>
</dependency>
<!-- Test 관련 라이브러리 -->
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- 테스트를 더 효율적으로 검증할 수 있는 관련 라이브러러 -->
<!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.21.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 프로젝트 빌드에 대한 설정(프로젝트 빌드에 사용될 여러가지 정보들) -->
<build>
<pluginManagement> <!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
> mybatis.config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Mapper 설정 파일임을 선언 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 속성 : 외부에서 mapper에 접근할 수 있는 이름(별칭) -->
<mapper namespace="memberMapper">
<!-- 중복되는 쿼리 내용을 재사용하기 위한 SQL 조각으로, 사용하려는 태그보다 항상 위에 있어야한다. -->
<sql id="selectMemberSql">
SELECT NO,
ID,
PASSWORD,
ROLE,
NAME,
PHONE,
EMAIL,
ADDRESS,
HOBBY,
STATUS,
ENROLL_DATE,
MODIFY_DATE
FROM MEMBER
</sql>
<!--
resultMap은 DB의 조회 결과와 자바 객체(클래스)를 명시적으로 매핑해줄 때 사용한다. (JDBC 코드를 줄여주는 역할을 한다.)
- type 속성 : ResultSet의 결과를 담을 자바 객체의 타입(클래스명)을 기술한다. 풀 패키지 명으로 적어주거나 Alias 사용
- id 속성 : resultMap의 고유 아이디로 SELECT 문에서 resultMap 속성에 사용하게 된다.
-->
<!-- <resultMap type="com.kh.mybatis.member.model.vo.Member" id="memberResultMap"> -->
<!-- 별칭 부여 후 짧아진 코드 확인 가능 -->
<resultMap type="Member" id="memberResultMap">
<!-- id : 기본 키 / property : 지정 필드 명 / column : 조회된 컬럼명 -->
<!-- property : 자바 객체의 필드명을 기술, column : DB에서 select 구문에 지정된 컬럼명을 기술 -->
<!-- 테이블에서 기본 키를 의미 -->
<id property="no" column="NO"/>
<!-- 테이블에서 일반 컬럼들을 의미 -->
<result property="id" column="ID"/>
<result property="password" column="PASSWORD"/>
<result property="role" column="ROLE"/>
<result property="name" column="NAME"/>
<result property="phone" column="PHONE"/>
<result property="email" column="EMAIL"/>
<result property="address" column="ADDRESS"/>
<result property="hobby" column="HOBBY"/>
<result property="status" column="STATUS"/>
<result property="enrollDate" column="ENROLL_DATE"/>
<result property="modifyDate" column="MODIFY_DATE"/>
</resultMap>
<!--
SELECT 구문
id 속성 : 쿼리문의 고유 아이디
resultType 속성 : 쿼리 실행 후 조회 된 결과값의 자료형
-->
<select id="selectCount" resultType="_int">
SELECT COUNT(*) FROM MEMBER
</select>
<!--
쿼리문의 수행 결과가 담긴 ResultSet은 resultType의 객체에 자동으로 mapping되어 리턴된다.
단, 조회 결과의 컬럼명과 자바 클래스에서 필드명이 동일해야 자동으로 mapping이 된다.
(대소문자는 구분하지 않음 / * enrollDate 와 enroll_date는 필드명이 달라서 조회되지 않음)
해결방법
1) 쿼리문에서 "as 별칭"으로 조회되는 컬럼명을 변경한다.
-> 여러 개의 쿼리문일 경우 번거로워 짐. -> 유지보수 문제로 비추천
2) resultMap을 이용해서 명시적으로 컬럼명과 변수명을 mapping 시켜준다. (추천)
resultMap : 조회된 결과 하나하나의 행을 명시적으로 어떤 필드에 매핑해줄지 지정해주는 것, 보통 상단에 작성해준다.
<select id="selectAll" resultType="com.kh.mybatis.member.model.vo.Member"> -> 방법 1
-->
<select id="selectAll" resultType="memberResultMap">
<!--
방법 1)
SELECT NO,
ID,
PASSWORD,
ROLE,
NAME,
PHONE,
EMAIL,
ADDRESS,
HOBBY,
STATUS,
ENROLL_DATE AS enrollDate,
MODIFY_DATE AS modifyDate
FROM MEMBER
WHERE STATUS = 'Y'
-->
<!--
방법 2)
SELECT NO,
ID,
PASSWORD,
ROLE,
NAME,
PHONE,
EMAIL,
ADDRESS,
HOBBY,
STATUS,
ENROLL_DATE,
MODIFY_DATE
FROM MEMBER
WHERE STATUS = 'Y'
-->
<include refid="selectMemberSql" />
WHERE STATUS = 'Y'
</select>
<!-- 한 행을 resultMap을 참조해서 resultMap에 있는대로 Member 객체로 mapping 후 결과 리턴 -->
<!-- parameterType을 map으로 해도 되는 이유? : 변수명을 키값으로 가져오면 Map형태로 바꿔주기 때문 -->
<!--
외부에서 매개변수로 받는 값이 있으면 해당하는 매개변수의 타입을 지정해야 한다. (parameterType="string")
외부에서 매개변수로 받는 값은 #{변수명}으로 설정해서 값을 가져온다.
-->
<!-- <select id="selectMemberById" parameterType="string" resultMap="memberResultMap"> -->
<select id="selectMemberById" parameterType="map" resultMap="memberResultMap">
<include refid="selectMemberSql" />
WHERE ID=#{id} AND STATUS = 'Y'
</select>
<!--
데이터를 Insert 후 PK 값을 받아오기 위해서 useGeneratedKeys, keyColumn, keyProperty 속성을 추가한다.
ex. insert 후 다른 테이블에도 INSERT 해줘야 하는 경우 pk 값이 fk로 들어가야 하기 때문에 재사용이 가능해진다.
- useGeneratedKeys : insert 후에 PK 값을 받아올 수 있도록 허용하는 속성
- keyColumn : 실제 PK에 해당하는 컬럼 지정하는 속성
- keyProperty : PK값이 담길 파라미터 객체의 필드명을 지정하는 속성
-->
<insert id="insertMember" parameterType="Member"
useGeneratedKeys="true" keyColumn="NO" keyProperty="no">
INSERT INTO MEMBER (
NO,
ID,
PASSWORD,
ROLE,
NAME,
PHONE,
EMAIL,
ADDRESS,
HOBBY,
STATUS,
ENROLL_DATE,
MODIFY_DATE
) VALUES (
SEQ_UNO.NEXTVAL,
#{id},
#{password},
DEFAULT,
#{name},
#{phone},
#{email},
#{address},
#{hobby},
DEFAULT,
DEFAULT,
DEFAULT
)
</insert>
</mapper>
> test/java/com/kh/mybatis/member/model/service/MemberServiceTest.java
package com.kh.mybatis.member.model.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import com.kh.mybatis.member.model.vo.Member;
@DisplayName("Member 테스트")
class MemberServiceTest {
private MemberService service;
@BeforeEach
public void setUp() {
service = new MemberService();
}
@Test
@Disabled
public void nothing() {
}
@Test
@Disabled
public void create() {
assertThat(service).isNotNull();
}
@Test
@DisplayName("회원 수 조회 테스트")
public void getMemberCountTest() {
int count = service.getMemberCount();
// assertThat(count).isGreaterThanOrEqualTo(2);
assertThat(count).isPositive().isGreaterThanOrEqualTo(2);
}
// 모든 멤버 조회하는 테스트 메소드
@Test
@DisplayName("모든 회원 조회 테스트")
public void findAllTest() {
// 결과로 받아올 값은 List 형태로 가져온다.
List<Member> members = null;
members = service.findAll();
// members는 null이 아니다라는 검증의 테스트
// assertThat(members).isNotNull()
assertThat(members).isNotNull()
.isNotEmpty()
.extracting("id")
.isNotNull()
.contains("mrhong", "admin2");
}
// JUnit jupiter params에서 제공하는 어노테이션
@ParameterizedTest
@ValueSource(strings = {"mrhong", "admin2"})
@DisplayName("회원 조회 테스트 (ID로 검색)")
public void findMemberByIdTest(String userId) {
// 회원 하나 조회
Member member = service.findMemberById(userId);
assertThat(member).isNotNull()
.extracting("id")
.isEqualTo(userId);
}
@Test
@DisplayName("회원 등록 테스트")
public void saveTest() {
int result = 0;
Member member = new Member();
member.setId("test1");
member.setPassword("12345");
member.setName("홍길동");
System.out.println(member.getNo());
result = service.save(member);
System.out.println(member.getNo());
assertThat(result).isGreaterThan(0);
// 다시 찾지 않고도 insert 후 NO값 받아오기
assertThat(member.getNo()).isGreaterThan(0);
}
}
> resources/driver.properties
db.driver=oracle.jdbc.driver.OracleDriver
db.url=jdbc:oracle:thin:@localhost:1521:xe
db.username=WEB
db.password=WEB
>src/main/java/kh/mybatis/member/service/MemberService.java
package com.kh.mybatis.member.model.service;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import static com.kh.mybatis.common.SqlSessionTemplate.getSession;
import com.kh.mybatis.member.model.dao.MemberDao;
import com.kh.mybatis.member.model.vo.Member;
public class MemberService {
private MemberDao dao = new MemberDao();
// 데이터베이스에서 숫자를 조회 후 리턴해주는 역할
public int getMemberCount() {
int count = 0;
SqlSession session = getSession();
count = dao.getMemberCount(session);
session.close();
return 2;
}
public List<Member> findAll() {
List<Member> members = null;
// SqlSession : 실제 DB와 연결할 수 있고, 실행시켜야 할 쿼리문도 알고 있음
SqlSession session = getSession();
members = dao.findAll(session);
// 데이터가 잘 넘어오는지 확인
// System.out.println(members);
// session 조회 후 close 해줌
session.close();
return members;
}
public Member findMemberById(String id) {
Member member = null;
SqlSession session = getSession();
member = dao.findMemberById(session, id);
session.close();
return member;
}
public int save(Member member) {
int result = 0;
SqlSession session = getSession();
if(member.getNo() != 0) {
// update
} else {
// insert
result = dao.insertMember(session, member);
}
if(result > 0) {
session.commit();
} else {
session.rollback();
}
session.close();
return result;
}
}
>src/main/java/kh/mybatis/member/dao/MemberDao.java
package com.kh.mybatis.member.model.dao;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.kh.mybatis.member.model.vo.Member;
public class MemberDao {
public int getMemberCount(SqlSession session) {
/*
* SqlSession 객체가 제공하는 메소드를 통해서 SQL을 실행 시킨다.
* 객체 한 개를 조회하기 위해서 SqlSession 객체의 selectOne() 메소드를 사용한다.
* - 첫 번째 매개 값은 쿼리문이 존재하는 경로이다.("Mapper네임스페이스.쿼리문아이디")
* - 두 번째 매개 값은 쿼리문에서 사용될 파라미터 객체이다.
* * 두 개 이상 전달해주고 싶을 때 : 배열, List, Map에 여러 개의 값을 담아서 준다. (대부분 Map으로 많이 넘김)
*/
// selectOne : 객체 하나만 가져오겠다라는 뜻
return session.selectOne("memberMapper.selectCount");
}
public List<Member> findAll(SqlSession session) {
// session의 selectList 메소드를 통해 ("")안의 값들을 객체로 변환해서 List형태로 반환받는다.
// 조회되는 데이터가 없을 경우 빈 리스트 객체를 만들어서 넘겨주기 때문에 테스트에서는 무조건 통과한다.
return session.selectList("memberMapper.selectAll");
}
public Member findMemberById(SqlSession session, String id) {
// 만약 조회되는 값이 없으면 null 리턴해준다.
return session.selectOne("memberMapper.selectMemberById", id);
}
public int insertMember(SqlSession session, Member member) {
return session.insert("memberMapper.insertMember", member);
}
}
'국비지원_Java > Java Programming_2' 카테고리의 다른 글
자바 프로그래밍_Day_98_Framework : MyBatis 동적 쿼리 / 게시글 조회 (0) | 2022.01.12 |
---|---|
자바 프로그래밍_Day_97_Framework : MyBatis 동적 SQL (0) | 2022.01.08 |
자바 프로그래밍_Day_96_Framework : MyBatis / 동적 쿼리 (0) | 2022.01.06 |
자바 프로그래밍_Day_94_Framework:MyBatis/TDD (0) | 2022.01.02 |
자바 프로그래밍_Day_93_Framework:Maven (0) | 2021.12.29 |