自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Google App Engine的Java持久性與數(shù)據(jù)存儲(chǔ)

開發(fā) 開發(fā)工具
本文是Rick Hightower的Google App Engine介紹系列的第三部分,他將在這篇文章中講述有關(guān)持久層和關(guān)系中一些需要學(xué)習(xí)的知識(shí)。

本文繼續(xù)介紹Google App Engine for Java。這篇講述持久性和關(guān)系。

Google App Engine for Java 力求為可伸縮的 Web 應(yīng)用程序成功地編寫一個(gè)持久層,可這個(gè)目標(biāo)的達(dá)成情況又如何呢?在本文中,我將概述 App Engine for Java 的持久性框架,從而結(jié)束本系列文章。該框架以 Java Data Objects(JDO)和 Java Persistence API(JPA)為基礎(chǔ)。盡管在剛剛出現(xiàn)時(shí)前景良好,但是 App Engine 的基于 Java 的持久性目前存在一些嚴(yán)重的缺陷,我將對(duì)此進(jìn)行解釋和演示。您將學(xué)習(xí) App Engine for Java 持久性是如何運(yùn)作以及有著哪些挑戰(zhàn),還將學(xué)習(xí)在使用面向 Java 開發(fā)人員的 Google 云平臺(tái)時(shí),您具有哪些持久性選擇。

在閱讀本文并遍覽這些示例時(shí),您要牢記這樣的事實(shí):現(xiàn)在的 App Engine for Java 是一個(gè)預(yù)覽 版。基于 Java 的持久性目前也許并不是您所希望或者需要的全部,可能并且應(yīng)該會(huì)在未來發(fā)生變化?,F(xiàn)如今,使用 App Engine for Java 進(jìn)行可伸縮的、數(shù)據(jù)密集型的 Java 應(yīng)用程序開發(fā)不合適膽小者或者保守派,這就是我在撰寫本文時(shí)所學(xué)到的。這更像跳入了游泳池的最深處:看不到任何救生員,項(xiàng)目要沉下去還是往前游,取決于您自己。

注意,本文中的示例應(yīng)用程序以 第 2 部分 中開發(fā)的聯(lián)系人管理應(yīng)用程序?yàn)榛A(chǔ)。您需要構(gòu)建該應(yīng)用程序,確保它是可運(yùn)行的,這樣才能繼續(xù)學(xué)習(xí)本文的示例。

基礎(chǔ)知識(shí)和抽象泄漏(leaky abstraction)

與原始的 Google App Engine 一樣,App Engine for Java 依靠 Google 的內(nèi)部基礎(chǔ)設(shè)施,實(shí)現(xiàn)可伸縮的應(yīng)用程序開發(fā)的 Big Three:分布、復(fù)制和負(fù)載均衡。由于使用的是 Google 基礎(chǔ)設(shè)施,因此所有神奇的地方大都發(fā)生在后臺(tái),可以通過 App Engine for Java 的基于標(biāo)準(zhǔn)的 API 獲得。數(shù)據(jù)存儲(chǔ)接口是以 JDO 和 JPA 為基礎(chǔ)的,而它們自身又是以開源的 DataNucleus 項(xiàng)目為基礎(chǔ)。AppEngine for Java 還提供了一個(gè)低級(jí)別的適配器 API,用來直接處理基于 Google 的 BigTable 實(shí)現(xiàn)的 App Engine for Java 數(shù)據(jù)存儲(chǔ)(要了解更多有關(guān) BigTable 的信息,請(qǐng)參見 第 1 部分)。

然而,App Engine for Java 數(shù)據(jù)持久性并不像純 Google App Engine 中的持久性那樣簡(jiǎn)單。由于 BigTable 不是一個(gè)關(guān)系數(shù)據(jù)庫,JDO 和 JPA 的接口出現(xiàn)了一些抽象泄漏。例如,在 App Engine for Java 中,您無法進(jìn)行那些執(zhí)行連接的查詢。您可以在 JPA 和 JDO 間設(shè)置關(guān)系,但它們只能用來持久化關(guān)系。并且在持久化對(duì)象時(shí),如果它們?cè)谙嗤膶?shí)體群中,那么它們只能被持久化到相同的原子事務(wù)中。根據(jù)慣例,具有所有權(quán)的關(guān)系位于與父類相同的實(shí)體群中。相反,不具有所有權(quán)的關(guān)系可以在不同的實(shí)體群中。

重新考慮數(shù)據(jù)規(guī)范化

要使用 App Engine 的可伸縮的數(shù)據(jù)存儲(chǔ),需要重新考慮有關(guān)規(guī)范化數(shù)據(jù)的優(yōu)點(diǎn)的教導(dǎo)。當(dāng)然,如果您在真實(shí)的環(huán)境中工作了足夠長(zhǎng)的時(shí)間,那么,您可能已經(jīng)為了追求性能而犧牲過規(guī)范化了。區(qū)別在于,在處理 App Engine 數(shù)據(jù)存儲(chǔ)時(shí),您必須盡早且經(jīng)常進(jìn)行反規(guī)范化。反規(guī)范化 不再是一個(gè)忌諱的字眼,相反,它是一個(gè)設(shè)計(jì)工具,您可以把它應(yīng)用在 App Engine for Java 應(yīng)用程序的許多方面。

當(dāng)您嘗試把為 RDBMS 編寫的應(yīng)用程序移植到 App Engine for Java 時(shí),App Engine for Java 的持久性泄漏的主要缺陷就會(huì)顯露出來。App Engine for Java 數(shù)據(jù)存儲(chǔ)并不是關(guān)系數(shù)據(jù)庫的臨時(shí)替代物,因此,要把您對(duì) App Engine for Java 所做的工作移植到 RDBMS 端口并不容易。采用現(xiàn)有的模式并把它移植到數(shù)據(jù)存儲(chǔ)中,這種場(chǎng)景則更為少見。如果您決定把一個(gè)遺留的 Java 企業(yè)應(yīng)用程序移植到 App 引擎中,建議您要小心謹(jǐn)慎,并進(jìn)行備份分析。Google App Engine 是一個(gè)針對(duì)專門為它設(shè)計(jì)的應(yīng)用程序的平臺(tái)。Google App Engine for Java 支持 JDO 和 JPA,這使得這些應(yīng)用程序能夠被移植回更傳統(tǒng)的、未進(jìn)行規(guī)范化的企業(yè)應(yīng)用程序。

關(guān)系的問題

App Engine for Java 目前的預(yù)覽版的另外一個(gè)缺點(diǎn)是它對(duì)關(guān)系的處理。為了創(chuàng)建關(guān)系,現(xiàn)在您必須對(duì) JDO 使用 App Engine for Java 特有的擴(kuò)展。假設(shè)鍵是在 BigTable 的工件的基礎(chǔ)上生成 — 也就是說,“主鍵” 將父對(duì)象鍵編碼到其所有子鍵中 — 您將不得不在一個(gè)非關(guān)系數(shù)據(jù)庫中管理數(shù)據(jù)。另外一個(gè)限制是持久化數(shù)據(jù)。如果您使用非標(biāo)準(zhǔn)的 AppEngine for Java Key 類,事情將會(huì)變得復(fù)雜。首先,把模型移植到 RDBMS 時(shí),如何使用非標(biāo)準(zhǔn) Key? 其次,由于無法使用 GWT 引擎轉(zhuǎn)換 Key 類,因此,任何使用這個(gè)類的模型對(duì)象都無法被作為 GWT 應(yīng)用程序的一部分進(jìn)行使用。

當(dāng)然,撰寫這篇文章時(shí),Google App Engine for Java 還是純粹的預(yù)覽模式,沒有到發(fā)布的最佳時(shí)間。學(xué)習(xí) JDO 中的關(guān)系文檔(很少,而且包含一些不完整的示例)時(shí),這點(diǎn)就變得顯而易見了。

App Engine for Java 開發(fā)包提供了一系列的示例程序。許多示例都使用 JDO,沒有一個(gè)使用 JPA。這些示例中沒有一個(gè)示例(包括一個(gè)名為 jdoexamples 的示例)演示了關(guān)系,即使是簡(jiǎn)單的關(guān)系。相反,所有的示例都只使用一個(gè)對(duì)象把數(shù)據(jù)保存到數(shù)據(jù)存儲(chǔ)中。Google App Engine for Java 討論組 充斥著有關(guān)如何使簡(jiǎn)單關(guān)系起作用的問題,但卻鮮有答案。很顯然,有些開發(fā)人員有辦法使其起作用,但是實(shí)現(xiàn)起來都很困難,而且遇到了一些復(fù)雜情況。

App Engine for Java 中的關(guān)系的底線是,無需從 JDO 或 JPA 獲得大量支持就能夠管理它們。 Google 的 BigTable 是一種已經(jīng)經(jīng)過檢驗(yàn)的技術(shù),可用來生成可伸縮的應(yīng)用程序,然而,您還可以在此基礎(chǔ)上進(jìn)行構(gòu)建。在 BigTable 上進(jìn)行構(gòu)建,您就不必處理還不完善的 API 層面。另一方面,您只要處理一個(gè)較低級(jí)別的 API。

#p#

App Engine for Java 中的 Java Data Objects

把傳統(tǒng)的 Java 應(yīng)用程序移植到 App Engine for Java 中,甚至是給出關(guān)系挑戰(zhàn),這些可能都沒有什么意義,然而,持久性場(chǎng)景還是存在的,這時(shí)使用這個(gè)平臺(tái)就有意義了。我將使用一個(gè)可行的示例來結(jié)束本文,您將體驗(yàn) App Engine for Java 持久性是如何工作的。我們將以 第 2 部分 中建立的聯(lián)系人管理應(yīng)用程序?yàn)榛A(chǔ),介紹如何添加支持,以使用 App Engine for Java 數(shù)據(jù)存儲(chǔ)工具持久化 Contact 對(duì)象。

在前面的文章中,您創(chuàng)建了一個(gè)簡(jiǎn)單的 GWT GUI,對(duì) Contact 對(duì)象進(jìn)行 CRUD 操作。您定義了簡(jiǎn)單的接口,如清單 1 所示:

清單 1. 簡(jiǎn)單的 ContactDAO 接口
				
package gaej.example.contact.server;

import java.util.List;

import gaej.example.contact.client.Contact;

public interface ContactDAO {
	void addContact(Contact contact);
	void removeContact(Contact contact);
	void updateContact(Contact contact);
	List<Contact> listContacts();
}

接下來,創(chuàng)建一個(gè)模擬版本,與內(nèi)存集合中的數(shù)據(jù)進(jìn)行交互,如清單 2 所示:

清單 2. 模擬 DAO 的 ContactDAOMock
				
package gaej.example.contact.server;

import gaej.example.contact.client.Contact;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ContactDAOMock implements ContactDAO {

	Map<String, Contact> map = new LinkedHashMap<String, Contact>();
	
	{
		map.put("rhightower@mammatus.com", new Contact("Rick Hightower", 
                                 "rhightower@mammatus.com", "520-555-1212"));
		map.put("scott@mammatus.com", new Contact("Scott Fauerbach", 
                                 "scott@mammatus.com", "520-555-1213"));
		map.put("bob@mammatus.com", new Contact("Bob Dean", 
                                 "bob@mammatus.com", "520-555-1214"));

	}
	
	public void addContact(Contact contact) {
		String email = contact.getEmail();
		map.put(email, contact);
	}

	public List<Contact> listContacts() {
		return Collections.unmodifiableList(new ArrayList<Contact>(map.values()));
	}

	public void removeContact(Contact contact) {
		map.remove(contact.getEmail());
	}

	public void updateContact(Contact contact) {		
		map.put(contact.getEmail(), contact);
	}

}

現(xiàn)在,使用與 Google App Engine 數(shù)據(jù)存儲(chǔ)交互的應(yīng)用程序替換模擬實(shí)現(xiàn),看看會(huì)發(fā)生什么。在這個(gè)示例中,您將使用 JDO 持久化 Contact 類。使用 Google Eclipse Plugin 編寫的應(yīng)用程序已經(jīng)擁有了使用 JDO 所需的所有庫。它還包含了一個(gè) jdoconfig.xml 文件,因此,一旦對(duì) Contact 類進(jìn)行了注釋,您就已經(jīng)準(zhǔn)備好開始使用 JDO。

清單 3 顯示擴(kuò)展后的 ContactDAO 接口,可使用 JDO API 進(jìn)行持久化、查詢、更新和刪除對(duì)象:

清單 3. 使用 JDO 的 ContactDAO
				
package gaej.example.contact.server;

import gaej.example.contact.client.Contact;

import java.util.List;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

public class ContactJdoDAO implements ContactDAO {
	private static final PersistenceManagerFactory pmfInstance = JDOHelper
			.getPersistenceManagerFactory("transactions-optional");

	public static PersistenceManagerFactory getPersistenceManagerFactory() {
		return pmfInstance;
	}

	public void addContact(Contact contact) {
		PersistenceManager pm = getPersistenceManagerFactory()
				.getPersistenceManager();
		try {
			pm.makePersistent(contact);
		} finally {
			pm.close();
		}
	}

	@SuppressWarnings("unchecked")
	public List<Contact> listContacts() {
		PersistenceManager pm = getPersistenceManagerFactory()
				.getPersistenceManager();
		String query = "select from " + Contact.class.getName();
		return (List<Contact>) pm.newQuery(query).execute();
	}

	public void removeContact(Contact contact) {
		PersistenceManager pm = getPersistenceManagerFactory()
				.getPersistenceManager();
		try {
			pm.currentTransaction().begin();

			// We don't have a reference to the selected Product.
			// So we have to look it up first,
			contact = pm.getObjectById(Contact.class, contact.getId());
			pm.deletePersistent(contact);

			pm.currentTransaction().commit();
		} catch (Exception ex) {
			pm.currentTransaction().rollback();
			throw new RuntimeException(ex);
		} finally {
			pm.close();
		}
	}

	public void updateContact(Contact contact) {
		PersistenceManager pm = getPersistenceManagerFactory()
				.getPersistenceManager();
		String name = contact.getName();
		String phone = contact.getPhone();
		String email = contact.getEmail();

		try {
			pm.currentTransaction().begin();
			// We don't have a reference to the selected Product.
			// So we have to look it up first,
			contact = pm.getObjectById(Contact.class, contact.getId());
			contact.setName(name);
			contact.setPhone(phone);
			contact.setEmail(email);
			pm.makePersistent(contact);
			pm.currentTransaction().commit();
		} catch (Exception ex) {
			pm.currentTransaction().rollback();
			throw new RuntimeException(ex);
		} finally {
			pm.close();
		}
	}

}

#p#

逐一比對(duì)方法

現(xiàn)在,考慮一下使用清單 3 中的每個(gè)方法時(shí)發(fā)生的情況。您將會(huì)發(fā)現(xiàn),方法的名字可能是新的,但它們的動(dòng)作大部分情況下都應(yīng)該感到熟悉。

首先,為了獲取 PersistenceManager,創(chuàng)建了一個(gè)靜態(tài)的 PersistenceManagerFactory。如果您以前使用過 JPA,PersistenceManager 與 JPA 中的 EntityManager 很相似。如果您使用過 Hibernate,PersistenceManager 與 Hibernate Session 很相似?;旧希?CODE>PersistenceManager 是 JDO 持久性系統(tǒng)的主接口。它代表了與數(shù)據(jù)庫的會(huì)話。getPersistenceManagerFactory() 方法返回靜態(tài)初始化的 PersistenceManagerFactory,如清單 4 所示:

清單 4. getPersistenceManagerFactory() 返回 PersistenceManagerFactory
				
private static final PersistenceManagerFactory pmfInstance = JDOHelper
		.getPersistenceManagerFactory("transactions-optional");

public static PersistenceManagerFactory getPersistenceManagerFactory() {
	return pmfInstance;
}

addContact() 方法把新的聯(lián)系人添加到數(shù)據(jù)存儲(chǔ)中。為了做到這點(diǎn),需要?jiǎng)?chuàng)建一個(gè) PersistenceManager 實(shí)例,然后,調(diào)用 PersistenceManagermakePersistence() 方法。makePersistence() 方法采用臨時(shí)的 Contact 對(duì)象(用戶將在 GWT GUI 中填充),并且使其成為一個(gè)持久的對(duì)象。所有這些如清單 5 所示:


清單 5. addContact()
				
public void addContact(Contact contact) {
	PersistenceManager pm = getPersistenceManagerFactory()
			.getPersistenceManager();
	try {
		pm.makePersistent(contact);
	} finally {
		pm.close();
	}
}

注意在清單 5 中,persistenceManager 是如何被封入在 finally 塊中。這確保能夠把與 persistenceManager 關(guān)聯(lián)的資源清除干凈。

如清單 6 所示,listContact() 方法從它所查找的 persistenceManager 中創(chuàng)建一個(gè)查詢對(duì)象。它調(diào)用了 execute() 方法,從數(shù)據(jù)存儲(chǔ)中返回 Contact 列表。


清單 6. listContact()
				
@SuppressWarnings("unchecked")
public List<Contact> listContacts() {
	PersistenceManager pm = getPersistenceManagerFactory()
			.getPersistenceManager();
	String query = "select from " + Contact.class.getName();
	return (List<Contact>) pm.newQuery(query).execute();
}

在從數(shù)據(jù)存儲(chǔ)中刪除聯(lián)系人之前,removeContact() 通過 ID 查找聯(lián)系人,如清單 7 所示。它必須這么做,而不僅僅是把聯(lián)系人直接刪除,這是因?yàn)閬碜?GWT GUI 的 Contact 對(duì) JDO 一無所知。在刪除前,您必須獲得與 PersistenceManager 緩存關(guān)聯(lián)的 Contact。


清單 7. removeContact()
				
public void removeContact(Contact contact) {
	PersistenceManager pm = getPersistenceManagerFactory()
			.getPersistenceManager();
	try {
		pm.currentTransaction().begin();

		// We don't have a reference to the selected Product.
		// So we have to look it up first,
		contact = pm.getObjectById(Contact.class, contact.getId());
		pm.deletePersistent(contact);

		pm.currentTransaction().commit();
	} catch (Exception ex) {
		pm.currentTransaction().rollback();
		throw new RuntimeException(ex);
	} finally {
		pm.close();
	}
}

清單 8 中的 updateContact() 方法與 removeContact() 方法類似,用來查找 Contact。然后,updateContact() 方法從 Contact 中復(fù)制屬性。這些屬性被當(dāng)作實(shí)參(Argument)傳送到 Contact,后者由持久性管理器查找。使用 PersistenceManager 檢查所查找的對(duì)象發(fā)生的變化。如果對(duì)象發(fā)生了變化,當(dāng)事務(wù)進(jìn)行提交時(shí),這些變化會(huì)被 PersistenceManager 刷新到數(shù)據(jù)庫。


清單 8. updateContact()
				
public void updateContact(Contact contact) {
	PersistenceManager pm = getPersistenceManagerFactory()
			.getPersistenceManager();
	String name = contact.getName();
	String phone = contact.getPhone();
	String email = contact.getEmail();

	try {
		pm.currentTransaction().begin();
		// We don't have a reference to the selected Product.
		// So we have to look it up first,
		contact = pm.getObjectById(Contact.class, contact.getId());
		contact.setName(name);
		contact.setPhone(phone);
		contact.setEmail(email);
		pm.makePersistent(contact);
		pm.currentTransaction().commit();
	} catch (Exception ex) {
		pm.currentTransaction().rollback();
		throw new RuntimeException(ex);
	} finally {
		pm.close();
	}
}

#p#

對(duì)象持久性注釋

為了使 Contact 能夠具有持久性,必須把它識(shí)別為一個(gè)具有 @PersistenceCapable 注釋的可持久性對(duì)象。然后,需要對(duì)它所有的可持久性字段進(jìn)行注釋,如清單 9 所示:


清單 9. Contact 具有可持久性
				
package gaej.example.contact.client;

import java.io.Serializable;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Contact implements Serializable {

	private static final long serialVersionUID = 1L;
	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Long id;
	@Persistent
	private String name;
	@Persistent
	private String email;
	@Persistent
	private String phone;

	public Contact() {

	}

	public Contact(String name, String email, String phone) {
		super();
		this.name = name;
		this.email = email;
		this.phone = phone;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPhone() {
		return phone;
	}

	public void setPhone(String phone) {
		this.phone = phone;
	}

}

通過面向?qū)ο蟮木幊毯徒涌谠O(shè)計(jì)原則,您只需使用新的 ContactJdoDAO 替代原始的 ContactDAOMock。然后 GWT GUI 無需任何修改就可處理 JDO。

最后,在這種替換中,真正改變 的是 DAO 在服務(wù)中被實(shí)例化的方式。如清單 10 所示:


清單 10. RemoteServiceServlet
				
public class ContactServiceImpl extends RemoteServiceServlet implements ContactService {
	private static final long serialVersionUID = 1L;
	//private ContactDAO contactDAO = new ContactDAOMock();
	private ContactDAO contactDAO = new ContactJdoDAO();
...

結(jié)束語

在這篇由三部分組成的文章中,介紹了 Google App Engine for Java 目前為持久性提供的支持,這是交付可伸縮應(yīng)用程序的基礎(chǔ)??偟慕Y(jié)論令人失望,但是要注意這是一個(gè)正在發(fā)展中的平臺(tái)。為 App Engine for Java 預(yù)覽版編寫的應(yīng)用程序被連接到 App Engine 的持久性基礎(chǔ)設(shè)施,即使是用 JDO 或 JPA 編寫。App Engine for Java 預(yù)覽版幾乎沒有為它的持久性框架提供任何文檔,而且 App Engine for Java 提供的示例幾乎無法演示即使是最簡(jiǎn)單的關(guān)系。

即使 JDO 和 JPA 實(shí)現(xiàn)已經(jīng)完全成熟,目前您仍然不可能編寫一個(gè) App Engine for Java 應(yīng)用程序并輕松地把它移植到一個(gè)基于 RDBMS 的企業(yè)應(yīng)用程序。要使移植能夠起作用,至少要編寫大量的代碼。

我希望持久性能隨著時(shí)間的推移而成熟起來。如果現(xiàn)在必須使用 App Engine for Java,您可能需要繞過 Java API,直接編寫低級(jí)別的 Datastore API。使用 App Engine for Java 平臺(tái)是可能的,但是,如果習(xí)慣了使用 JPA 和/或 JDO,那么將出現(xiàn)一條學(xué)習(xí)曲線,因?yàn)榇嬖诒疚那懊婷枋龅某橄笮孤?,并且目前的功能要么還無法正常運(yùn)行,要么還沒有進(jìn)行很好的文檔記錄。

責(zé)任編輯:yangsai 來源: IBMDW
相關(guān)推薦

2013-07-09 10:18:58

VDI虛擬化

2022-10-27 08:00:32

DockerAWS云容器

2021-05-25 10:20:31

Python持久性編程語言

2023-12-08 08:26:05

數(shù)據(jù)存儲(chǔ)持久性

2019-09-06 09:50:52

云存儲(chǔ)硬盤云服務(wù)

2009-09-02 11:34:09

Google App

2009-09-27 09:55:38

Hibernate持久

2021-01-22 10:40:08

Linux文件內(nèi)存

2009-06-26 16:32:22

App Engine文檔存儲(chǔ)文檔搜索

2009-09-23 15:25:08

Hibernate 3

2009-04-08 16:47:11

GoogleApp EngineJava

2009-04-13 15:48:54

Google AppJavaSun

2009-04-09 08:54:07

App EnginegoogleJava

2009-04-09 09:53:43

GoogleAppEngineJava

2012-08-01 14:12:45

IBMdW

2009-09-10 10:11:44

Google App Java開發(fā)2.0

2014-06-05 14:41:05

亞馬遜AWS

2021-06-02 08:00:00

MOSH開源工具

2009-07-14 09:25:43

Google App GAEJava SDK 1.

2009-05-14 09:47:30

GoogleApp EngineJava SDK
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)