지지자 - ORM을 활용하면 생산성이 높아지고 캐시 등 다양한 저장소를 활용하기에 유연한 구조를 만들어 준다는 장점을 내세웁니다.
회의론자 - 초기에 배워야 할 지식이 많아서 생산성이 단기에는 나타나지 않으며, 숙련된 개발자만이 원하는 SQL을 유도할 수 있기 때문에 대용량 데이터를 다루기에는 실용적이지 않다고 주장
ORM의 범주로 분류하기에는 논란
iBatis나 MyBatis의 인기가 높은 편이기 때문에 참고삼아 포함
iBatis처럼 쿼리를 res/values/sqlmaps.xml 파일에 저장한다.
1
2
3
4
5
6
7
|
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="getCategoryById">
SELECT * FROM category WHERE id = #id#;
</string>
</resources>
|
1
2
3
4
5
6
|
public Note get(long noteId) {
Map<String, Object> params = new HashMap<String, Object>();
params.put(ID, noteId);
return abatis.executeForBean("getNoteById", params, Note.class);
}
|
테이블의 칼럼명과 Java 객체의 멤버변수명의 매핑은 관례에 따라서 알아서 처리한다. 'user_id' 칼럼을 'userId'로 매핑하는 변환도 자동으로 지원된다.
ActiveAndroid
Ruby on Rails처럼 Active Record 패턴을 지원하는 라이브러리이다. AndroidManifest.xml 파일에 데이터베이스 이름과 버전을 선언하고, Application 클래스에서 ActiveAndroid의 initialize 메서드와 dispose 메서드를 호출해야 한다.
@Table과 @Column 애노테이션을 사용해서 엔티티 클래스를 정의하면 데이터베이스에 테이블이 생성된다. 엔티티로 사용할 클래스는 <예제 4>와 같이 Model 클래스를 상속하고 애노테이션으로 매핑 정보를 기술한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Table(name = "Note")
public class Note extends Model {
@Column(name = "title") private String title;
@Column(name = "contents") private String contents;
@Column(name = "date") private java.util.Date date;
@Column(name = "categoryId") private Long categoryId;
// To One Relationship
@Column(name = "category", onDelete = ForeignKeyAction.CASCADE)
private Category category;
// GETTER, SETTER 생략
}
@Table(name = "Category")
public class Category extends Model {
@Column(name = "name") private String name;
@Column(name = "categoryinfo") private String categoryInfo;
...
// To-many Relationship
public List<Note> getNote() {
return getMany(Note.class, "category");
}
}
|
Category와 Note는 1:N 관계이다. Note는 하나의 Category에 속할 수 있으며 이를 클래스에서 참조로 구현한다. @Column(name = "category", onDelete = ForeignKeyAction.CASCADE)에서 볼 수 있듯이 ActiveAndroid는 Cascade delete mode를 지원한다. iOS의 Core Data에서는 기본적으로 릴레이션을 설정할 때 Delete Mode를 설정하는 기능을 제공하지만, Android에서는 ActiveAndroid가 유일하게 이를 지원한다.
Category 클래스에서는 상위클래스인 'Model'에 정의된 getMany 메서드를 사용해 사용해 To Many 관계에 있는 레코드를 손쉽게 얻을 수 있다. getMany 메서드는 Select 클래스 객체를 사용해 릴레이션 관계에 있는 레코드를 로딩하는데, 별도의 캐싱 기법 없이 매번 쿼리문을 수행하고 결과를 메모리로 로딩한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class NoteDaoActiveAndroid implements NoteDaoInterface {
@Override
public void insert(Note note) {
note.save();
}
@Override
public Note get(long noteId) {
return new Select().from(Note.class).where("Id = ?", noteId).executeSingle();
}
@Override
public void delete(Note note) {
note.delete();
}
|
입력, 조회, 삭제 구현
Android-Active-Record
마찬가지로 Active Record 패턴을 구현한 라이브러리이다. 데이터베이스 테이블을 생성하려면 사용자가 수동으로 코드를 추가해야 한다는 단점이 있다
Colored By Color Scripter™
1
2
3
4
5
6
7
8
9
|
public class Note extends ActiveRecordBase { public String title;
public String title
public String contents;
public long categoryId;
...
// Primary Key는 ActiveRecordBase로부터 상속받는다.
public long getId() {
return _id;
}
|
엔티티 클래스는 ActiveRecordBase를 상속받아야 한다. ActiveRecordBase에는 insert, update, find 등의 메서드가 미리 구현되어 있어서 DAO 같은 클래스 없이 Model 객체만으로 CRUD 기능을 사용한다. 테이블의 Primary Key가 되는 _id는 ActiveRecordBase에 _id로 선언되어 있고 getID라는 Accessor 메서드가 정의되어 있다. 엔티티 간의 관계를 설정하는 기능은 제공되지 않는다.
1
2
3
4
5
6
7
8
9
|
public void insert(Note note) throws ActiveRecordException {
note.setDatabase(database);
note.save();
}
public List<Note> getByTitleQuery(final String query) ) throws ActiveRecordException {
return activeRecordBase.find(Note.class, "title LIKE ?", new String[]{"%"+query+"%"});
}
|
ActiveRecord는 ActiveRecordException이라는 Exception을 발생시키는데 이는 Checked Exception이기 때문에 try-catch 구문을 사용하여 처리할 것을 권장
ORMLite
Android ORM 라이브러리 중 가장 강력한 기능을 제공
ORMLite만이 유일하게 일반 Java 환경과 Android 환경 모두에서 사용할 수 있다.
1
2
3
4
5
6
7
8
9
|
@DatabaseTable
public class Category extends BaseDaoEnabled<Category, Long> {
@DatabaseField(generatedId = true) private Long Id;
@DatabaseField(canBeNull = false) private String name;
@DatabaseField private String categoryInfo;
@ForeignCollectionField private ForeignCollection<Note> note;
// getter, setter 생략
}
|
Foreign Key를 지원하며 To-Many Relationship에서는 Collection 인터페이스를 상속받는 ForeignCollection를 사용하여 자신을 참조하는 엔티티 객체를 담을 수 있다. ORMLite는 가장 많은 애노테이션을 지원한다. 인덱싱이나 참조하는 객체가 변경되었을 때 이를 반영하는 옵션도 애노테이션으로 설정할 수 있다.
데이터베이스의 스키마 생성은 <예제 10>과 같이 OrmLiteSqliteOpenHelper의 하위 클래스의 onCreate 메서드와 onUpgrade 메서드 안에서 사용자가 구현해야 한다. onUpgrade 메서드는 사용자가 커스터마이징할 수 있으므로 Raw Query를 사용하여 마이그레이션을 수행할 수 있다.
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
44
|
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String DB_NAME = "Note.db";
private static final int DB_VERSION = 1;
private Dao<Category, Long> categoryDao = null;
private Dao<Note, Long> noteDao = null;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqliteDatabase, ConnectionSource connectionSource) {
try {
TableUtils.createTable(connectionSource, Category.class);
TableUtils.createTable(connectionSource, Note.class);
onCreate(sqliteDatabase, connectionSource);
} catch (SQLException e) {
e.printStackTrace();
}
}
public Dao<Category, Long> getCategoryDao() throws SQLException {
if (categoryDao == null)
categoryDao = getDao(Category.class);
return categoryDao;
}
public Dao<Note, Long> getNoteDao() throws SQLException {
if (noteDao == null)
noteDao = getDao(Note.class);
return noteDao;
}
@Override
public void onUpgrade(SQLiteDatabase sqliteDatabase, ConnectionSource connectionSource, int oldVer, int newVer) {
try {
TableUtils.dropTable(connectionSource, Category.class, true);
TableUtils.dropTable(connectionSource, Note.class, true);
onCreate(sqliteDatabase, connectionSource);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
|
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
|
public class NoteDaoOrmLite implements NoteDao {
private RuntimeExceptionDao<Note, Long> noteDao = null;
public void init(Context context) {
DatabaseHelper helper = new DatabaseHelper(context);
noteDao = helper.getNoteDao();
}
@Override
public void insert(Note note) {
noteDao.create(note);
}
@Override
public List<Note> getByTitleQuery(String query) {
try {
return noteDao.queryBuilder().where()
.like("title", "%" + query + "%").query();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
|
지원 플랫폼
greenDAO와 ORMLite, stORM은 최근까지 업데이트가 지속적으로 이루어지고 있다. aBatis와 ActiveAndroid, Android-Active-Record는 최종 커밋이 발생한 이래 꽤 오랜 기간 동안 커밋이 없었다.
이중에 가장 좋다고 하는 ORMLite 에 대해서 사용방법을 적어 보겠습니다.
ORMlite
ORMlite 홈페이지 : http://ormlite.com/sqlite_java_android_orm.shtml
영문 설명 사이트 링크 : http://logic-explained.blogspot.kr/2011/12/using-ormlite-in-android-projects.html
sample 프로젝트 : https://github.com/justadreamer/WishListManager
ORMlite 준비
http://ormlite.com/sqlite_java_android_orm.shtml 홈페이지를 통해서 jar 파일을 다운로드 합니다.
가운데 있는 get started 를 선택합니다.
ORMLite release page 를 선택 합니다 .
core jar , android jar 파일을 다운로드 해서 프로젝트 lib 에 넣습니다.
준비 과정은 끝났습니다 .
이제 코드를 통해서 사용하는 방법에 대해 알아 보겠습니다.
ORMlite 사용
Colored By Color Scripter™
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String DATABASE_NAME = "ContentListDB.sqlite";
private static final int DATABASE_VERSION = 1;
private Dao<GroupList, Integer> groupListDao = null;
private Dao<Content, Integer> contentDao = null;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
try {
TableUtils.createTable(connectionSource, GroupList.class);
TableUtils.createTable(connectionSource, Content.class);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource,
int oldVersion, int newVersion) {
try {
TableUtils.dropTable(connectionSource, GroupList.class, true);
TableUtils.dropTable(connectionSource, Content.class, true);
onCreate(database, connectionSource);
} catch (SQLException e) {
e.printStackTrace();
}
}
public Dao<GroupList, Integer> getGroupListDao() {
if (null == groupListDao) {
try {
groupListDao = getDao(GroupList.class);
} catch (java.sql.SQLException e) {
e.printStackTrace();
}
}
return groupListDao;
}
public Dao<Content, Integer> gerContentListDao() {
if (null == contentDao) {
try {
contentDao = getDao(Content.class);
} catch (SQLException e) {
e.printStackTrace();
}
}
return contentDao;
}
}
|
SqlLite 를 통해서 데이터베이스를 생성합니다 .
기존 안드로이드 에서 사용하는 SqlLite 와 조금 다른 점이 있습니다.
extends OrmLiteSqliteOpenHelper 상속을 하고
ORMlite 메서드들 을 이용하여 데이터 베이스 생성및 테이블 수정을 구성하였습니다.
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
|
@DatabaseTable
public class Content {
@DatabaseField(generatedId = true)
int id;
@DatabaseField
String name;
@DatabaseField(foreign = true, foreignAutoRefresh = true)
GroupList list;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public GroupList getList() {
return list;
}
public void setList(GroupList list) {
this.list = list;
}
}
|
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
44
|
@DatabaseTable
public class GroupList {
@DatabaseField(generatedId = true)
int id;
@DatabaseField
String name;
@DatabaseField
int order;
@ForeignCollectionField
ForeignCollection<Content> items;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public ForeignCollection<Content> getItems() {
ArrayList<Content> itemList = new ArrayList<Content>();
for (Content item : items) {
itemList.add(item);
}
return items;
}
public void setItems(ForeignCollection<Content> items) {
this.items = items;
}
}
|
@DatabaseTable , @DatabaseField ,@ForeignCollectionField 어노테이션 등을 이용하여
데이터베이스 테이블과 필드를 매칭 작업을 합니다 .
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
public class DatabaseManager {
static private DatabaseManager instance;
private final DatabaseHelper helper;
static public void init(Context context) {
if (null == instance) {
instance = new DatabaseManager(context);
}
DatabaseHelper helper = new DatabaseHelper(context);
helper.getGroupListDao();
}
static public DatabaseManager getInstance() {
return instance;
}
private DatabaseManager(Context context) {
helper = new DatabaseHelper(context);
}
public DatabaseHelper getHelper() {
return helper;
}
public List<GroupList> getAllGroupLists() {
List<GroupList> wishLists = null;
try {
wishLists = getHelper().getGroupListDao().queryForAll();
} catch (SQLException e) {
e.printStackTrace();
} catch (java.sql.SQLException e) {
e.printStackTrace();
}
return wishLists;
}
public void addGroupList(GroupList groupList) {
try {
try {
DatabaseManager.getInstance().getHelper().getGroupListDao().create(groupList);
} catch (java.sql.SQLException e) {
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void updateGroupList(GroupList groupList) {
try {
try {
DatabaseManager.getInstance().getHelper().getGroupListDao().update(groupList);
} catch (java.sql.SQLException e) {
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public List<GroupList> getGroupList() {
List<GroupList> groupList = null;
groupList = DatabaseManager.getInstance().getAllGroupLists();
return groupList;
}
public GroupList getGroupListWithId(int groupListId) {
GroupList groupList = null;
try {
try {
groupList = DatabaseManager.getInstance().getHelper().getGroupListDao().queryForId(groupListId);
} catch (java.sql.SQLException e) {
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
return groupList;
}
}
|
데이터 베이스 생성 및 매핑작업을 마치고 , 사용할 구문을 ORMLite 를 통해서 만들어 줍니다 .
addGroupList , updateGroupList , getGroupList , getGroupListWithId 메서드를 보시면
ORMLite 문법을 통해서 만든것을 볼수 있습니다 .
문법에 대해서 자세한 정보는 홈페이지 가면 자세히 나와있습니다 .
이제 마지막으로 Manager 를 구성한 메서드를 사용하면 되겠습니다 .
Activity 에서 사용한 예제 입니다 .
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
|
public class MainActivity extends Activity {
EditText editText;
TextView textView;
String names;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DatabaseManager.init(this);
editText = (EditText) findViewById(R.id.editText);
textView = (TextView) findViewById(R.id.textView);
}
public void addGroupList(View view) {
GroupList groupList = new GroupList();
groupList.setName(editText.getText().toString());
DatabaseManager.getInstance().addGroupList(groupList);
List<GroupList> GroupLists = DatabaseManager.getInstance().getAllGroupLists();
for (GroupList group : GroupLists) {
names = names + group.getName();
}
textView.setText(names);
}
}
|
ORMLite 장단점
ORMLite 를 사용하지 않으면
1
2
3
4
5
6
7
|
String str = "insert into book(title, author, publisher,publishDate,isbn,bookimg,booklink,status)" +
" values('" + bookList.get(position).getTitle() + "','" + bookList.get(position).getAuthor() + "'," +
"'" + bookList.get(position).getPublisher() + "', '" + bookList.get(position).getPublishDate() + "','"
+ bookList.get(position).getIsbn() + "','" + bookList.get(position).getImg() + "','"
+ bookList.get(position).getLink() + "','"+status+"');";
|
이와 같은 sql 문을 사용하게 되는데 '' 을하다보면 실수를 하는 경우가 많습니다 .
ORMLite 의 장점은 이러한 오타로 인한 오류를 피할수 있습니다 .
또한 트랙잰션 관리를 할수 있습니다 .
하지만 ORMLite 단점은 학습이 필요하다는 것입니다.
조금의 학습을 통해서 익혀 놓으면 단점보다는 도움이 많이 될수 있을것 같습니다 .