Day59 Java프로그래밍 기초(reflect), 실습프로젝트(Mybatis)
Reflection API
클래스로딩
- 클래스를 동적으로 호출 할 때 필요하다. 주로 라이브러리 개발에 사용된다.
- 클래스 로딩을 하면 스태틱 변수를 준비하고 스태틱 블록을 실행한다.
- 객체를 인스턴스 하는 것이 클래스 로딩이며, 레퍼런스(배열 등)을 선언하는 것과는 다르다.
1
Class.forName("패키지를 포함한 전체 클래스명")
중첩클래스로딩
- 상위 클래스를 로딩하는 것과 종속된 클래스를 로딩하는 것은 별개이다.
- 상위 클래스가 종속 클래스를 사용하는 것이지 포함하는 것은 아니기 때문이다.
- 종속 클래스는 상위 클래스의 한 기능을 담당하는 것이다.
- 종속 클래스는 패키지.상위클래스$종속클래스의 경로로 로딩 가능하다.
클래스 로딩 방법
- 클래스 로딩방법은 2가지가 있다.
1 2
Class clazz1 = Class.forName("fully qualified class name") Class clazz2 = classNmae.class;
- forName : 클래스를 로딩 시 스태틱변수 선언과 스태틱필드 실행
- .class : 클래스 로딩 시 스태틱 미 실행, 실제 클래스 사용시 스태틱변수 선언과 스태틱필드 실행
- .class를 사용하면 유지보수 측면에서 직접 클래스 명을 바꿔줘야한다.(유지보수에 불리하다.)
1 2 3 4 5 6
// className만 변경하면 클래스 로딩 부분의 수정이 없다. String className; Class clazz1 = Class.forName(className); // class가 로딩되는 부분에서 모두 수정해야한다. className2.class
클래스 정보 추출
클래스 이름
- getSimplName() : 클래스 이름만 추출
- getName() : 클래스 전체 이름 추출
- getCanonicalName() : “.”으로 정규화된 클래스 전체 이름 추출
- getTypeName() : 클래스 타입을 기분으로 전체 이름 추출
익명클래스의 경우 CanonicalName은 null 이지만 TypeName,Name은 $n으로 표시된다.
1 2 3 4 5 6
class A {} Class<?> clazz = Class.forName("com.eomcs.reflect.ex02.Exam0110$A"); System.out.println(clazz2.getSimpleName()); // A System.out.println(clazz2.getName()); // com.eomcs.reflect.ex02.Exam01$A System.out.println(clazz2.getCanonicalName()); // com.eomcs.reflect.ex02.Exam01.A System.out.println(clazz2.getTypeName()); // com.eomcs.reflect.ex02.Exam01$A
배열의 타입 및 값의 타입
배열은
[b
처럼 대괄호로 표현된다.1 2 3 4 5
System.out.println(byte.class.getName()); // byte 타입 System.out.println(byte[].class.getName()); // byte 배열 타입 // result // byte // [B
배열의 타입을 구하려면 getComponentType()을 통해 배열의 객체 타입을 받을 수 있다.
1 2 3 4 5 6 7
System.out.println(Date.class.getName()); System.out.println(Date[].class.getName()); System.out.println(Date[].class.getComponentType().getName()); // result // java.sql.Date // [Ljava.sql.Date; // java.sql.Date
Collection 값의 타입
Collection 값의 type을 추출하기 위해서는 값을 꺼내와 클래스 정보를 얻고 이름을 얻으면된다.
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
public class Test { public static void main(String[] args) throws Exception { ArrayList<Object> values = new ArrayList<>(); values.add(100); values.add(100L); values.add(3.14f); values.add(314.55); values.add(true); values.add('A'); values.add("Hello"); values.add(new int[] {100, 200, 300}); values.add(new String[] {"aaa", "bbb", "ccc"}); for (Object value : values) { printTypeInfo(value.getClass()); } } private static void printTypeInfo(Class<?> type) { if (type.getName().startsWith("[")) { System.out.printf("=> %s[]\n", type.getComponentType().getName()); } else { System.out.printf("=> %s\n", type.getName()); } }
수퍼클래스 정보
getSuperclass() : 수퍼 클래스의 클래스 정보를 가져온다.
1 2 3
Class<?> superClazz = clazz.getSuperclass(); System.out.println(superClazz.getName()); System.out.println(superClazz.getSuperclass().getName());
중첩클래스 정보
public 중첩클래스 호출
- getClasses() : Class.forName으로 호출한 class에 중첩된 class 정보를 리턴한다.
public으로 공개된 중첩클래스 및 인터페이스를 가져온다.
1 2 3 4
Class<?> clazz = Class.forName("com.eomcs.reflect.ex02.Exam0310$A"); // public 으로 공개된 중첩 클래스 및 인터페이스 정보를 가져온다. Class<?>[] nestedList = clazz.getClasses();
모든 중첩클래스 호출
- getDeclaredClasses() : 클래스 안에 선언된 모든 클래스 정보를 가져온다.
클래스 안에 선언 메소드에 정의된 로컬 클래스는 대상이 아니다.
1 2 3 4 5 6 7 8
Class<?> clazz = Class.forName("com.eomcs.reflect.ex02.Exam0320$A"); // 접근 범위에 상관 없이 모든 중첩 클래스 및 인터페이스 정보를 가져온다. // => 메서드 안에 정의된 로컬 클래스는 대상이 아니다. Class<?>[] nestedList = clazz.getDeclaredClasses(); for (Class<?> nested : nestedList) { System.out.println(nested.getName()); }
구현인터페이스 정보
- getInterfaces() : 해당 클래스가 구현한 인터페이스 정보를 가져온다.
구현한 인터페이스의 수퍼 클래스 정보는 가져오지 않는다.
1 2 3 4 5 6 7
Class<?> clazz = Class.forName("com.eomcs.reflect.ex02.Exam0410$D"); // 해당 클래스가 구현한 인터페이스 정보를 가져온다. Class<?>[] list = clazz.getInterfaces(); for (Class<?> c : list) { System.out.println(c.getName()); }
메소드 정보 추출
pulic 메서드
getMethods() : 해당 클래스에 선언된 public 메서드 + 상속받은 Public 메서드를 가져온다.
1 2 3 4 5 6 7 8 9 10
Class<?> clazz = Exam0110.class; // 클래스에서 메서드 정보를 추출하기 // => 해당 클래스에 선언된 public 메서드 + 상속 받은 public 메서드 Method[] list = clazz.getMethods(); for (Method m : list) { System.out.println(m.getName()); } } // Object메서드도 가져온다.
클래스에 선언한 메서드
getDeclaredMethods() : 현재클래스에 정의된 모든 메서드 정보를 가져온다.
1 2 3 4 5 6 7
Class<?> clazz = Exam0120.class; // => 현재 클래스에 정의된 모든 메서드 Method[] list = clazz.getDeclaredMethods(); for (Method m : list) { System.out.println(m.getName()); }
특정 메서드만 추출
- getMethod(“methodName”) : 현재클래스에서 가용한 public 메서드(상속받은 메서드 포함)중에 methodName의 메서드 정보를 가져온다.
getDeclaredMethod(“methodName) : 현재클래스에서 모든 메서드 중에 methodName의 메서드 정보를 가져온다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Class<?> clazz = Exam0210.class; // 해당 클래스에 선언된 메서드와 상속 받은 메서드까지 포함하여 // 파라미터가 없는 "m3" 이름을 가진 public 메서드 추출 // Method m0 = clazz.getMethod("m3"); // public이 아니기 때문에 못 찾는다. Method m = clazz.getMethod("m1"); // OK! System.out.println(m.getName()); System.out.println(clazz.getMethod("toString").getName()); System.out.println("----------------------"); // => public 이 아닌 메서드를 찾고 싶다면, m = clazz.getDeclaredMethod("m3"); // OK System.out.println(m.getName()); // => 단 현재 클래스에 정의된 메서드를 찾는다. // => 상속 받은 메서드는 제외한다. System.out.println(clazz.getDeclaredMethod("toString")); // 예외 발생! // 상속 받은 메서드는 못찾는다.
파라미터가 있는 경우 매개변수로 (“methodName”,”parmeterType*“)을 매개변수 순서에 맞춰서 넘겨야 method정보를 가져올 수 있다.
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 32 33 34 35
Class<?> clazz = Exam0220.class; // 파라미터가 없는 메서드를 찾을 때는 파라미터의 타입 정보를 넘기지 않는다. System.out.println(clazz.getMethod("m1").getName()); // 파라미터가 있는 메서드를 찾을 때 그 파라미터의 타입 정보를 넘겨야 한다. // 타입정보 = 클래스 정보 = Class 객체 Class<?> parameterType = String.class; Method m = clazz.getMethod("m2", parameterType); System.out.println(m.getName()); // 위와 같다. Method m2 = clazz.getMethod("m2", String.class); System.out.println(m2.getName()); m = clazz.getMethod("m2", Class.forName("java.lang.String")); System.out.println(m.getName()); // primitive 타입도 클래스 정보가 있다. // int => int.class // byte,short,int,long,float,double,boolean,char 는 비록 클래스는 아니지만, // 일반 클래스처럼 타입 정보를 꺼낼 수 있도록 "class"라는 스태틱 변수를 제공한다. Class<?> intType = int.class; Class<?> stringType = String.class; m = clazz.getMethod("m3", stringType, intType); System.out.println(m.getName()); m = clazz.getMethod("m3", String.class, int.class); System.out.println(m.getName()); // 메서드의 파라미터 순서를 지켜야 한다. // m = clazz.getMethod("m3", int.class, String.class); // System.out.println(m.getName()); System.out.println(clazz.getMethod("m3", String.class, int.class).getName());
메서드 호출
- getMethod를 통해서 method 정보를 추출한다.
- invoke() 메서드를 통해서 메서드를 동적으로 호출한다.
스태틱메서드 호출
invoke(null,parameter*) : 스태틱 메서드는 인스턴스 생성이 필요 없으므로 바로 호출 할 수 있다.\
1 2 3
Class<?> cls = ClassName.class; Method m = cls.getMethod("methodName",parameterType*); m.invoke(null,parameter*);
인스턴스메서드 호출
- invoke(new Instance(),parameter*) : 인스턴스 메서드는 인스턴스 매개변수가 필요하다.
- 인스턴스 없이 invoke를 호출하면 NullPointException 에러가 발생한다.
객체를 미리 생성 할 수 도 있고, 매개변수에 직접 생성할 수도 있다.
1 2 3 4 5 6 7 8 9 10 11
Class<?> clazz = Exam0320.class; // 1) 인스턴스 메서드를 찾아 호출하기 Method m = clazz.getMethod("minus", int.class, int.class); // => 인스턴스 메서드를 호출할 때는 반드시 인스턴스 주소를 넘겨야 한다. Exam0320 obj = new Exam0320(); m.invoke(obj, 10, 20); // 리플렉션 API를 사용하여 인스턴스 메서드 호출 // => 인스턴스 메서드를 호출하는 일반적인 방식 obj.minus(10, 20);
메서드 파라미터
파라미터를 new Objec[]{value, value, …}으로 전달 할 수도 있다.
1 2 3 4 5
// 파라미터 값을 낱개로 전달하기 m.invoke(null, "홍길동", 100, 90, 80); // 파라미터 값을 배열에 담아서 전달할 수 있다. m.invoke(null, new Object[] {"홍길동", 100, 100, 100});
- 배열의 파라미터인 경우는 배열에 담아서 전달해야한다.
- 가변 파라미터의 경우에도 배열에 담아서 전달해야한다.
필드 정보 추출
- getDeclaredFields() : 현재클래스의 필드정보 추출한다.
- getDeclaredField(“FeildName”) : 현재클래스에서 FieldName의 필드를 추출한다.
fieldName.setAccessible([true false]) : 필드의 접근을 동적으로 제어한다. - setAccessible(true) : 모든 modifier에 접근가능하다
필드에 직접적으로 접근하는 것은 객체지향의 개념에 어긋나는 행위이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Class<?> clazz = Car.class; Field[] fields = clazz.getDeclaredFields(); for (Field f : fields) { System.out.printf("%s:%s\n", f.getName(), f.getType().getName()); } Constructor<Car> defaultConst = (Constructor<Car>) clazz.getConstructor(); Car car = defaultConst.newInstance(); // 1) private 필드는 일반적인 방식으로 접근할 수 없다. // car.maker = "비트자동차"; // 컴파일 오류! // 2) 다음과 같이 Reflection API를 사용하면 private 필드에 접근할 수 있다. Field makerField = clazz.getDeclaredField("maker"); // private modifier로 선언된 필드라 하더라도 // 다음 메서드를 통해 접근 가능하도록 만들 수 있다. makerField.setAccessible(true); // private 필드에 값 넣기 // 가능해? makerField.set(car, "비트자동차");
생성자 정보 추출
생성자 및 파라미터
- getConstructors() : 생성자의 정보들을 가져온다.
getParameterCount() : 메서드의 파라미터 갯수를 카운트 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class Exam01 { public Exam01() {} public Exam01(int i) {} public Exam01(String s, int i) {} public static void main(String[] args) { Class<?> clazz = Exam01.class; // 생성자 목록 가져오기 Constructor<?>[] list = clazz.getConstructors(); for (Constructor<?> c : list) { System.out.printf("%s(%d)\n", c.getName(), c.getParameterCount()); } } }
생성자 호출
객체를 생성하기 위해서는 클래스호출, 생성자 호출, 인스턴스호출 순으로 이루어진다.
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
public class Exam03 { int value; public Exam03(int i) { this.value = i; } public void print() { System.out.printf("value=%d\n", this.value); } public static void main(String[] args) throws Exception { Class<?> clazz = Exam03.class; // newInstance()는 객체를 생성한 후 기본 생성자를 호출한다. // Exam03은 기본 생성자가 없기 때문에 실행 오류가 발생한다! // Exam03 obj0 = (Exam03) clazz.newInstance(); // 실행 오류! // 해결=> 생성자를 준비한다. Constructor<?> c = clazz.getConstructor(int.class); // 생성자 객체를 통해 인스턴스를 생성해야 한다. Exam03 obj = (Exam03) c.newInstance(200); obj.print(); } }
타입정보 추출
파라미터 타입
- getParameters() : 메서드의 파라미터들의 정보를 가져온다.
getType() : 파라미터의 타입정보를 가져온다.
1 2 3 4 5 6 7 8 9 10 11 12
Class<?> clazz = Exam0110.class; // 해당 클래스에 정의된 메서드를 모두 가져온다. Method[] methods = clazz.getDeclaredMethods(); for (Method m : methods) { System.out.printf("%s:\n", m.getName()); // 메서드의 파라미터 정보를 가져온다. Parameter[] parameters = m.getParameters(); for (Parameter p : parameters) { System.out.printf(" %s : %s\n", p.getName(), p.getType().getName()); }
리턴 타입
getReturnType() : 메서드의 리턴 타입정보를 가져온다.
1 2 3 4 5 6 7 8 9 10 11
Class<?> clazz = Exam0210.class; // 클래스에 정의된 메서드를 모두 가져온다. Method[] methods = clazz.getDeclaredMethods(); for (Method m : methods) { System.out.printf("%s:\n", m.getName()); // 메서드의 리턴 타입 가져오기 Class<?> returnType = m.getReturnType(); System.out.printf(" 리턴: %s\n", returnType.getName()); }
제네릭 타입
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
32
33
34
35
36
37
38
39
40
41
42
43
public class Exam0220 {
public String m1(String name, int age) {
return null;
}
public char[] m2() {
return null;
}
public ArrayList<String> m3(File file, String name) {
return null;
}
public void m4() {}
public Map<String,File> m5() {
return null;
}
public static void main(String[] ok) {
Class<?> clazz = Exam0220.class;
// 클래스에 정의된 메서드를 모두 가져온다.
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
System.out.printf("%s:\n", m.getName());
// 메서드의 제네릭 리턴 타입 가져오기
Type returnType = m.getGenericReturnType();
System.out.printf(" 리턴: %s\n", returnType.getTypeName());
if (returnType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
System.out.print(" => ");
for (Type actualType : actualTypes) {
System.out.print(actualType.getTypeName() + ", ");
}
System.out.println();
}
}
}
}
modifier 타입 (public, static, protected)
- public, static, protected에 할당된 비트와 호출한 객체와 비트 연산(&)을 통해 해당 객체의 modifier타입을 추출한다.
- reflect에 내장된 isModifier()를 통해 modifier타입을 추출한다.
비트 연산자가 속도 측면에서 유리하고 내장함수가 시인성 측면에서 유리하다.
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 32 33 34 35 36
public static void main(String[] ok) { Class<?> clazz = String.class; // 클래스에 정의된 메서드를 모두 가져온다. Method[] methods = clazz.getDeclaredMethods(); for (Method m : methods) { System.out.printf("%s() => ", m.getName()); int modifiers = m.getModifiers(); System.out .println(String.format("%32s", Integer.toBinaryString(modifiers)).replace(' ', '0')); if (Modifier.isPublic(modifiers)) { // (modifiers & Modifier.PUBLIC) == Modifier.PUBLIC) System.out.print(" public"); } else if ((modifiers & PROTECTED) != 0) { System.out.print(" protected"); } else if ((modifiers & PRIVATE) != 0) { System.out.print(" private"); } // if ((modifiers & STATIC) != 0) if (Modifier.isStatic(modifiers)) { System.out.print(" static"); } if ((modifiers & FINAL) != 0) { System.out.print(" final"); } System.out.println(); } } }
MyBatis
Persistence Framework
사용준비
DTD
XML(Extensible Markup Language)
- 데이터를 구조화 시켜 저장하는 파일로 데이터 끼리 상관관계를 표현 할 수 있다.
- Markup 언어는
<tag> value </tag>
tag로 시작해서 끝나는 언어이다. - XML은 Markup언어의 사용규칙을 준수하여 만들 파일이다.
설정파일 준비
- https://mybatis.org/mybatis-3/ko/getting-started.html 접속
- xml 파일 만들기
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
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- DBMS 접속정보 --> <properties resource="jdbc.properties"/> <!-- SQL문 파일 위치 --> <environments default="development-local"> <!-- 개발하는 동안 사용할 DBMS 접속 정보 --> <environment id="development-local"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="UserDaoMapper.xml"/> <mapper resource="BoardDaoMapper.xml"/> </mappers> </configuration>
사용하기
Dao 기능분리
- 기존 Dao 소스는 java상에서 JDBC를 활용하여 쿼리문을 전송하고 결과를 리턴 받는다.
- 유지보수 측면에서 Java코드와 SQL쿼리가 혼재되어 불리하다.
Mybatis를 사용해서 JDBC API의 역할을 이전하고 소스에서는 자바코드만 작성한다.
SqlSession 객체 생성
- SqlSession을 인스턴스하기 위해서는 SqlSessionFactory 객체가 필요하다.
- SqlSessionFactory를 인스턴스하기 위해서는 SqlSessionFactoryBuilder 객체가 필요하다.
SqlSessionFactoryBuilder가 xml파일을 읽어 Factory 객체를 인스턴스해야한다.
1 2 3 4
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(false);
selectList()
- java에서는 XML에 태그를 넘기면서 sqlSession의 메서드를 호출한다.
리턴된 List타입을 List객체에 저장한다.
1
sqlSession.selectList("aaa.sql1");
- java에서 받은 id를 찾아서 쿼리문을 실행한다.
- 쿼리문을 실행해서 나온 테이블을 resultType에서 받아온 User 객체를 생성하고 담는다.
User객체들을 List에 add한고 List를 리턴한다.
1 2 3 4 5 6 7 8 9 10
<select id="sql1" resultType="bitcamp.myapp.vo.User"> select user_id as no, name, email from myapp_users order by user_id asc </select>
insert()
java에서는 XML에 태그를 넘기면서 sqlSession의 메서드를 호출한다.
1
sqlSession.insert("aaa.sql2", user);
- java에서 받은 id를 찾아서 쿼리문을 실행한다.
- 파라미터로 받은 User타입에서 getter메서드를 찾는다.
- xml에 작성된 #{property}을 getPorperty()으로 변환을 한다.
- 파라미터로 받은 user에서 user.getProperty()를 하여 sql구문을 완성한다.
완성된 sql구문을 sql서버에 전송한다.
1 2 3 4
<insert id="slq2" parameterType="bitcamp.myapp.vo.User"> insert into myapp_users(name, email, pwd, tel) values (#{name}, #{email}, sha1(#{password}), #{tel}) </insert>
selectOne()
- java에서는 XML에 태그를 넘기면서 sqlSession의 메서드를 호출한다.
- 리턴된 객체 정보를 저장한다.
리턴된 객체는 반드시 1개여야 한다.
1
sqlSession.selectOne("sql3.findBy", no);
- java에서 받은 parmeter로 sql구문을 실행한다.
- primitive 타입의 property name은 없기때문 사용자가 임의로 작성 가능하다.
- sql 서버에서 받은 table의 컬럼 정보로 property name을 생성한다.
- resultType에서 setProperty(Object obj)를 호출하여 객체에 정보를 넣는다.
완성된 객체를 java에 넘겨준다.
1 2 3 4 5 6 7 8 9 10 11
<select id="sql3" resultType="bitcamp.myapp.vo.User" parameterType="int"> select user_id as no, name, email, tel from myapp_users where user_id = #{ok} </select>
update()
java에서는 XML에 태그를 넘기면서 sqlSession의 메서드를 호출한다.
1
sqlSession.update("UserDao.update", user);
- java에서 user객체를 받는다.
user의 property name으로 getProperty() 메서드를 통해 값을 얻어 sql구문을 call한다.
1 2 3 4 5 6 7 8 9
<update id="update" parameterType="user"> update myapp_users set name=#{name}, email=#{email}, pwd=sha1(#{password}), tel= #{tel} where user_id=#{no} </update>