Local Databases with SQLiteOpenHelper
Overview
For maximum control over local data, developers can use SQLite directly by leveraging SQLiteOpenHelper for executing SQL requests and managing a local database.
In this guide, we'll use the example of building a database to persist user created "Posts" to demonstrate SQLite and SQLiteOpenHelper.
If you want to use SQLite directly but reduce the verbosity of working with the database, check out our Easier SQL with Cupboardguide for a middle ground between SQLite and a full-fledged ORM.
Defining the Database Handler
We need to write our own class to handle database operations such as creation, upgrading, reading and writing. Database operations are defined using the SQLiteOpenHelper:
public class PostsDatabaseHelper extends SQLiteOpenHelper {
// Database Info
private static final String DATABASE_NAME = "postsDatabase";
private static final int DATABASE_VERSION = 1;
// Table Names
private static final String TABLE_POSTS = "posts";
private static final String TABLE_USERS = "users";
// Post Table Columns
private static final String KEY_POST_ID = "id";
private static final String KEY_POST_USER_ID_FK = "userId";
private static final String KEY_POST_TEXT = "text";
// User Table Columns
private static final String KEY_USER_ID = "id";
private static final String KEY_USER_NAME = "userName";
private static final String KEY_USER_PROFILE_PICTURE_URL = "profilePictureUrl";
public PostsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// Called when the database connection is being configured.
// Configure database settings for things like foreign key support, write-ahead logging, etc.
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.setForeignKeyConstraintsEnabled(true);
}
// Called when the database is created for the FIRST time.
// If a database already exists on disk with the same DATABASE_NAME, this method will NOT be called.
@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_POSTS_TABLE = "CREATE TABLE " + TABLE_POSTS +
"(" +
KEY_POST_ID + " INTEGER PRIMARY KEY," + // Define a primary key
KEY_POST_USER_ID_FK + " INTEGER REFERENCES " + TABLE_USERS + "," + // Define a foreign key
KEY_POST_TEXT + " TEXT" +
")";
String CREATE_USERS_TABLE = "CREATE TABLE " + TABLE_USERS +
"(" +
KEY_USER_ID + " INTEGER PRIMARY KEY," +
KEY_USER_NAME + " TEXT," +
KEY_USER_PROFILE_PICTURE_URL + " TEXT" +
")";
db.execSQL(CREATE_POSTS_TABLE);
db.execSQL(CREATE_USERS_TABLE);
}
// Called when the database needs to be upgraded.
// This method will only be called if a database already exists on disk with the same DATABASE_NAME,
// but the DATABASE_VERSION is different than the version of the database that exists on disk.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
// Simplest implementation is to drop all old tables and recreate them
db.execSQL("DROP TABLE IF EXISTS " + TABLE_POSTS);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
onCreate(db);
}
}
}
Important Note: The SQLite database is lazily initialized. This means that it isn't actually created until it's first accessed through a call to getReadableDatabase() or getWriteableDatabase(). This also means that any methods that callgetReadableDatabase() or getWriteableDatabase() should be done on a background thread as there is a possibility that they might be kicking off the initial creation of the database.
Singleton Pattern
Often a SQLite database will be used across your entire application; within services, applications, fragments, and more. For this reason, best practices often advise you to apply the singleton pattern to your SQLiteOpenHelper instances to avoid memory leaks and unnecessary reallocations. The best solution is to make your database instance a singleton instance across the entire application's lifecycle.
public class PostsDatabaseHelper extends SQLiteOpenHelper {
private static PostsDatabaseHelper sInstance;
// ...
public static synchronized PostsDatabaseHelper getInstance(Context context) {
// Use the application context, which will ensure that you
// don't accidentally leak an Activity's context.
// See this article for more information: http://bit.ly/6LRzfx
if (sInstance == null) {
sInstance = new PostsDatabaseHelper(context.getApplicationContext());
}
return sInstance;
}
/**
* Constructor should be private to prevent direct instantiation.
* Make a call to the static method "getInstance()" instead.
*/
private PostsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
The static getInstance() method ensures that only one PostsDatabaseHelper will ever exist at any given time. If thesInstance object has not been initialized, one will be created. If one has already been created then it will simply be returned. Then we can access our database connection with:
// In any activity just pass the context and use the singleton method
PostsDatabaseHelper helper = PostsDatabaseHelper.getInstance(this);
See this android design patterns article for more information.
Defining our Models
In order to access our records from the database more easily, we should create a model class for each of our resources. In this case, let's define a Post and a User model:
public class Post {
public User user;
public String text;
}
public class User {
public String userName;
public String profilePictureUrl;
}
Now we can interact with our data using the models.
CRUD Operations (Create, Read, Update, Delete)
We'll walk through examples of creating, reading, updating, and deleting posts / users in our database.
Inserting New Records
public class PostsDatabaseHelper extends SQLiteOpenHelper {
// ...existing methods...
// Insert a post into the database
public void addPost(Post post) {
// Create and/or open the database for writing
SQLiteDatabase db = getWritableDatabase();
// It's a good idea to wrap our insert in a transaction. This helps with performance and ensures
// consistency of the database.
db.beginTransaction();
try {
// The user might already exist in the database (i.e. the same user created multiple posts).
long userId = addOrUpdateUser(post.user);
ContentValues values = new ContentValues();
values.put(KEY_POST_USER_ID_FK, userId);
values.put(KEY_POST_TEXT, post.text);
// Notice how we haven't specified the primary key. SQLite auto increments the primary key column.
db.insertOrThrow(TABLE_POSTS, null, values);
db.setTransactionSuccessful();
} catch (Exception e) {
Log.d(TAG, "Error while trying to add post to database");
} finally {
db.endTransaction();
}
}
// Insert or update a user in the database
// Since SQLite doesn't support "upsert" we need to fall back on an attempt to UPDATE (in case the
// user already exists) optionally followed by an INSERT (in case the user does not already exist).
// Unfortunately, there is a bug with the insertOnConflict method
// (https://code.google.com/p/android/issues/detail?id=13045) so we need to fall back to the more
// verbose option of querying for the user's primary key if we did an update.
public long addOrUpdateUser(User user) {
// The database connection is cached so it's not expensive to call getWriteableDatabase() multiple times.
SQLiteDatabase db = getWritableDatabase();
long userId = -1;
db.beginTransaction();
try {
ContentValues values = new ContentValues();
values.put(KEY_USER_NAME, user.userName);
values.put(KEY_USER_PROFILE_PICTURE_URL, user.profilePictureUrl);
// First try to update the user in case the user already exists in the database
// This assumes userNames are unique
int rows = db.update(TABLE_USERS, values, KEY_USER_NAME + "= ?", new String[]{user.userName});
// Check if update succeeded
if (rows == 1) {
// Get the primary key of the user we just updated
String usersSelectQuery = String.format("SELECT %s FROM %s WHERE %s = ?",
KEY_USER_ID, TABLE_USERS, KEY_USER_NAME);
Cursor cursor = db.rawQuery(usersSelectQuery, new String[]{String.valueOf(user.userName)});
try {
if (cursor.moveToFirst()) {
userId = cursor.getInt(0);
db.setTransactionSuccessful();
}
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
} else {
// user with this userName did not already exist, so insert new user
userId = db.insertOrThrow(TABLE_USERS, null, values);
db.setTransactionSuccessful();
}
} catch (Exception e) {
Log.d(TAG, "Error while trying to add or update user");
} finally {
db.endTransaction();
}
return userId;
}
}
Note: If you are inserting a large number of records, you might want to use a compiled SQLiteStatement. You can read more about the performance benefits on this blog.
Querying Records
public class PostsDatabaseHelper extends SQLiteOpenHelper {
// ...existing methods...
public List<Post> getAllPosts() {
List<Post> posts = new ArrayList<>();
// SELECT * FROM POSTS
// LEFT OUTER JOIN USERS
// ON POSTS.KEY_POST_USER_ID_FK = USERS.KEY_USER_ID
String POSTS_SELECT_QUERY =
String.format("SELECT * FROM %s LEFT OUTER JOIN %s ON %s.%s = %s.%s",
TABLE_POSTS,
TABLE_USERS,
TABLE_POSTS, KEY_POST_USER_ID_FK,
TABLE_USERS, KEY_USER_ID);
// "getReadableDatabase()" and "getWriteableDatabase()" return the same object (except under low
// disk space scenarios)
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery(POSTS_SELECT_QUERY, null);
try {
if (cursor.moveToFirst()) {
do {
User newUser = new User();
newUser.userName = cursor.getString(cursor.getColumnIndex(KEY_USER_NAME));
newUser.profilePictureUrl = cursor.getString(cursor.getColumnIndex(KEY_USER_PROFILE_PICTURE_URL));
Post newPost = new Post();
newPost.text = cursor.getString(cursor.getColumnIndex(KEY_POST_TEXT));
newPost.user = newUser;
posts.add(newPost);
} while(cursor.moveToNext());
}
} catch (Exception e) {
Log.d(TAG, "Error while trying to get posts from database");
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
return posts;
}
}
Updating Records
public class PostsDatabaseHelper extends SQLiteOpenHelper {
// ...existing methods...
// Update the user's profile picture url
public int updateUserProfilePicture(User user) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_USER_PROFILE_PICTURE_URL, user.profilePictureUrl);
// Updating profile picture url for user with that userName
return db.update(TABLE_USERS, values, KEY_USER_NAME + " = ?",
new String[] { String.valueOf(user.userName) });
}
}
Deleting Records
public class PostsDatabaseHelper extends SQLiteOpenHelper {
// ...existing methods...
public void deleteAllPostsAndUsers() {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
try {
// Order of deletions is important when foreign key relationships exist.
db.delete(TABLE_POSTS, null, null);
db.delete(TABLE_USERS, null, null);
db.setTransactionSuccessful();
} catch (Exception e) {
Log.d(TAG, "Error while trying to delete all posts and users");
} finally {
db.endTransaction();
}
}
}
Using our Database Handler
We can now leverage our database handler and models to persist data to our SQLite store:
public class SQLiteExampleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite_example);
// Create sample data
User sampleUser = new User();
sampleUser.userName = "Steph";
sampleUser.profilePictureUrl = "https://i.imgur.com/tGbaZCY.jpg";
Post samplePost = new Post();
samplePost.user = sampleUser;
samplePost.text = "Won won!";
// Get singleton instance of database
PostsDatabaseHelper databaseHelper = PostsDatabaseHelper.getInstance(this);
// Add sample post to the database
databaseHelper.addPost(samplePost);
// Get all posts from database
List<Post> posts = databaseHelper.getAllPosts();
for (Post post : posts) {
// do something
}
}
}
Note: In many cases, rather than interacting with SQL directly, Android apps can leverage one of the many available higher-level ORMs (object relational mappers) to persist Java models to a database table instead.
Note: In many cases, rather than interacting with SQL directly, Android apps can leverage one of the many available higher-level ORMs (object relational mappers) to persist Java models to a database table instead.
Full Database Handler Source
The full source code for the database handler above can be found here for reference:
public class PostsDatabaseHelper extends SQLiteOpenHelper {
// Database Info
private static final String DATABASE_NAME = "postsDatabase";
private static final int DATABASE_VERSION = 1;
// Table Names
private static final String TABLE_POSTS = "posts";
private static final String TABLE_USERS = "users";
// Post Table Columns
private static final String KEY_POST_ID = "id";
private static final String KEY_POST_USER_ID_FK = "userId";
private static final String KEY_POST_TEXT = "text";
// User Table Columns
private static final String KEY_USER_ID = "id";
private static final String KEY_USER_NAME = "userName";
private static final String KEY_USER_PROFILE_PICTURE_URL = "profilePictureUrl";
private static PostsDatabaseHelper sInstance;
public static synchronized PostsDatabaseHelper getInstance(Context context) {
// Use the application context, which will ensure that you
// don't accidentally leak an Activity's context.
// See this article for more information: http://bit.ly/6LRzfx
if (sInstance == null) {
sInstance = new PostsDatabaseHelper(context.getApplicationContext());
}
return sInstance;
}
/**
* Constructor should be private to prevent direct instantiation.
* Make a call to the static method "getInstance()" instead.
*/
private PostsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// Called when the database connection is being configured.
// Configure database settings for things like foreign key support, write-ahead logging, etc.
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.setForeignKeyConstraintsEnabled(true);
}
// Called when the database is created for the FIRST time.
// If a database already exists on disk with the same DATABASE_NAME, this method will NOT be called.
@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_POSTS_TABLE = "CREATE TABLE " + TABLE_POSTS +
"(" +
KEY_POST_ID + " INTEGER PRIMARY KEY," + // Define a primary key
KEY_POST_USER_ID_FK + " INTEGER REFERENCES " + TABLE_USERS + "," + // Define a foreign key
KEY_POST_TEXT + " TEXT" +
")";
String CREATE_USERS_TABLE = "CREATE TABLE " + TABLE_USERS +
"(" +
KEY_USER_ID + " INTEGER PRIMARY KEY," +
KEY_USER_NAME + " TEXT," +
KEY_USER_PROFILE_PICTURE_URL + " TEXT" +
")";
db.execSQL(CREATE_POSTS_TABLE);
db.execSQL(CREATE_USERS_TABLE);
}
// Called when the database needs to be upgraded.
// This method will only be called if a database already exists on disk with the same DATABASE_NAME,
// but the DATABASE_VERSION is different than the version of the database that exists on disk.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
// Simplest implementation is to drop all old tables and recreate them
db.execSQL("DROP TABLE IF EXISTS " + TABLE_POSTS);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
onCreate(db);
}
}
// Insert a post into the database
public void addPost(Post post) {
// Create and/or open the database for writing
SQLiteDatabase db = getWritableDatabase();
// It's a good idea to wrap our insert in a transaction. This helps with performance and ensures
// consistency of the database.
db.beginTransaction();
try {
// The user might already exist in the database (i.e. the same user created multiple posts).
long userId = addOrUpdateUser(post.user);
ContentValues values = new ContentValues();
values.put(KEY_POST_USER_ID_FK, userId);
values.put(KEY_POST_TEXT, post.text);
// Notice how we haven't specified the primary key. SQLite auto increments the primary key column.
db.insertOrThrow(TABLE_POSTS, null, values);
db.setTransactionSuccessful();
} catch (Exception e) {
Log.d(TAG, "Error while trying to add post to database");
} finally {
db.endTransaction();
}
}
// Insert or update a user in the database
// Since SQLite doesn't support "upsert" we need to fall back on an attempt to UPDATE (in case the
// user already exists) optionally followed by an INSERT (in case the user does not already exist).
// Unfortunately, there is a bug with the insertOnConflict method
// (https://code.google.com/p/android/issues/detail?id=13045) so we need to fall back to the more
// verbose option of querying for the user's primary key if we did an update.
public long addOrUpdateUser(User user) {
// The database connection is cached so it's not expensive to call getWriteableDatabase() multiple times.
SQLiteDatabase db = getWritableDatabase();
long userId = -1;
db.beginTransaction();
try {
ContentValues values = new ContentValues();
values.put(KEY_USER_NAME, user.userName);
values.put(KEY_USER_PROFILE_PICTURE_URL, user.profilePictureUrl);
// First try to update the user in case the user already exists in the database
// This assumes userNames are unique
int rows = db.update(TABLE_USERS, values, KEY_USER_NAME + "= ?", new String[]{user.userName});
// Check if update succeeded
if (rows == 1) {
// Get the primary key of the user we just updated
String usersSelectQuery = String.format("SELECT %s FROM %s WHERE %s = ?",
KEY_USER_ID, TABLE_USERS, KEY_USER_NAME);
Cursor cursor = db.rawQuery(usersSelectQuery, new String[]{String.valueOf(user.userName)});
try {
if (cursor.moveToFirst()) {
userId = cursor.getInt(0);
db.setTransactionSuccessful();
}
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
} else {
// user with this userName did not already exist, so insert new user
userId = db.insertOrThrow(TABLE_USERS, null, values);
db.setTransactionSuccessful();
}
} catch (Exception e) {
Log.d(TAG, "Error while trying to add or update user");
} finally {
db.endTransaction();
}
return userId;
}
// Get all posts in the database
public List<Post> getAllPosts() {
List<Post> posts = new ArrayList<>();
// SELECT * FROM POSTS
// LEFT OUTER JOIN USERS
// ON POSTS.KEY_POST_USER_ID_FK = USERS.KEY_USER_ID
String POSTS_SELECT_QUERY =
String.format("SELECT * FROM %s LEFT OUTER JOIN %s ON %s.%s = %s.%s",
TABLE_POSTS,
TABLE_USERS,
TABLE_POSTS, KEY_POST_USER_ID_FK,
TABLE_USERS, KEY_USER_ID);
// "getReadableDatabase()" and "getWriteableDatabase()" return the same object (except under low
// disk space scenarios)
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery(POSTS_SELECT_QUERY, null);
try {
if (cursor.moveToFirst()) {
do {
User newUser = new User();
newUser.userName = cursor.getString(cursor.getColumnIndex(KEY_USER_NAME));
newUser.profilePictureUrl = cursor.getString(cursor.getColumnIndex(KEY_USER_PROFILE_PICTURE_URL));
Post newPost = new Post();
newPost.text = cursor.getString(cursor.getColumnIndex(KEY_POST_TEXT));
newPost.user = newUser;
posts.add(newPost);
} while(cursor.moveToNext());
}
} catch (Exception e) {
Log.d(TAG, "Error while trying to get posts from database");
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
return posts;
}
// Update the user's profile picture url
public int updateUserProfilePicture(User user) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_USER_PROFILE_PICTURE_URL, user.profilePictureUrl);
// Updating profile picture url for user with that userName
return db.update(TABLE_USERS, values, KEY_USER_NAME + " = ?",
new String[] { String.valueOf(user.userName) });
}
// Delete all posts and users in the database
public void deleteAllPostsAndUsers() {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
try {
// Order of deletions is important when foreign key relationships exist.
db.delete(TABLE_POSTS, null, null);
db.delete(TABLE_USERS, null, null);
db.setTransactionSuccessful();
} catch (Exception e) {
Log.d(TAG, "Error while trying to delete all posts and users");
} finally {
db.endTransaction();
}
}
}
SQLite Database Debugging
When working with SQLite, opening and inspecting the SQLite database can be helpful while debugging issues. You can leverage the Stetho library to view your data directly, or you can use the following command-line tools to retrieve the data.
The commands below will show how to get at the data (whether running on an emulator or an actual device). The commands should be performed within the terminal or command-line. Once you have the data, there are desktop SQLite viewers to help inspect the SQLite data graphically.
On an Emulator
Use SQLite3 to query the data on the emulator:
cd /path/to/my/sdk/platform-tools
./adb shell
run-as <app package name>
cd /data/data/<app package name>/databases
ls
chmod 666 <database file name>
sqlite3 <database file name>
> (semi-colon terminated commands can be run here to query the data)
> .exit
(copy full database path)
exit
For further inspection, we can download the database file with:
./adb wait-for-device pull /data/data/<app package name>/databases/<database file name>
On a Device
There isn't a SQLite3 executable on the device so our only option is to download the database file with:
./adb shell run-as <app package name> chmod 666 /data/data/<app package name>/databases/<database file name>
./adb shell cp /data/data/<app package name>/databases/<database file name> /sdcard/
./adb pull /sdcard/<database file name>
References
Local Databases with SQLiteOpenHelper的更多相关文章
- Persisting Data to the Device
Persisting Data to the Device Edit PagePage History Overview The Android framework offers several op ...
- MongoDB 安全和访问权限控制
MongoDB的访问控制能够有效保证数据库的安全,访问控制是指绑定Application监听的IP地址,设置监听端口,使用账户和密码登录 一,访问控制的参数 1,绑定IP地址 mongod 参数:-- ...
- MongoDB的内置角色 Built-In Roles
关于芒果的权限控制说白了就是定义 Role(角色) 来控制对数据库进行的操作(调用的方法比如查询方法find). 系统内置的Role分为 以下几大类: Database User Roles 这个是针 ...
- pouchdb Conflicts
Conflicts are an unavoidable reality when dealing with distributed systems. And make no mistake: cli ...
- pouchdb sync
PouchDB and CouchDB were designed for one main purpose: sync. Jason Smith has a great quote about th ...
- DB2调优摘要
感谢原博主 http://yishueitian326.blog.163.com/blog/static/2858637520106695733719/ 性 能是关系到随需应变型应用程序成功与否的关键 ...
- mongoDB基础使用
环境交代 操作系统: CentOS 6.8 64位 mongodb: 4.06 安装 官方下载地址:https://www.mongodb.org/dl/linux/x86_64-rhel62 阿里云 ...
- MongoDB 设置账号和密码
一.安装MongoDB 1.环境配置: i.操作系统:CentOS release 6.8 (Final) [root@iZ2ze2pbbffhmn53ao4tuaZ bin]# cat /etc/r ...
- (转)调优 DB2 UDB v8.1 及其数据库的最佳实践
原文:https://www.ibm.com/developerworks/cn/data/library/techarticles/dm-0404mcarthur/index.html 简介 性能是 ...
随机推荐
- Python补充--Python内置函数清单
Python内置函数 Python内置(built-in)函数随着python解释器的运行而创建.在Python的程序中,你可以随时调用这些函数,不需要定义.最常见的内置函数是: print(&quo ...
- POJ 2115 C Looooops【数论】
很容易看出来一个同余式,说到底是解一个线性同余方程,计算机解通常有拓展欧几里得和欧拉定理两种算法,参照去年的NOIP水题,问题是这题数据范围是2^32所以要int64 TAT #include< ...
- MS SQLServer Update语法和实例
最常用的update语法是: 1 2 UPDATE TABLE_NAME SET column_name1 = VALUE WHRER column_name2 = VALUE 如果我的更新值Valu ...
- HDU 2352 Verdis Quo
罗马数字转化为十进制的值 题目非常的长 提取有效信息 并且介绍很多规则 但是事实上有用的信息就是如何加 什么时候减 当当前字母小于下一个字母时 减去当前字母的值 #include <iostre ...
- MySQL性能优化的21个最佳实践 和 mysql使用索引【转载】
今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我 们程序员需要去关注的事情.当我们去设计数据库表结构,对操作数 ...
- 前端学习之- Ajax
Ajax:页面不做刷新,直接将数据悄悄提交到后台,然后通过回调函数处理返回结果. $.Ajax({ # 提交到后台 url:'/host', # 提交到哪里 type:'POST' # 提交方式 da ...
- service mesh架构
service mesh 系列文章 https://my.oschina.net/iamlipeng/blog/1631575 http://developer.51cto.com/art/2018 ...
- how to read openstack code: loading process
之前我们了解了neutron的结构,plugin 和 extension等信息.这一章我们看一下neutron如何加载这些plugin和extension.也就是neutron的启动过程.本文涉及的代 ...
- ThoughtWorks技术雷达
ThoughtWorks技术雷达 技术成熟方案的一个推荐网站.
- [Hibernate Search] (3) 基础查询
基础查询 眼下我们仅仅用到了基于keyword的查询,实际上Hibenrate Search DSL还提供了其他的查询方式,以下我们就来一探到底. 映射API和查询API 对于映射API.我们能够通过 ...