Upload
donghyeok-kang
View
673
Download
7
Embed Size (px)
Citation preview
Java Annotation과 MyBatis로나만의 ORM Framework을 만들어보자
2011 JCO 11th Conference | Session ${track_#}-${session_#} | Javacommunity.Org
강동혁 (한솔헬스케어)[email protected]: 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
Do It Yourself and Have Fun.
42
이 저작물은 크리에이티브 커먼스 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
This work is Licensed under Creative Commons Korea Attribution 2.0 License.