Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자

Preview:

Citation preview

Java Annotation과 MyBatis로나만의 ORM Framework을 만들어보자

2011 JCO 11th Conference | Session ${track_#}-${session_#} | Javacommunity.Org

강동혁 (한솔헬스케어)wolfkang@gmail.comRevision: 20110612

하기 싫은 일

Class.forName("com.mysql.jdbc.Driver");Connection c = DriverManager.getConnection();PreparedStatement stmt = c.prepareStatement

("select title, year_made from movies "+ "where year_made >= ?"+ " and year_made < ?");

for(int decadeStart = 1920; decadeStart < 2000; decadeStart += 10){stmt.setInt(1, decadeStart);stmt.setInt(2, decadeStart + 10);ResultSet rs = stmt.executeQuery();while(rs.next()){System.out.println(rs.getString(1) + " (" + rs.getInt(2) + ")");

}}

2

귀찮은 일

<insert id="insertDoctor" parameterType="domain.Doctor">insert into Doctor (id,username,password,email,bio)values (#{id},#{username},#{password},#{email},#{bio})

</insert><update id="updateDoctor" parameterType="domain.Doctor">

update Doctor setusername = #{username}, password = #{password},email = #{email}, bio = #{bio}

where id = #{id}</update><delete id="deleteDoctor” parameterType="int">

delete from Doctor where id = #{id}</delete><select id="selectDoctor" resultType="domain.Doctor">

select * from Doctor</select>

3

머리 아픈 일

<hibernate-mapping>

<class name="hello.Message“ table="MESSAGES">

<id name="id" column="MESSAGE_ID">

<generator class="increment"/>

</id>

<property name="text" column="MESSAGE_TEXT"/>

<many-to-one name="nextMessage" cascade="all" column="NEXT_MESSAGE_ID"/>

</class>

</hibernate-mapping>

SELECT new list(mother, offspr, mate.name)

FROM DomesticCat AS mother

INNER JOIN mother.mate AS mate

LEFT OUTER JOIN mother.kittens AS offspr4

하고 싶은 일

• SQL 작성 최소화

• Object oriented programming

• 로직에 집중

• 단순한 설정 - 쉽고, 직관적

• 학습의 최소화

5

ORM? Hibernate?

6

Hibernate + MyBatis?

7

고려해야 할 점

• 설정

– object, relation 매핑

• SQL 생성

– object 를 SQL 의 파라미터로 전달

– SQL 결과를 object 로 리턴

8

구현 전략

• object 와 relation 의 1:1 매핑 구현– MyBatis 를 이용하여 기본적인 insert, update,

delete, select 문을 runtime 자동 생성

– 생성된 SQL문을 MyBatis SQL repository 에저장

– Association 은 다루지 않음

– Join, subquery 구문은 MyBatis로 처리

• 설정은 Java Annotation 사용

• 일단 MySQL 먼저

9

Java Annotation

• 자바 5.0 에서 소개• 자바 소스 코드에 추가되는 문법적인 메타데이터• 클래스, 메소드, 변수, 파라미터, 패키지에 첨언하

여 컴파일러의 의해 .class 파일에 포함되어 컴파일혹은 런타임 시 이용

• 번거로운 설정 작업들과 반복적인 코드를 줄여줌

• Built-in Annotations– @Override, @Deprecated, @SupressWarnings

• Custom Annotations– @Controller, @Service, @Entity, @Column

10

Custom Annotation

• @interface 선언public @interface MyAnnotation {

String value();}

• Annotation 추가import MyAnnotation@MyAnnotation(value=“my annotation”)public void myMethod(int arg) {

// do something}

11

Annotation API

• Class 와 Field

– getAnnotation(Class<A> annotationClass)특정 타입에 대한 annotation 을 리턴

– getAnnotations()모든 타입의 annotation 을 리턴

– isAnnotationPresent(Class annotationClass)특정 타입에 대한 annotation 이 존재하면true 리턴

12

MyBatis

• 구) iBatis

• SQL문과 객체를 매핑

• SQL문을 XML 파일에 저장

• JDBC 코드 제거

13

출처) www.mybatis.org

MyBatis 예제

• Mapped Statement<mapper namespace="org.mybatis.example.HospitalMapper">

<select id="selectHospital" parameterType="int" resultType="Hospital">

select * from Hospital where id = #{id}</select>

</mapper>

• Java DAO codepublic interface HospitalMapper {

public Hospital selectHospital(int id);}HospitalMapper mapper = session.getMapper(HospitalMapper.class);Hospital hospital = mapper.selectHospital(101);

14

순서

1. Table 생성2. Annotation 정의3. Mapping class 생성4. EntityManager 작성 – CRUD interface5. SQL Generation 클래스 작성

– InsertSqlSource– UpdateSqlSource– DeleteSqlSource– SelectOneSqlSource– SelectListSqlSource

15

1. Table 생성

Create table hospital (

hospitalid INT NOT NULL

PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(30),

regdttm DATETIME

)

16

2. Annotation 정의

public @interface Table {

String value() default "";

}

public @interface Column {

String name() default "";

boolean primaryKey() default false;

boolean autoIncrement() default false;

}

17

3. Mapping Class

@Table(“hospital”)Public class Hospital {

@Column(primaryKey=true,autoIncrement=true)

private Integer hospitalid;@Column private String name;@Column(name=“regdttm”)

private Date regdttm;}

18

4. EntityManager

void insert(Object parameter)

void update(Object parameter)

void delete(Object parameter)

Object load(Object parameter)

List list(Object parameter)

List list(Object parameter, String orderby)

List list(Object parameter, String orderby,

int rows)

19

EntityManager 사용예

EntityManager entityManager = new EntityManager();

Hospital hospital = new Hospital();hospital.setHospitalid(12345);hospital.setName(“JCO병원”);hospital.setRegdttm(new Date());entityManager.insert(hospital);

hospital = entityManager.load(hospital);

hospital.setName(“JCO병원”);entityManager.update(hospital);

entityManager.delete(hospital);

20

5. SQL Generation

• INSERT– 컬럼 중 auto_increment 로 선언된 컬럼은 insert 문

에서 제외

• UPDATE– Primary Key 가 아닌 컬럼들만 update– PK로 where 조건절 생성

• DELETE– parameter 객체에서 null 값이 아닌 field 로 where 조

건절 생성

• SELECT– parameter 객체에서 null 값이 아닌 field 로 where 조

건절 생성

21

5-1. Insert문 생성

• 컬럼 중 auto_increment 로 선언된 컬럼은insert 문에서 제외

<생성되는 SQL>

INSERT INTO hospital (name, regdttm) VALUES (#{name}, #{regdttm})

22

EntityManager.insert()

1: Class<?> clazz = object.getClass();

2: String statementName = PREFIX_INSERT +

3: clazz.getSimpleName();

4: if (!configuration.hasStatement(statementName)) {

5: addMappedStatement(

6: statementName,

7: new InsertSqlSource(sqlSourceParser,clazz),

8: SqlCommandType.INSERT,null);

9: }

10: getSqlSession().insert(statementName, object);

23

InsertSqlSource

1: List<String> columnNames =

2: AnnotationUtil.getNonAutoIncrementColumnNames(clazz);

3: String sql = String.format(

4: “INSERT INTO %1$s ( %2$s ) VALUES ( %3$s ) ",

5: AnnotationUtil.getTableName(clazz),

6: StringUtil.join(columnNames, ","),

7: StringUtil.join(columnNames, "#{%1$s}",","));

8: parse(sqlSourceParser, sql, clazz);

24

AnnotationUtil.getTableName

Table t = clazz.getAnnotation(Table.class);

return t.value();

25

AnnotationUtil.getNonAutoIncrementColumnNames

1: List<String> names = new LinkedList<String>();

2: for (Field field : clazz.getDeclaredFields()) {

3: if (field.isAnnotationPresent(Column.class)) {

4: Column c = field.getAnnotation(Column.class);

5: if (!c.autoIncrement())

6: names.add("".equals(c.name()) ? field.getName():c.name());

7: }

8: }

9: return names;

26

5-2. Update문 생성

• Primary Key 가 아닌 컬럼들만 update

• PK로 where 조건절 생성

<생성되는 SQL>

UPDATE hospital

SET name = #{name}, regdttm = #{regdttm}

WHERE hospitalid = #{hospitalid}

27

EntityManager.update()

1: Class<?> clazz = object.getClass();

2: String statementName = PREFIX_UPDATE +

3: clazz.getSimpleName();

4: if (!configuration.hasStatement(statementName)) {

5: addMappedStatement(

6: statementName,

7: new UpdateSqlSource(sqlSourceParser,clazz),

8: SqlCommandType.UPDATE,null);

9: }

10: getSqlSession().update(statementName, object);

28

UpdateSqlSource

String sql = String.format(

"UPDATE %1$s SET %2$s WHERE %3$s",

AnnotationUtil.getTableName(clazz),

StringUtil.join(

AnnotationUtil.getNonPrimaryKeyColumnNames(clazz),

"%1$s = #{%1$s}", ", "),

StringUtil.join(

AnnotationUtil.getPrimaryKeyColumnNames(clazz),

"%1$s = #{%1$s}"," AND "));

parse(sqlSourceParser, sql, clazz);

29

AnnotationUtil.getNonPrimaryKeyColumnNames

1: List<String> names = new LinkedList<String>();

2: for (Field field : clazz.getDeclaredFields()) {

3: if (field.isAnnotationPresent(Column.class)) {

4: Column c = field.getAnnotation(Column.class);

5: if (!c.primaryKey())

6: names.add("".equals(c.name()) ? field.getName() :

7: c.name());

8: }

9: }

10: return names;

30

AnnotationUtil.getPrimaryKeyColumnNames

List<String> names = new LinkedList<String>();

for (Field field : clazz.getDeclaredFields()) {

if (field.isAnnotationPresent(Column.class)) {

Column c = field.getAnnotation(Column.class);

if (c.primaryKey())

names.add("".equals(c.name()) ? field.getName() : c.name());

}

}

return names;

31

5-3. Delete문 생성

• parameter 객체에서 null 값이 아닌 field 로where 조건절 생성

예)

Hospital hospital = new Hospital();

hospital.setName(“JCO병원”);

entityManager.delete(hospital);

<생성되는 SQL>

DELETE FROM hospital

WHERE name = #{name}

32

EntityManager.delete()

1: Class<?> clazz = object.getClass();

2: String statementName = PREFIX_DELETE + clazz.getSimpleName();

3: if (!configuration.hasStatement(statementName)) {

4: addMappedStatement(

5: statementName,

6: new DeleteSqlSource(sqlSourceParser,clazz),

7: SqlCommandType.DELETE,null);

8: }

9: getSqlSession().delete(statementName, object);

33

DeleteSqlSource

1: public DeleteSqlSource(SqlSourceBuilder sqlSourceParser,

2: Class<?> clazz) {

3: super(sqlSourceParser);

4: staticSql = "DELETE FROM”

5: +AnnotationUtil.getTableName(clazz);

6: }

7: public BoundSql getBoundSql(Object parameterObject) {

8: String sql = staticSql + " WHERE " +

9: StringUtil.join(

10: AnnotationUtil.getNotNullColumnNames(parameterObject),

11: "%1$s = #{%1$s}"," AND ");

12: return getBoundSql(sql,parameterObject);

13: }

34

5-4. Select문 생성

• parameter 객체에서 null 값이 아닌 field 로 where 조건절 생성

예)

Hospital hospital = new Hospital();

hospital.setName(“JCO병원”);

hospital = (Hospital)entityManager.load(hospital);

<생성되는 SQL>

SELECT hospitalid, name, regdttm

FROM hospital

WHERE name = #{name}

35

EntityManager.load()

1: Class<?> clazz = object.getClass();

2: String statementName = PREFIX_LOAD + clazz.getSimpleName();

3: if (!configuration.hasStatement(statementName)) {

4: addMappedStatement(statementName,

5: new SelectOneSqlSource(sqlSourceParser,clazz),

6: SqlCommandType.SELECT,clazz);

7: }

8: Object result = getSqlSession().selectOne(statementName, object);

9: if (result != null)

10: BeanUtils.copyProperties(result, object);

11: return result;

36

SelectOneSqlSource

1: public SelectOneSqlSource(SqlSourceBuilder sqlSourceParser, Class<?> clazz) {

2: super(sqlSourceParser);

3: staticSql = String.format("SELECT %1$s FROM %2$s ",

4: StringUtil.join(AnnotationUtil.getColumnNames(clazz),", "),

5: AnnotationUtil.getTableName(clazz));

6: }

7:

8: public BoundSql getBoundSql(Object parameterObject) {

9: String sql = staticSql + " WHERE " +

10: StringUtil.join(

11: AnnotationUtil.getNotNullColumnNames(parameterObject),

12: "%1$s = #{%1$s}"," AND ") +

13: " LIMIT 1";

14: return getBoundSql(sql,parameterObject);

15: }

37

복습

1. Table 생성2. Annotation 정의3. Mapping class 생성4. EntityManager 작성 – CRUD interface5. SQL Generation 클래스 작성

– InsertSqlSource– UpdateSqlSource– DeleteSqlSource– SelectOneSqlSource– SelectListSqlSource

38

Effect

• 비타민MD 사이트에 일부 적용

• 만약 전체 적용한다면

69% SQL문 제거 가능

39

SQL 적용 전 적용 후

insert 112 7

update 80 2

delete 79 0

select 327 176

총 598 185

Future Work

• Annotation 추가

– Like 구문 지원

– DDL 상의 default 값, not null 등의 constraint 지원

• Transaction

• Caching

• Oracle, MSSQL, DB2 등 다른 DBMS 지원

40

Contact

http://code.google.com/p/mybatis-orm

wolfkang@gmail.com

41

Do It Yourself and Have Fun.

42

이 저작물은 크리에이티브 커먼스 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.

This work is Licensed under Creative Commons Korea Attribution 2.0 License.

Recommended