ContentProvider详细介绍(附实例源码)
来源:互联网 发布:三维动画制作软件 编辑:程序博客网 时间:2024/05/16 10:22
1.ContentProvider是什么?
ContentProvider——内容提供者。它是一个类,这个类主要是对Android系统中进行共享的数据进行包装,并提供了一组统一的访问接口供其他程序调用。这些被共享的数据,可以使系统自己的也可以使我们个人应用程序中的数据,ContentProvider使用表的形式来组织数据.
2.为什么要有ContentProvider?
在Android中,数据的存储有很多种方式,最常用的就是SQLite和XML文件方式。在不同的应用程序间,其实数据是不能直接被相互访问和操作的,在这种情况下,ContentProvider很好的被用来解决了不同应用程序间数据共享的问题。
其实在Android系统中,已经为我们提供了许多ContentProvider,如:Contacts、Browser、CallLog、 Settings等等。那么,Android系统中提供了这么多的ContentProvider,另外还有我们自己公开的共享数据,我们在写程序的时 候,怎么才能让我们的应用程序知道去哪儿取、如何取这些数据呢?我们自然的会想到URI。
一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。
也就是说,一个程序可以通过实现一个Content Provider的抽象接口将自己的数据暴露出去。
外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,
重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据
3.URI是什么?
URI(Uniform Resource Identifier)——统一资源定位符,URI在ContentProvider中代表了要操做的数据。
在Android系统中通常的URI格式为:content://com.wirelessqa.content.provider/profile/10
在万维网访问时通常用的URI格式为:http://www.XXXX.com/AAA/123
- content://——schema,这个是Android中已经定义好的一个标准。我个人一直认为这和我们的http://有异曲同工之妙,都是代表的协议。ContentProvider(内容提供者)的scheme已经由Android所规定为:content://
- com.wirelessqa.content.provider——authority(主机名),用于唯一标识这个ContentProvider,外部调用者通过这个authority来找到它。相当于www.XXXX.com, 代表的是我们ContentProvider所在的”域名”,这个”域名”在我们Android中一定要是唯一的,否则系统怎么能知道该找哪一个 Provider呢?所以一般情况下,建议采用完整的包名加类名来标识这个ContentProvider的authority。
- /profile/10——路径,用来标识我们要操作的数据。/profile/10表示的意思是——找到profile中id为10的记录。其实这个相当于/AAA/123。
【扩展阅读】
1.要操作profile表中id为10的记录,可以构建这样的路径:/profile/10
2.要操作profile表中id为10的记录的name字段, profile/10/name
3.要操作profile表中的所有记录,可以构建这样的路径:/profile
4.要操作xxx表中的记录,可以构建这样的路径:/xxx
5.当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:要操作xml文件中profile节点下的name节点,可以构建这样的路径:/profile/name
6.如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:Uri uri = Uri.parse(“content://com.wirelessqa.content.provider/pofile”)
综上所述,content://com.wirelessqa.content.provider/profile/10/User 所代表的URI的意思为:标识com.wirelessqa.content.provider中proifle表中_ID为10的User项。
4.URI常用方法有哪些?
UriMatcher:用于匹配Uri,它的用法如下:
1. 首先把你需要匹配Uri路径全部给注册上,如下:
1
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。
2
UriMatcher uriMatcher =
new
UriMatcher(UriMatcher.NO_MATCH);
3
//如果match()方法匹配content://com.wirelessqa.content.provider/profile路径,返回匹配码为1
4
uriMatcher.addURI(“com.wirelessqa.content.provider”, “profile”,
1
);
//添加需要匹配uri,如果匹配就会返回匹配码
5
//如果match()方法匹配 content://com.wirelessqa.content.provider/profile/路径,返回匹配码为2
6
uriMatcher.addURI(“com.wirelessqa.content.provider”, “profile/#”,
2
);
//#号为通配符
2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用 addURI()方法传入的第三个参数,假设匹配 content://com.wirelessqa.content.provider/profile路径,返回的匹配 码为1。
ContentUris:用于获取Uri路径后面的ID部分,它有两个比较实用的方法:
1
withAppendedId(uri, id)
// 用于为路径加上ID部分
2
parseId(uri)
//用于从路径中获取ID部分
ContentResolver:当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。
5. ContentProvider中公开的几个方法
- public boolean onCreate():该方法在ContentProvider创建后就会被调用,Android系统运行后,ContentProvider只有在被第一次使用它时才会被创建。
- public Uri insert(Uri uri, ContentValues values):外部应用程序通过这个方法向 ContentProvider添加数据。
- uri—— 标识操作数据的URI
- values—— 需要添加数据的键值对
- public int delete(Uri uri, String selection, String[] selectionArgs):外部应用程序通过这个方法从 ContentProvider中删除数据。
- uri——标识操作数据的URI
- selection——构成筛选添加的语句,如”id=1″ 或者 “id=?”
- selectionArgs——对应selection的两种情况可以传入null 或者 new String[]{“1″}
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):外部应用程序通过这个方法对 ContentProvider中的数据进行更新。
- values——对应需要更新的键值对,键为对应共享数据中的字段,值为对应的修改值
- 其余参数同delete方法
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):外部应用程序通过这个方法从ContentProvider中获取数据,并返回一个Cursor对象。
- projection——需要从Contentprovider中选择的字段,如果为空,则返回的Cursor将包含所有的字段。
- sortOrder——默认的排序规则
- 其余参数同delete方法
- public String getType(Uri uri):该方法用于返回当前Url所代表数据的MIME类型。
如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,
例如:要得到所有person记录的Uri为content://com.wirelessqa.content.provider/profile,那么返回的MIME类型字符串应该为:”vnd.android.cursor.dir/profile”。
如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,
例如:得到id为10的person记录,Uri为content://com.wirelessqa.content.provider/profile/10,那么返回的MIME类型字符串为:”vnd.android.cursor.item/profile”。
6.实现步骤
想要自己的程序里面的数据能够被其他程序所访问到,有以下步骤:
第一:首先生成一个继承contentprovider的类.
第二:在androidMainfest.xml里面添加一个provider的标签就可以了.
1
<provider android:name=
"MyProvider"
android:authorities=
"com.wirelessqa.content.provider"
/>
是不是很简单?其他程序访问的时候只要按以下步骤就可以访问到了:
1
Uri uri=Uri.Uri.parse(
"content://"
+AUTHORY+
"/profile"
);
AUTHORY其实就是 android:authorities的值.,注意.这里必须一样..否则系统是找不到的.也是就是 String AUTHORY=”content://com.wirelessqa.content.provider”
然后获取一个 ContentResolver mContentResolver=getContentResolver();这样就其他程序就可以反问我们的数据了
ContentResolver对应的几个方法:
- query(Uri, String[], String, String[], String) which returns data to the caller
- insert(Uri, ContentValues) which inserts new data into the content provider
- update(Uri, ContentValues, String, String[]) which updates existing data in the content provider
- delete(Uri, String, String[]) which deletes data from the content provider
其实和contentprovider里面的方法是一样的..他们所对应的数据,最终是会被传到我们在之前程序里面定义的那个contentprovider类的方法,至于你想要在这几个方法里面做什么事,随你
7.实例讲解
AndroidManifest.xml
在AndroidManifest.xml的<application>和</application>之间加入:
1
<provider android:name=
"MyProvider"
android:authorities=
"com.wirelessqa.content.provider"
/>
Profile.java
01
package com.wirlessqa.content.provider;
02
03
import android.net.Uri;
04
05
/**
06
* Profile类用于存放各种常量
07
*
08
* @author www.wirelessqa.com 2013-2-26 下午11:01:46
09
*/
10
public class Profile {
11
12
public static final String TABLE_NAME = "profile"; // 表格名称
13
14
public static final String COLUMN_ID = "_id"; // 列表一,_ID,自动增加
15
16
public static final String COLUMN_NAME = "name"; // 列表二,名称
17
18
public static final String AUTOHORITY = "com.wirlessqa.content.provider";
19
public static final int ITEM = 1;
20
public static final int ITEM_ID = 2;
21
22
// 如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,
23
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/profile";
24
// 如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头
25
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/profile";
26
27
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/profile");
28
}
DBHelper.java
01
package
com.wirlessqa.content.provider;
02
03
import
android.content.Context;
04
import
android.database.SQLException;
05
import
android.database.sqlite.SQLiteDatabase;
06
import
android.database.sqlite.SQLiteOpenHelper;
07
08
/**
09
* 定义一个数据库类
10
*
11
* @author www.wirelessqa.com 2013-2-26 下午11:01:46
12
*/
13
public
class
DBHelper
extends
SQLiteOpenHelper {
14
15
private
static
final
String DATABASE_NAME =
"wirelessqa.db"
;
// 数据库名
16
17
private
static
final
int
DATABASE_VERSION =
1
;
// 版本号
18
19
public
DBHelper(Context context){
20
super
(context, DATABASE_NAME,
null
, DATABASE_VERSION);
21
}
22
23
@Override
24
public
void
onCreate(SQLiteDatabase db)
throws
SQLException {
25
// 创建的数据表中必须含有"_id"这个字段,这个字段是自增长的,插入的时候不用管这个字段,数据库会自己递增地加上
26
db.execSQL(
"CREATE TABLE IF NOT EXISTS "
+ Profile.TABLE_NAME +
"("
+ Profile.COLUMN_ID
27
+
" INTEGER PRIMARY KEY AUTOINCREMENT,"
+ Profile.COLUMN_NAME +
" VARCHAR NOT NULL);"
);
28
}
29
30
@Override
31
public
void
onUpgrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion)
throws
SQLException {
32
// 删除并创建表格
33
db.execSQL(
"DROP TABLE IF EXISTS "
+ Profile.TABLE_NAME +
";"
);
34
onCreate(db);
35
}
36
}
MyProvider.java
001
package
com.wirlessqa.content.provider;
002
003
import
android.content.ContentProvider;
004
import
android.content.ContentUris;
005
import
android.content.ContentValues;
006
import
android.content.UriMatcher;
007
import
android.database.Cursor;
008
import
android.database.SQLException;
009
import
android.database.sqlite.SQLiteDatabase;
010
import
android.net.Uri;
011
012
/**
013
* contentprovider的调用者有可能是Activity,Service,Application这3种context,被谁调用,getContext就是谁
014
* @author www.wirelessqa.com 2013-2-26 下午11:01:46
015
*/
016
public
class
MyProvider
extends
ContentProvider {
017
018
DBHelper mDbHelper =
null
;
019
SQLiteDatabase db =
null
;
020
021
private
static
final
UriMatcher mMatcher;
022
// 1.第一步把你需要匹配Uri路径全部给注册上
023
static
{
024
// UriMatcher:用于匹配Uri
025
// 常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。
026
mMatcher =
new
UriMatcher(UriMatcher.NO_MATCH);
027
// 如果match()方法匹配content://com.wirlessqa.content.provider/profile路径,返回匹配码为1
028
mMatcher.addURI(Profile.AUTOHORITY, Profile.TABLE_NAME, Profile.ITEM);
// 添加需要匹配uri,如果匹配就会返回匹配码
029
// 如果match()方法匹配 content://com.wirlessqa.content.provider/profile/#路径,返回匹配码为2
030
mMatcher.addURI(Profile.AUTOHORITY, Profile.TABLE_NAME +
"/#"
, Profile.ITEM_ID);
// #号为通配符
031
032
// 注册完需要匹配的Uri后,就可以使用mMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,
033
// 匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.wirelessqa.content.provider/profile路径,返回的匹配码为1
034
}
035
036
// onCreate()方法在ContentProvider创建后就会被调用,Android系统运行后,ContentProvider只有在被第一次使用它时才会被创建。
037
@Override
038
public
boolean
onCreate() {
039
mDbHelper =
new
DBHelper(getContext());
040
041
db = mDbHelper.getReadableDatabase();
042
043
return
true
;
044
}
045
046
047
@Override
048
public
int
delete(Uri uri, String selection, String[] selectionArgs) {
049
long
rowId;
050
if
(mMatcher.match(uri) != Profile.ITEM) {
051
throw
new
IllegalArgumentException(
"Unknown URI"
+ uri);
052
}
053
rowId = db.delete(Profile.TABLE_NAME, selection, selectionArgs);
054
// if(rowId>0){
055
// Uri noteUri = ContentUris.withAppendedId(Profile.CONTENT_URI, rowId);
056
// getContext().getContentResolver().notifyChange(noteUri, null);
057
// }
058
return
0
;
059
}
060
061
@Override
062
public
String getType(Uri uri) {
063
switch
(mMatcher.match(uri)) {
064
case
Profile.ITEM:
065
return
Profile.CONTENT_TYPE;
066
case
Profile.ITEM_ID:
067
return
Profile.CONTENT_ITEM_TYPE;
068
default
:
069
throw
new
IllegalArgumentException(
"Unknown URI"
+ uri);
070
}
071
}
072
073
// 外部应用程序通过这个方法向 ContentProvider添加数据。
074
@Override
075
public
Uri insert(Uri uri, ContentValues values) {
076
long
rowId;
077
// mMatcher.match(uri)对输入的Uri进行匹配,如果匹配就返回匹配码
078
if
(mMatcher.match(uri) != Profile.ITEM) {
079
throw
new
IllegalArgumentException(
"Unknown URI"
+ uri);
080
}
081
rowId = db.insert(Profile.TABLE_NAME,
null
, values);
//向数据库里插入数据
082
if
(rowId >
0
) {
083
// ContentUris.withAoppendedId 用于为路径加上ID部分
084
Uri noteUri = ContentUris.withAppendedId(Profile.CONTENT_URI, rowId);
085
// 当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成
086
//ContentResolver是属于context的,通过getContentResolver获取
087
//ContentResolver可以通过registerContentObserver注册观察者(观察者是ContentObserver 的派生类)
088
//一旦ContentProvider操作的数据变化后,调用ContentResolver的notifyChange方法即可通知到观察者(回调观察者的onChange方法)
089
//注册观察者不是必须的,所有notifyChange不是必须调用的
090
getContext().getContentResolver().notifyChange(noteUri,
null
);
091
return
noteUri;
092
}
093
094
throw
new
SQLException(
"Failed to insert row into "
+ uri);
095
}
096
097
@Override
098
public
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
099
Cursor c =
null
;
100
switch
(mMatcher.match(uri)) {
101
case
Profile.ITEM:
102
c = db.query(Profile.TABLE_NAME, projection, selection, selectionArgs,
null
,
null
, sortOrder);
103
break
;
104
case
Profile.ITEM_ID:
105
c = db.query(Profile.TABLE_NAME, projection, Profile.COLUMN_ID +
"="
+ uri.getLastPathSegment(),
106
selectionArgs,
null
,
null
, sortOrder);
107
break
;
108
default
:
109
throw
new
IllegalArgumentException(
"Unknown URI"
+ uri);
110
}
111
//从而在ContentService中注册contentservice的观察者,这个观察者是cursor的内部成员(cursor是一个接口,此处真正的cursor是sqlitecursor)
112
//这样每个查询返回的cursor都能在contentprovider对应数据改变时得到通知,因为这些cursor都有一个成员注册成了contentservice的观察者
113
c.setNotificationUri(getContext().getContentResolver(), uri);
114
return
c;
115
}
116
117
@Override
118
public
int
update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
119
// TODO Auto-generated method stub
120
return
0
;
121
}
122
123
}
定义ContentProvider的子类
1. contentprovider的调用者有可能是Activity,Service,Application这3种context,被谁调用getContext就是谁
2. ContentResolver是属于context的,通过getContentResolver获取,ContentResolver可以通过registerContentObserver注册观察者(观察者是ContentObserver 的派生类),一旦ContentProvider操作的数据变化后,调用ContentResolver的notifyChange方法即可通知到观察者(回调观察者的onChange方法),注册观察者不是必须的,所有notifyChange不是必须调用的
3. ContentValues 一次只能放入一行数据(可以使多个字段,即多个名值对)
4. onCreate只在ContentProvider第一次被调用的时候调用,多次调用共享的是一个ContentProvider
5. Cursor即数据库查询结果的操作游标,用户随机访问查询结果,获取查询结果的数目等
6. 在Content Resolver中有几个需要注意的接口:
notifyChange (Uri uri, ContentObserver observer, boolean syncToNetwork);
registerContentObserver (Uri uri, boolean notifyForDescendents, ContentObserver observer);
unregisterContentObserver (ContentObserver observer);
7.在Cursor中也有几个类似的接口:
setNotificationUri (ContentResolver cr, Uri uri);
registerContentObserver (ContentObserver observer);
unregisterContentObserver (ContentObserver observer);
8.要在query中调用
setNotificationUri(ContentResolver cr, Uri notifyUri)从而在ContentService中注册contentservice的观察者,这个观察者是cursor的内部成员(cursor是一个接口,此处真正的cursor是sqlitecursor),这样每个查询返回的cursor都能在contentprovider对应数据改变时得到通知,因为这些cursor都有一个成员注册成了contentservice的观察者
那么数据改变时,是怎么通知这些cursor的观察者成员呢?
这就需要在delete,update,insert这些改变数据的方法中调用contentresolver的notifychange方法,这个notifychange实际调用的是contentservice的notifychange,在这个notifychange方法里,contentservice查找所有在其中注册的观察者,找出对这次更新数据感兴趣的观察者(通过uri),然后通知它们数据改变
contentservice有很多观察者,它是系统服务,管理系统中所有contentprovider,通过uri匹配查找观察者,通知符合要求的观察者(实际就是通知对应的cursor)
cursor得到通知以后做些什么呢?
事实上,cursor也可以有很多观察者,因为一个查询出来的结果集可能会被多个地方使用(比如多个listview使用一个cursor),cursor对应的数据改变的时候,它也会通知到所有关注它的观察者(调用它们的onchange)
那么,cursor的观察者是怎么注册进去的呢?
是通过cursor的registerContentObserver这个方法注册进去的
以下例作为解析,在simplecursoradapter(继承自cursoradapter)的构造函数中,调用父类cursoradapter的构造函数,在这个构造函数里,
调用了cursor的registerContentObserver,把一个继承自contentobserver的成员对象作为观察者注册进了cursor的观察者里。这样cursor变化了,就会通知simplecursoradapter,simplecursoradapter里就可以重新查询结果并显示在listview中
以上其实就是两个观察者模式,cursor观察contentservice,同时cursor又被cursoradapter观察(都是通过其成员变量观察,不是直接观察),我们也可以通过contentservice和cursoradapter提供的接口注册我们自己的观察者,也就是说contentservice的观察者可以不是cursor,cursor的观察者可以不是cursoradapter
MainActivity.java
01
package
com.wirlessqa.content.provider;
02
03
import
android.app.ListActivity;
04
import
android.content.ContentResolver;
05
import
android.content.ContentValues;
06
import
android.database.Cursor;
07
import
android.os.Bundle;
08
import
android.widget.SimpleCursorAdapter;
09
10
/**
11
* @author www.wirelessqa.com 2013-2-26 下午11:00:29
12
*/
13
public
class
MainActivity
extends
ListActivity {
14
15
private
SimpleCursorAdapter adapter =
null
;
16
private
Cursor mCursor =
null
;
17
private
ContentResolver mContentResolver =
null
;
18
19
@Override
20
public
void
onCreate(Bundle savedInstanceState) {
21
super
.onCreate(savedInstanceState);
22
initData();
23
initAdapter();
24
}
25
26
public
void
initData() {
27
mContentResolver = getContentResolver();
28
// 删除一条记录可以用下面的方法
29
// String where = "_id = '1'";
30
// mContentResolver.delete(Profile.CONTENT_URI, where, null);
31
// 填充数据
32
for
(
int
i =
0
; i <
20
; i++) {
33
// ContentValues 和HashTable类似都是一种存储的机制 但是两者最大的区别就在于
34
// contenvalues只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西
35
ContentValues values =
new
ContentValues();
36
values.put(Profile.COLUMN_NAME, i +
" 网址:www.wirelessqa.com"
);
37
// 通过ContentResolver来向数据库插入数据
38
mContentResolver.insert(Profile.CONTENT_URI, values);
39
}
40
}
41
42
public
void
initAdapter() {
43
// 查询表格,并获得Cursor
44
// 查询全部数据
45
mCursor = mContentResolver.query(Profile.CONTENT_URI,
new
String[] { Profile.COLUMN_ID, Profile.COLUMN_NAME },
46
null
,
null
,
null
);
47
48
// 查询部分数据
49
// String selection = Profile.COLUMN_ID + " LIKE '%1'";
50
// mCursor = mContentResolver.query(Profile.CONTENT_URI, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME},
51
// selection, null, null);
52
53
// 查询一个数据
54
// Uri uri = ContentUris.withAppendedId(Profile.CONTENT_URI, 50);
55
// mCursor = mContentResolver.query(uri, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, null, null, null);
56
57
startManagingCursor(mCursor);
58
59
// 设置adapter
60
adapter =
new
SimpleCursorAdapter(
this
, android.R.layout.simple_list_item_2, mCursor,
new
String[] {
61
Profile.COLUMN_ID, Profile.COLUMN_NAME },
new
int
[] { android.R.id.text1, android.R.id.text2 });
62
setListAdapter(adapter);
63
}
64
65
}
此文参考多份网络上的文章
源码下载:http://download.csdn.net/detail/wirelessqa/5091983
本文链接:【Android数据存储】ContentProvider详细介绍(附实例源码)
转载声明:本站文章若无特别说明,皆为原创,转载请注明来源:WirelessQA,谢谢!^^
- ContentProvider详细介绍(附实例源码)
- ContentProvider详细介绍(附实例源码)
- 【Android数据存储】ContentProvider详细介绍(附实例源码)
- ContentProvider数据共享(附源码)
- Android ContentProvider 详细介绍
- ContentProvider详细介绍
- 初步理解ContentProvider的工作原理(附简单实例)
- Spring中@Transactional事务回滚(含实例详细讲解,附源码)
- 微信开发获取地理位置实例(java,非常详细,附工程源码)
- 微信开发获取地理位置实例(java,非常详细,附工程源码)
- Spring中@Transactional事务回滚(含实例详细讲解,附源码)
- Spring中@Transactional事务回滚(含实例详细讲解,附源码)
- Spring中@Transactional事务回滚(含实例详细讲解,附源码)
- 微信开发获取地理位置实例(java,非常详细,附工程源码)
- Spring中@Transactional事务回滚(含实例详细讲解,附源码)
- 微信开发获取地理位置实例(java,非常详细,附工程源码)
- Spring中@Transactional事务回滚(含实例详细讲解,附源码)
- Spring中@Transactional事务回滚(含实例详细讲解,附源码)
- java 遍历resultset报错: peration not allowed after ResultSet closed【整理】
- fastdfs分布式文件系统之Storage server介绍
- Centos6.5下配置NFS存储
- 使用 Chef 自动化部署一个 Web 服务器
- 日期组件的实现
- ContentProvider详细介绍(附实例源码)
- leetcode No33. Search in Rotated Sorted Array
- nodejs webSocket 压测工具
- Object Pooling
- 基于keepalived的nginx高可用方案
- leetcode09-Palindrome Number之Java版本
- JsonP跨域请求
- TCP的连接管理
- 资源监控工具Munin