البرنامج التعليمي: إنشاء تطبيق ويب Java باستخدام Azure Cosmos DB وواجهة برمجة التطبيقات ل NoSQL

ينطبق على: NoSQL

يوضح لك هذا البرنامج التعليمي لتطبيق Java على الويب كيفية استخدام خدمة Microsoft Azure Cosmos DB لتخزين البيانات والوصول إليها من تطبيق Java المستضاف على تطبيقات Azure App Service Web Apps. بدون بطاقة ائتمان أو اشتراك Azure، يمكنك إعداد حساب تجربة Azure Cosmos DB مجاني. في هذه المقالة، سوف تتمكن من:

  • كيفية إنشاء تطبيق JavaServer Pages (JSP) أساسي في Eclipse.
  • كيفية العمل مع خدمة Azure Cosmos DB باستخدام Azure Cosmos DB Java SDK.

يوضح لك هذا البرنامج التعليمي لتطبيق Java كيفية إنشاء تطبيق إدارة المهام على شبكة الإنترنت، ويمكّنك من إنشاء، واسترداد، وتمييز المهام على أنها مكتملة، كما هو موضح في الصورة التالية. يتم تخزين كل مهمة من المهام في قائمة ToDo كمستندات JSON في Azure Cosmos DB.

تطبيق Java My ToDo List

تلميح

يفترض هذا البرنامج التعليمي لتطوير التطبيقات أن لديك خبرة سابقة في استخدام Java. في حال كنت مستخدمًا جديدًا لـ Java أو أدوات المتطلبات الأساسية، نوصي بتنزيل مشروع todo الكامل من GitHub وإنشائه باستخدام الإرشادات الموجودة في نهاية هذه المقالة. بمجرد الانتهاء من بنائه، يمكنك مراجعة المقالة للحصول على نظرة ثاقبة حول الكود في سياق المشروع.

المتطلبات الأساسية لهذا البرنامج التعليمي لتطبيق Java على الويب

قبل أن تبدأ هذا البرنامج التعليمي لتطوير التطبيق، ينبغي أن يكون لديك ما يلي:

في حال كنت تثبت هذه الأدوات لأول مرة، فإن موقع coreservlets.com يوفر نظرة عامة على عملية التثبيت في قسم التشغيل السريع للبرنامج التعليمي: مقالة تثبيت TomCat7 واستخدامه مع Eclipse.

إنشاء حساب Azure Cosmos DB

فلنبدأ بإنشاء حساب Azure Cosmos DB. في حال كان لديك حساب بالفعل أو كنت تستخدم Azure Cosmos DB Emulator لهذا البرنامج التعليمي، فيمكنك التخطي إلى الخطوة 2: إنشاء تطبيق Java JSP.

  1. من قائمة مدخل Azure أو الصفحة الرئيسية، حدد Create a resource.

  2. ابحث عن Azure Cosmos DB. حدد Create>Azure Cosmos DB.

  3. في صفحة Create an Azure Cosmos DB account ، حدد الخيار Create ضمن قسم Azure Cosmos DB for NoSQL .

    يوفر Azure Cosmos DB العديد من واجهات برمجة التطبيقات:

    • NoSQL، لبيانات المستند
    • PostgreSQL
    • MongoDB، لبيانات المستند
    • Apache Cassandra
    • جدول
    • Apache Gremlin، لبيانات الرسم البياني

    لمعرفة المزيد حول واجهة برمجة التطبيقات ل NoSQL، راجع مرحبا بك في Azure Cosmos DB.

  4. في صفحة إنشاء حساب Azure Cosmos DB، أدخل الإعدادات الأساسية لحساب Azure Cosmos DB الجديد.

    الإعداد قيمة ‏‏الوصف
    الاشتراك اسم الاشتراك حدد اشتراك Azure الذي تريد استخدامه لحساب Azure Cosmos DB هذا.
    مجموعة الموارد اسم مجموعة الموارد حدد مجموعة موارد، أو حدد إنشاء جديد، ثم أدخل اسمًا فريدًا لمجموعة الموارد الجديدة.
    اسم الحساب اسم فريد أدخل اسما لتعريف حساب Azure Cosmos DB الخاص بك. لأنه يتم إلحاق documents.azure.com إلى الاسم الذي تقوم بتوفيره لإنشاء URI الخاص بك، استخدم اسمًا فريدًا. يمكن أن يحتوي الاسم على أحرف صغيرة وأرقام وحرف الواصلة (-) فقط. يجب أن يكون من 3 إلى 44 حرفا.
    الموقع المنطقة الأقرب إلى مستخدميك حدد موقعًا جغرافيًّا لاستضافة حساب Azure Cosmos DB. استخدم الموقع الأقرب إلى المستخدمين لمنحهم أسرع وصول إلى البيانات.
    وضع السعة معدل النقل المقدم أو بلا خادم حدد "Provisioned throughput" لإنشاء حساب في وضع معدل النقل المتوفر. حدد Serverless لإنشاء حساب في وضع دون خادم.
    تطبيق خصم طبقة Azure Cosmos DB المجاني تطبيق أو عدم تطبيق مع الطبقة المجانية لـ Azure Cosmos DB، ستحصل على أول 1000 RU/ثانية و25 غيغابايت من التخزين مجاناً في الحساب. تعرف على المزيد حول الطبقة المجانية.
    ضع حدُا لمعدل نقل الحساب محدد أم لا حدد إجمالي مقدار معدل النقل الذي يمكن توفيره على هذا الحساب. يمنع هذا الحد الرسوم غير المتوقعة المتعلقة بمعدل النقل المقدم. يمكنك تحديث هذا الحد أو إزالته بعد إنشاء حسابك.

    يمكن أن يكون لديك ما يصل إلى حساب Azure Cosmos DB مجاني واحد لكل اشتراك Azure ويجب عليك الاشتراك عند إنشاء الحساب. إذا كنت لا ترى خيار تطبيق خصم الطبقة المجانية، فهذا يعني تمكين حساب آخر بالفعل في الاشتراك مع الطبقة المجانية.

    تظهر لقطة الشاشة صفحة إنشاء حساب Azure Cosmos DB.

    إشعار

    الخيارات التالية غير متوفرة إذا قمت بتحديد دون خادمباعتبارهاوضع السعة:

    • تطبيق خصم من الدرجة المجانية
    • ضع حدُا لمعدل نقل الحساب
  5. في علامة تبويب Global Distributionكوّن التفاصيل التالية. يمكنك ترك القيم الافتراضية لهذه البداية السريعة:

    الإعداد قيمة ‏‏الوصف
    التكرار الجغرافي تعطيل تمكين التوزيع العمومي على حسابك أو تعطيله عن طريق إقران منطقتك بمنطقة زوج. يمكنك إضافة المزيد من المناطق إلى حسابك لاحقًا.
    كتابات متعددة المناطق تعطيل تتيح لك إمكانية الكتابة متعددة المناطق الاستفادة من الإنتاجية المقدمة لقواعد البيانات والحاويات الخاصة بك في جميع أنحاء العالم.
    مجموعات التوافر تعطيل تساعدك مناطق التوفر على تحسين توفر التطبيق ومرونته.

    إشعار

    لا تتوفر الخيارات التالية إذا قمت بتحديد Serverless كوضع Capacity في صفحة Basics السابقة:

    • Geo-redundancy
    • كتابات متعددة المناطق
  6. اختياريا، يمكنك تكوين مزيد من التفاصيل في علامات التبويب التالية:

    • الشبكات. تكوين الوصول من شبكة ظاهرية.
    • نهج النسخ الاحتياطي. تكوين نهج النسخ الاحتياطي الدوري أو المستمر .
    • التشفير. استخدم إما مفتاحا مدارا بواسطة الخدمة أو مفتاحا يديره العميل.
    • العلامات. العلامات هي أزواج أسماء/قيم تمكنك من تصنيف الموارد وعرض الفوترة الموحدة من خلال تطبيق العلامة نفسها على موارد ومجموعات موارد متعددة.
  7. حدد "Review + create".

  8. راجع الإعدادات، ثم حدد Create. يستغرق إنشاء جهاز ظاهر بضع دقائق. انتظر حتى يتم عرض صفحة المدخل، اكتمل النشر.

    تظهر لقطة الشاشة اكتمال النشر.

  9. حدد الانتقال إلى المورد للانتقال إلى صفحة حساب Azure Cosmos DB.

    تظهر لقطة الشاشة صفحة حساب Azure Cosmos DB.

انتقل إلى صفحة حساب Azure Cosmos DB، وحدد Keys. انسخ القيم المراد استخدامها في تطبيق الويب الذي تنشئه بعد ذلك.

لقطة شاشة لمدخل Azure مع تمييز الزر «Keys» في صفحة حساب Azure Cosmos DB

إنشاء تطبيق Java JSP

لإنشاء تطبيق JSP:

  1. أولاً، سنبدأ بإنشاء مشروع Java. ابدأ Eclipse، ثم حدد File، وحدد New، ثم حدد Dynamic Web Project. إذا كنت لا ترى Dynamic Web Project مدرجاً كمشروع متاح، فقم بما يلي: حدد File، وحدد New، وحدد Project...، ووسّع Web، وحدد Dynamic Web Project، وحدد Next.

    تطوير تطبيقات JSP Java

  2. أدخل اسم مشروع في المربع Project name، وفي القائمة المنسدلة Target Runtime، حدد قيمة اختيارياً (مثل Apache Tomcat v7.0)، ثم حدد Finish. يتيح لك تحديد وقت تشغيل مستهدف تشغيل مشروعك محليًّا من خلال Eclipse.

  3. في Eclipse، في مستكشف Project Explorer، قم بتوسيع مشروعك. انقر بزر الماوس الأيمن فوق WebContent، وحدد New، ثم حدد JSP File.

  4. في مربع الحوارNew JSP File، قم بتسمية الملف index.jsp. احتفظ بالمجلد الأصلي كـ WebContent، كما هو موضح في الرسم التوضيحي التالي، ثم حدد Next.

    قم بإنشاء ملف JSP جديد - برنامج تعليمي لتطبيق Java على الويب

  5. في مربع الحوار Select JSP Template، لغرض هذا البرنامج التعليمي، حدد New JSP File (html)، ثم حدد Finish.

  6. عندما يفتح الملف index.jsp في Eclipse، أضف نصاً لعرض Hello World! داخل عنصر <body> الحالي. يجب أن يبدو المحتوى المحدث <body> مثل الكود التالي:

    <body>
      <% out.println("Hello World!"); %>
    </body>
    
  7. احفظ الملف index.jsp.

  8. إذا قمت بتعيين وقت تشغيل مستهدف في الخطوة 2، فيمكنك تحديد Project ثم Run لتشغيل تطبيق JSP محلياً:

    مرحبًا بالعالم - برنامج تعليمي لتطبيق Java

ثبت SQL Java SDK

أسهل طريقة لسحب SQL Java SDK وتبعياتها تكون من خلالApache Maven. لعمل ذلك، تحتاج إلى تحويل مشروعك إلى مشروع Maven باستخدام الخطوات التالية:

  1. انقر بزر الماوس الأيمن فوق مشروعك في Project Explorer، وحدد Configure، وحدد Convert to Maven Project.

  2. في نافذة إنشاء POM جديد، اقبل الإعدادات الافتراضية، وحدد Finish.

  3. في Project Explorer، افتح الملف pom.xml.

  4. في علامة التبويب Dependencies، في جزء Dependencies، حدد Add.

  5. في نافذة "Select Dependency" افعل ما يلي:

    • في المربع "Group Id" أدخل com.azure.
    • في مربع "Artifact Id"، أدخل azure-cosmos.
    • في المربع "Version"، أدخل 4.11.0.

    أو يمكنك إضافة تبعية XML لمعرّف المجموعة ومعرف الأداة مباشرةً إلى ملفpom.xml:

    <dependency>
      <groupId>com.azure</groupId>
      <artifactId>azure-cosmos</artifactId>
      <version>4.11.0</version>
    </dependency>
    
  6. حدد OK وسيقوم Maven بتثبيت SQL Java SDK أو حفظ ملف pom.xml.

استخدام خدمة Azure Cosmos DB في تطبيق Java

الآن دعونا نضيف النماذج، وطرق العرض، ووحدات التحكم إلى تطبيقك.

إضافة نموذج

أولًا، دعنا نعرف نموذجًا في ملف جديد "TodoItem.java". تعرفTodoItem الفئة مخطط عنصر مع أساليب getter و setter:

package com.microsoft.azure.cosmos.sample.model;

//@Data
//@Builder
public class TodoItem {
    private String entityType;
    private String category;
    private boolean complete;
    private String id;
    private String name;

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getEntityType() {
        return entityType;
    }

    public void setEntityType(String entityType) {
        this.entityType = entityType;
    }

    public boolean isComplete() {
        return complete;
    }

    public void setComplete(boolean complete) {
        this.complete = complete;
    }

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

أضف فئات كائن الوصول إلى البيانات (DAO)

أنشئ كائن الوصول إلى البيانات (DAO) لدعم استمرارية عناصر ToDo إلى Azure Cosmos DB. من أجل حفظ عناصر ToDo إلى مجموعة، يحتاج العميل إلى معرفة قاعدة البيانات التي يجب أن تستمر إلى (كما هو المشار إليه بواسطة الارتباطات الذاتية). بشكل عام، فمن الأفضل تخزين قاعدة البيانات، وجمع عندما يكون ذلك ممكنا لتجنب رحلات إضافية ذهابًا وإيابًا إلى قاعدة البيانات.

  1. لاستدعاء خدمة Azure Cosmos DB، يجب عليك إنشاء مثيل كائن cosmosClient جديد. بشكل عام، فمن الأفضل إعادة استخدام كائن cosmosClient بدلًا من إنشاء عميل جديد لكل طلب لاحق. يمكنك إعادة استخدام العميل بتعريفه داخل الفئة cosmosClientFactory. تحديث قيم HOST وMASTER_KEY التي حفظتها في الخطوة 1. استبدل متغير HOST بـ URI الخاص بك واستبدل MASTER_KEY بالمفتاح الأساسي. استخدم التعليمات البرمجية التالية لإنشاء الفئة CosmosClientFactory داخل ملف CosmosClientFactory.java:

    package com.microsoft.azure.cosmos.sample.dao;
    
    import com.azure.cosmos.ConsistencyLevel;
    import com.azure.cosmos.CosmosClient;
    import com.azure.cosmos.CosmosClientBuilder;
    
    public class CosmosClientFactory {
        private static final String HOST = "[ACCOUNT HOST NAME]";
        private static final String MASTER_KEY = "[ACCOUNT KEY]";
    
        private static CosmosClient cosmosClient = new CosmosClientBuilder()
                .endpoint(HOST)
                .key(MASTER_KEY)
                .consistencyLevel(ConsistencyLevel.EVENTUAL)
                .buildClient();
    
        public static CosmosClient getCosmosClient() {
            return cosmosClient;
        }
    
    }
    
  2. أنشئ ملف todoDao.java جديدًا وأضف TodoDao الفئة لإنشاء، وتحديث، وقراءة، وحذف عناصر todo:

    package com.microsoft.azure.cosmos.sample.dao;
    
    import java.util.List;
    
    import com.microsoft.azure.cosmos.sample.model.TodoItem;
    
    public interface TodoDao {
        /**
         * @return A list of TodoItems
         */
        public List<TodoItem> readTodoItems();
    
        /**
         * @param todoItem
         * @return whether the todoItem was persisted.
         */
        public TodoItem createTodoItem(TodoItem todoItem);
    
        /**
         * @param id
         * @return the TodoItem
         */
        public TodoItem readTodoItem(String id);
    
        /**
         * @param id
         * @return the TodoItem
         */
        public TodoItem updateTodoItem(String id, boolean isComplete);
    
        /**
         *
         * @param id
         * @return whether the delete was successful.
         */
        public boolean deleteTodoItem(String id);
    }
    
  3. أنشئ ملف MockDao.java جديدًا وأضف الفئة MockDao، هذه الفئة تنفذ الفئة TodoDao لتنفيذ عمليات CRUD على العناصر:

    package com.microsoft.azure.cosmos.sample.dao;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import lombok.NonNull;
    
    import com.microsoft.azure.cosmos.sample.model.TodoItem;
    
    public class MockDao implements TodoDao {
        private final Map<String, TodoItem> todoItemMap;
    
        public MockDao() {
            todoItemMap = new HashMap<String, TodoItem>();
        }
    
        @Override
        public TodoItem createTodoItem(@NonNull TodoItem todoItem) {
            if (todoItem.getId() == null || todoItem.getId().isEmpty()) {
                todoItem.setId(generateId());
            }
            todoItemMap.put(todoItem.getId(), todoItem);
            return todoItem;
        }
    
        @Override
        public TodoItem readTodoItem(@NonNull String id) {
            return todoItemMap.get(id);
        }
    
        @Override
        public List<TodoItem> readTodoItems() {
            return new ArrayList<TodoItem>(todoItemMap.values());
        }
    
        @Override
        public TodoItem updateTodoItem(String id, boolean isComplete) {
            todoItemMap.get(id).setComplete(isComplete);
            return todoItemMap.get(id);
        }
    
        @Override
        public boolean deleteTodoItem(@NonNull String id) {
            todoItemMap.remove(id);
            return true;
        }
    
        private String generateId() {
            return new Integer(todoItemMap.size()).toString();
        }
    }
    
  4. أنشئ ملف DocDbDao.java جديدًا وأضف الفئة DocDbDao. تعرف هذه الفئة التعليمات البرمجية للاحتفاظ بـ TodoItems في الحاوية، أو استرداد قاعدة بياناتك والمجموعة، إذا كانت موجودة، أو إنشاء واحدة جديدة إذا لم تكن موجودة. يستخدم هذا المثال Gson لتغيير كائنات TodoItem Plain Old Java (POJOs) إلى متسلسل وإلغاء تسلسلها إلى مستندات JSON. من أجل حفظ عناصر ToDo إلى مجموعة، يحتاج العميل إلى معرفة قاعدة البيانات التي يجب أن تستمر إلى (كما هو المشار إليه بواسطة الارتباطات الذاتية). تعرف هذه الفئة أيضًا دالة المساعد لاسترداد المستندات بواسطة سمة أخرى (مثل "معرف") بدلاً من الارتباط الذاتي. يمكنك استخدام أسلوب المساعد لاسترداد مستند TodoItem JSON بواسطة معرف، ثم إلغاء تسلسله إلى POJO.

    يمكنك أيضًا استخدام cosmosClient كائن العميل للحصول على مجموعة أو قائمة TodoItems باستخدام استعلام SQL. وأخيرًا، يمكنك تعريف أسلوب الحذف لحذف TodoItem من قائمتك. التعليمات البرمجية التالية تظهر محتويات الفئة DocDbDao:

    package com.microsoft.azure.cosmos.sample.dao;
    
    import com.azure.cosmos.CosmosClient;
    import com.azure.cosmos.CosmosContainer;
    import com.azure.cosmos.CosmosDatabase;
    import com.azure.cosmos.CosmosException;
    import com.azure.cosmos.implementation.Utils;
    import com.azure.cosmos.models.CosmosContainerProperties;
    import com.azure.cosmos.models.CosmosContainerResponse;
    import com.azure.cosmos.models.CosmosDatabaseResponse;
    import com.azure.cosmos.models.CosmosItemRequestOptions;
    import com.azure.cosmos.models.CosmosQueryRequestOptions;
    import com.azure.cosmos.models.FeedResponse;
    import com.azure.cosmos.models.PartitionKey;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import com.google.gson.Gson;
    import com.microsoft.azure.cosmos.sample.model.TodoItem;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class DocDbDao implements TodoDao {
        // The name of our database.
        private static final String DATABASE_ID = "TestDB";
    
        // The name of our collection.
        private static final String CONTAINER_ID = "TestCollection";
    
        // We'll use Gson for POJO <=> JSON serialization for this example.
        private static Gson gson = new Gson();
    
        // The Cosmos DB Client
        private static CosmosClient cosmosClient = CosmosClientFactory
            .getCosmosClient();
    
        // The Cosmos DB database
        private static CosmosDatabase cosmosDatabase = null;
    
        // The Cosmos DB container
        private static CosmosContainer cosmosContainer = null;
    
        // For POJO/JsonNode interconversion
        private static final ObjectMapper OBJECT_MAPPER = Utils.getSimpleObjectMapper();
    
        @Override
        public TodoItem createTodoItem(TodoItem todoItem) {
            // Serialize the TodoItem as a JSON Document.
    
            JsonNode todoItemJson = OBJECT_MAPPER.valueToTree(todoItem);
    
            ((ObjectNode) todoItemJson).put("entityType", "todoItem");
    
            try {
                // Persist the document using the DocumentClient.
                todoItemJson =
                    getContainerCreateResourcesIfNotExist()
                        .createItem(todoItemJson)
                        .getItem();
            } catch (CosmosException e) {
                System.out.println("Error creating TODO item.\n");
                e.printStackTrace();
                return null;
            }
    
    
            try {
    
                return OBJECT_MAPPER.treeToValue(todoItemJson, TodoItem.class);
                //return todoItem;
            } catch (Exception e) {
                System.out.println("Error deserializing created TODO item.\n");
                e.printStackTrace();
    
                return null;
            }
    
        }
    
        @Override
        public TodoItem readTodoItem(String id) {
            // Retrieve the document by id using our helper method.
            JsonNode todoItemJson = getDocumentById(id);
    
            if (todoItemJson != null) {
                // De-serialize the document in to a TodoItem.
                try {
                    return OBJECT_MAPPER.treeToValue(todoItemJson, TodoItem.class);
                } catch (JsonProcessingException e) {
                    System.out.println("Error deserializing read TODO item.\n");
                    e.printStackTrace();
    
                    return null;
                }
            } else {
                return null;
            }
        }
    
        @Override
        public List<TodoItem> readTodoItems() {
    
            List<TodoItem> todoItems = new ArrayList<TodoItem>();
    
            String sql = "SELECT * FROM root r WHERE r.entityType = 'todoItem'";
            int maxItemCount = 1000;
            int maxDegreeOfParallelism = 1000;
            int maxBufferedItemCount = 100;
    
            CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
            options.setMaxBufferedItemCount(maxBufferedItemCount);
            options.setMaxDegreeOfParallelism(maxDegreeOfParallelism);
            options.setQueryMetricsEnabled(false);
    
            int error_count = 0;
            int error_limit = 10;
    
            String continuationToken = null;
            do {
    
                for (FeedResponse<JsonNode> pageResponse :
                    getContainerCreateResourcesIfNotExist()
                        .queryItems(sql, options, JsonNode.class)
                        .iterableByPage(continuationToken, maxItemCount)) {
    
                    continuationToken = pageResponse.getContinuationToken();
    
                    for (JsonNode item : pageResponse.getElements()) {
    
                        try {
                            todoItems.add(OBJECT_MAPPER.treeToValue(item, TodoItem.class));
                        } catch (JsonProcessingException e) {
                            if (error_count < error_limit) {
                                error_count++;
                                if (error_count >= error_limit) {
                                    System.out.println("\n...reached max error count.\n");
                                } else {
                                    System.out.println("Error deserializing TODO item JsonNode. " +
                                        "This item will not be returned.");
                                    e.printStackTrace();
                                }
                            }
                        }
    
                    }
                }
    
            } while (continuationToken != null);
    
            return todoItems;
        }
    
        @Override
        public TodoItem updateTodoItem(String id, boolean isComplete) {
            // Retrieve the document from the database
            JsonNode todoItemJson = getDocumentById(id);
    
            // You can update the document as a JSON document directly.
            // For more complex operations - you could de-serialize the document in
            // to a POJO, update the POJO, and then re-serialize the POJO back in to
            // a document.
            ((ObjectNode) todoItemJson).put("complete", isComplete);
    
            try {
                // Persist/replace the updated document.
                todoItemJson =
                    getContainerCreateResourcesIfNotExist()
                        .replaceItem(todoItemJson, id, new PartitionKey(id), new CosmosItemRequestOptions())
                        .getItem();
            } catch (CosmosException e) {
                System.out.println("Error updating TODO item.\n");
                e.printStackTrace();
                return null;
            }
    
            // De-serialize the document in to a TodoItem.
            try {
                return OBJECT_MAPPER.treeToValue(todoItemJson, TodoItem.class);
            } catch (JsonProcessingException e) {
                System.out.println("Error deserializing updated item.\n");
                e.printStackTrace();
    
                return null;
            }
        }
    
        @Override
        public boolean deleteTodoItem(String id) {
            // CosmosDB refers to documents by self link rather than id.
    
            // Query for the document to retrieve the self link.
            JsonNode todoItemJson = getDocumentById(id);
    
            try {
                // Delete the document by self link.
                getContainerCreateResourcesIfNotExist()
                    .deleteItem(id, new PartitionKey(id), new CosmosItemRequestOptions());
            } catch (CosmosException e) {
                System.out.println("Error deleting TODO item.\n");
                e.printStackTrace();
                return false;
            }
    
            return true;
        }
    
        /*
        
        private CosmosDatabase getTodoDatabase() {
            if (databaseCache == null) {
                // Get the database if it exists
                List<CosmosDatabase> databaseList = cosmosClient
                        .queryDatabases(
                                "SELECT * FROM root r WHERE r.id='" + DATABASE_ID
                                        + "'", null).getQueryIterable().toList();
    
                if (databaseList.size() > 0) {
                    // Cache the database object so we won't have to query for it
                    // later to retrieve the selfLink.
                    databaseCache = databaseList.get(0);
                } else {
                    // Create the database if it doesn't exist.
                    try {
                        CosmosDatabase databaseDefinition = new CosmosDatabase();
                        databaseDefinition.setId(DATABASE_ID);
    
                        databaseCache = cosmosClient.createDatabase(
                                databaseDefinition, null).getResource();
                    } catch (CosmosException e) {
                        // TODO: Something has gone terribly wrong - the app wasn't
                        // able to query or create the collection.
                        // Verify your connection, endpoint, and key.
                        e.printStackTrace();
                    }
                }
            }
    
            return databaseCache;
        }
    
        */
    
        private CosmosContainer getContainerCreateResourcesIfNotExist() {
    
            try {
    
                if (cosmosDatabase == null) {
                    CosmosDatabaseResponse cosmosDatabaseResponse = cosmosClient.createDatabaseIfNotExists(DATABASE_ID);
                    cosmosDatabase = cosmosClient.getDatabase(cosmosDatabaseResponse.getProperties().getId());
                }
    
            } catch (CosmosException e) {
                // TODO: Something has gone terribly wrong - the app wasn't
                // able to query or create the collection.
                // Verify your connection, endpoint, and key.
                System.out.println("Something has gone terribly wrong - " +
                    "the app wasn't able to create the Database.\n");
                e.printStackTrace();
            }
    
            try {
    
                if (cosmosContainer == null) {
                    CosmosContainerProperties properties = new CosmosContainerProperties(CONTAINER_ID, "/id");
                    CosmosContainerResponse cosmosContainerResponse = cosmosDatabase.createContainerIfNotExists(properties);
                    cosmosContainer = cosmosDatabase.getContainer(cosmosContainerResponse.getProperties().getId());
                }
    
            } catch (CosmosException e) {
                // TODO: Something has gone terribly wrong - the app wasn't
                // able to query or create the collection.
                // Verify your connection, endpoint, and key.
                System.out.println("Something has gone terribly wrong - " +
                    "the app wasn't able to create the Container.\n");
                e.printStackTrace();
            }
    
            return cosmosContainer;
        }
    
        private JsonNode getDocumentById(String id) {
    
            String sql = "SELECT * FROM root r WHERE r.id='" + id + "'";
            int maxItemCount = 1000;
            int maxDegreeOfParallelism = 1000;
            int maxBufferedItemCount = 100;
    
            CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
            options.setMaxBufferedItemCount(maxBufferedItemCount);
            options.setMaxDegreeOfParallelism(maxDegreeOfParallelism);
            options.setQueryMetricsEnabled(false);
    
            List<JsonNode> itemList = new ArrayList();
    
            String continuationToken = null;
            do {
                for (FeedResponse<JsonNode> pageResponse :
                    getContainerCreateResourcesIfNotExist()
                        .queryItems(sql, options, JsonNode.class)
                        .iterableByPage(continuationToken, maxItemCount)) {
    
                    continuationToken = pageResponse.getContinuationToken();
    
                    for (JsonNode item : pageResponse.getElements()) {
                        itemList.add(item);
                    }
                }
    
            } while (continuationToken != null);
    
            if (itemList.size() > 0) {
                return itemList.get(0);
            } else {
                return null;
            }
        }
    
    }
    
  5. بعد ذلك، أنشئ ملف TodoDaoFactory.java جديدًا وأضف TodoDaoFactory الفئة التي تنشئ كائن DocDbDao جديدًا:

    package com.microsoft.azure.cosmos.sample.dao;
    
    public class TodoDaoFactory {
        private static TodoDao myTodoDao = new DocDbDao();
    
        public static TodoDao getDao() {
            return myTodoDao;
        }
    }
    

إضافة وحدة تحكم

أضف وحدة تحكم TodoItemController إلى تطبيقك. في هذا المشروع، تستخدم Project Lombokلإنشاء المُنشئ، والمكتسبات، والمحددات، والمنشئ. وبدلاً من ذلك، يمكنك كتابة هذه التعليمة البرمجية يدوياً أو جعل IDE ينشئه:

package com.microsoft.azure.cosmos.sample.controller;

import java.util.List;
import java.util.UUID;

import lombok.NonNull;

import com.microsoft.azure.cosmos.sample.dao.TodoDao;
import com.microsoft.azure.cosmos.sample.dao.TodoDaoFactory;
import com.microsoft.azure.cosmos.sample.model.TodoItem;

public class TodoItemController {
    public static TodoItemController getInstance() {
        if (todoItemController == null) {
            todoItemController = new TodoItemController(TodoDaoFactory.getDao());
        }
        return todoItemController;
    }

    private static TodoItemController todoItemController;

    private final TodoDao todoDao;

    TodoItemController(TodoDao todoDao) {
        this.todoDao = todoDao;
    }

    public TodoItem createTodoItem(@NonNull String name,
            @NonNull String category, boolean isComplete) {
        TodoItem todoItem = new TodoItem();
        
        todoItem.setName(name);
        todoItem.setCategory(category);
        todoItem.setComplete(isComplete);
        todoItem.setId(UUID.randomUUID().toString());

        return todoDao.createTodoItem(todoItem);
    }

    public boolean deleteTodoItem(@NonNull String id) {
        return todoDao.deleteTodoItem(id);
    }

    public TodoItem getTodoItemById(@NonNull String id) {
        return todoDao.readTodoItem(id);
    }

    public List<TodoItem> getTodoItems() {
        return todoDao.readTodoItems();
    }

    public TodoItem updateTodoItem(@NonNull String id, boolean isComplete) {
        return todoDao.updateTodoItem(id, isComplete);
    }
}

إنشاء servlet

بعد ذلك، أنشئ servlet لتوجيه طلبات HTTP إلى وحدة التحكم. أنشئ ملف ApiServlet.java ثم عرف التعليمات البرمجية التالية تحته:

package com.microsoft.azure.cosmos.sample;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;
import com.microsoft.azure.cosmos.sample.controller.TodoItemController;

/**
 * API Frontend Servlet
 */
@WebServlet("/api")
public class ApiServlet extends HttpServlet {
    // API Keys
    public static final String API_METHOD = "method";

    // API Methods
    public static final String CREATE_TODO_ITEM = "createTodoItem";
    public static final String GET_TODO_ITEMS = "getTodoItems";
    public static final String UPDATE_TODO_ITEM = "updateTodoItem";

    // API Parameters
    public static final String TODO_ITEM_ID = "todoItemId";
    public static final String TODO_ITEM_NAME = "todoItemName";
    public static final String TODO_ITEM_CATEGORY = "todoItemCategory";
    public static final String TODO_ITEM_COMPLETE = "todoItemComplete";

    public static final String MESSAGE_ERROR_INVALID_METHOD = "{'error': 'Invalid method'}";

    private static final long serialVersionUID = 1L;
    private static final Gson gson = new Gson();

    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        String apiResponse = MESSAGE_ERROR_INVALID_METHOD;

        TodoItemController todoItemController = TodoItemController
                .getInstance();

        String id = request.getParameter(TODO_ITEM_ID);
        String name = request.getParameter(TODO_ITEM_NAME);
        String category = request.getParameter(TODO_ITEM_CATEGORY);
        String itemComplete = request.getParameter(TODO_ITEM_COMPLETE);
        boolean isComplete = itemComplete!= null && itemComplete.equalsIgnoreCase("true");

        switch (request.getParameter(API_METHOD)) {
        case CREATE_TODO_ITEM:
            apiResponse = gson.toJson(todoItemController.createTodoItem(name,
                    category, isComplete));
            break;
        case GET_TODO_ITEMS:
            apiResponse = gson.toJson(todoItemController.getTodoItems());
            break;
        case UPDATE_TODO_ITEM:
            apiResponse = gson.toJson(todoItemController.updateTodoItem(id,
                    isComplete));
            break;
        default:
            break;
        }

        response.setCharacterEncoding("UTF-8");
        response.getWriter().println(apiResponse);
    }

    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

توصيل باقي تطبيقات Java معاً

الآن بعد أن انتهينا من البتات الممتعة ، كل ما تبقى هو بناء واجهة مستخدم سريعة، وأسلاك تصل إلى DAO الخاص بك.

  1. تحتاج إلى واجهة مستخدم ويب لعرضها للمستخدم. دعنا نعد كتابة index.jsp الذي أنشأناه في وقت سابق مع التعليمات البرمجية التالية:

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge;" />
      <title>Azure Cosmos Java Sample</title>
    
      <!-- Bootstrap -->
      <link href="//ajax.aspnetcdn.com/ajax/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
    
      <style>
        /* Add padding to body for fixed nav bar */
        body {
          padding-top: 50px;
        }
      </style>
    </head>
    <body>
      <!-- Nav Bar -->
      <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
        <div class="container">
          <div class="navbar-header">
            <a class="navbar-brand" href="#">My Tasks</a>
          </div>
        </div>
      </div>
    
      <!-- Body -->
      <div class="container">
        <h1>My ToDo List</h1>
    
        <hr/>
    
        <!-- The ToDo List -->
        <div class = "todoList">
          <table class="table table-bordered table-striped" id="todoItems">
            <thead>
              <tr>
                <th>Name</th>
                <th>Category</th>
                <th>Complete</th>
              </tr>
            </thead>
            <tbody>
            </tbody>
          </table>
    
          <!-- Update Button -->
          <div class="todoUpdatePanel">
            <form class="form-horizontal" role="form">
              <button type="button" class="btn btn-primary">Update Tasks</button>
            </form>
          </div>
    
        </div>
    
        <hr/>
    
        <!-- Item Input Form -->
        <div class="todoForm">
          <form class="form-horizontal" role="form">
            <div class="form-group">
              <label for="inputItemName" class="col-sm-2">Task Name</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="inputItemName" placeholder="Enter name">
              </div>
            </div>
    
            <div class="form-group">
              <label for="inputItemCategory" class="col-sm-2">Task Category</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="inputItemCategory" placeholder="Enter category">
              </div>
            </div>
    
            <button type="button" class="btn btn-primary">Add Task</button>
          </form>
        </div>
    
      </div>
    
      <!-- Placed at the end of the document so the pages load faster -->
      <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.1.min.js"></script>
      <script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.2.0/bootstrap.min.js"></script>
      <script src="assets/todo.js"></script>
    </body>
    </html>
    
  2. أخيرًا، اكتب بعض JavaScript من جانب العميل لربط واجهة مستخدم الويب وservlet معًا:

    /**
     * ToDo App
     */
    
    var todoApp = {
      /*
       * API methods to call Java backend.
       */
      apiEndpoint: "api",
    
      createTodoItem: function(name, category, isComplete) {
        $.post(todoApp.apiEndpoint, {
            "method": "createTodoItem",
            "todoItemName": name,
            "todoItemCategory": category,
            "todoItemComplete": isComplete
          },
          function(data) {
            var todoItem = data;
            todoApp.addTodoItemToTable(todoItem.id, todoItem.name, todoItem.category, todoItem.complete);
          },
          "json");
      },
    
      getTodoItems: function() {
        $.post(todoApp.apiEndpoint, {
            "method": "getTodoItems"
          },
          function(data) {
            var todoItemArr = data;
            $.each(todoItemArr, function(index, value) {
              todoApp.addTodoItemToTable(value.id, value.name, value.category, value.complete);
            });
          },
          "json");
      },
    
      updateTodoItem: function(id, isComplete) {
        $.post(todoApp.apiEndpoint, {
            "method": "updateTodoItem",
            "todoItemId": id,
            "todoItemComplete": isComplete
          },
          function(data) {},
          "json");
      },
    
      /*
       * UI Methods
       */
      addTodoItemToTable: function(id, name, category, isComplete) {
        var rowColor = isComplete ? "active" : "warning";
    
        todoApp.ui_table().append($("<tr>")
          .append($("<td>").text(name))
          .append($("<td>").text(category))
          .append($("<td>")
            .append($("<input>")
              .attr("type", "checkbox")
              .attr("id", id)
              .attr("checked", isComplete)
              .attr("class", "isComplete")
            ))
          .addClass(rowColor)
        );
      },
    
      /*
       * UI Bindings
       */
      bindCreateButton: function() {
        todoApp.ui_createButton().click(function() {
          todoApp.createTodoItem(todoApp.ui_createNameInput().val(), todoApp.ui_createCategoryInput().val(), false);
          todoApp.ui_createNameInput().val("");
          todoApp.ui_createCategoryInput().val("");
        });
      },
    
      bindUpdateButton: function() {
        todoApp.ui_updateButton().click(function() {
          // Disable button temporarily.
          var myButton = $(this);
          var originalText = myButton.text();
          $(this).text("Updating...");
          $(this).prop("disabled", true);
    
          // Call api to update todo items.
          $.each(todoApp.ui_updateId(), function(index, value) {
            todoApp.updateTodoItem(value.name, value.value);
            $(value).remove();
          });
    
          // Re-enable button.
          setTimeout(function() {
            myButton.prop("disabled", false);
            myButton.text(originalText);
          }, 500);
        });
      },
    
      bindUpdateCheckboxes: function() {
        todoApp.ui_table().on("click", ".isComplete", function(event) {
          var checkboxElement = $(event.currentTarget);
          var rowElement = $(event.currentTarget).parents('tr');
          var id = checkboxElement.attr('id');
          var isComplete = checkboxElement.is(':checked');
    
          // Togle table row color
          if (isComplete) {
            rowElement.addClass("active");
            rowElement.removeClass("warning");
          } else {
            rowElement.removeClass("active");
            rowElement.addClass("warning");
          }
    
          // Update hidden inputs for update panel.
          todoApp.ui_updateForm().children("input[name='" + id + "']").remove();
    
          todoApp.ui_updateForm().append($("<input>")
            .attr("type", "hidden")
            .attr("class", "updateComplete")
            .attr("name", id)
            .attr("value", isComplete));
    
        });
      },
    
      /*
       * UI Elements
       */
      ui_createNameInput: function() {
        return $(".todoForm #inputItemName");
      },
    
      ui_createCategoryInput: function() {
        return $(".todoForm #inputItemCategory");
      },
    
      ui_createButton: function() {
        return $(".todoForm button");
      },
    
      ui_table: function() {
        return $(".todoList table tbody");
      },
    
      ui_updateButton: function() {
        return $(".todoUpdatePanel button");
      },
    
      ui_updateForm: function() {
        return $(".todoUpdatePanel form");
      },
    
      ui_updateId: function() {
        return $(".todoUpdatePanel .updateComplete");
      },
    
      /*
       * Install the TodoApp
       */
      install: function() {
        todoApp.bindCreateButton();
        todoApp.bindUpdateButton();
        todoApp.bindUpdateCheckboxes();
    
        todoApp.getTodoItems();
      }
    };
    
    $(document).ready(function() {
      todoApp.install();
    });
    
  3. الآن لم يتبقَّ سوى اختبار التطبيق. شغل التطبيق محليًّا، وأضف بعض عناصر Todo عن طريق ملء اسم العنصر والفئة والنقر على "Add Task". بعد ظهور العنصر، يمكنك تحديث ما إذا كان مكتملًا عن طريق تعديل مربع الاختيار والنقر على "Update Tasks".

نشر تطبيق Java على مواقع ويب Azure

تجعل مواقع ويب Azure نشر تطبيقات Java أمرًا بسيطًا مثل تصدير تطبيقك كملف WAR وإما رفعه من خلال التحكم في المصدر (مثل Git) أو FTP.

  1. لتصدير تطبيقك كملف WAR، انقر بزر الماوس الأيمن على مشروعك في Project Explorer، وحدد Export، ثم حدد WAR File.

  2. في نافذة "WAR Export" نفذ بما يلي:

    • في مربع مشروع ويب، أدخل azure-cosmos-java-sample.
    • في المربع الوجهة، اختر وجهة لحفظ ملف WAR.
    • حدد إنهاء.
  3. الآن بعد أن أصبح لديك ملف WAR، يمكنك ببساطة رفعه إلى دليل Webapps على موقع Azure. للحصول على إرشادات حول رفع الملف، راجع إضافة تطبيق Java إلى Azure App Service Web Apps. بعد تحميل ملف WAR إلى دليل webapps، ستكشف لك بيئة وقت التشغيل أنك أضفته وستحمله تلقائيًّا.

  4. لعرض المنتج النهائي، انتقل إلى http://YOUR\_SITE\_NAME.azurewebsites.net/azure-cosmos-java-sample/ وابدأ في إضافة مهامك.

احصل على المشروع من GitHub

يتم تضمين جميع العينات في هذا البرنامج التعليمي في مشروع todo على GitHub. لاستيراد مشروع todo إلى Eclipse، تأكد من وجود البرامج والموارد المدرجة في قسم المتطلبات الأساسية ثم قم بما يلي:

  1. ثبت Project Lombok. يُستخدم Lombok لإنشاء المُنشئين، والمكتسبين، والمستوطنين في المشروع. بمجرد تنزيل الملف lombok.jar، انقر نقرًا مزدوجًا فوقه لتثبيته أو تثبيته من سطر الأوامر.

  2. إذا كان Eclipse مفتوحًا، فأغلقه وأعد تشغيله لتحميل Lombok.

  3. في Eclipse، في قائمة File، حدد Import.

  4. في نافذة Import، حدد Git، وحدد Projects from Git، ثم حدد Next.

  5. في شاشة Select Repository Source، حدد Clone URI.

  6. في شاشة Source Git Repository، في مربع URI، أدخل https://github.com/Azure-Samples/azure-cosmos-java-sql-api-todo-app، ثم حدد Next.

  7. في شاشة Branch Selection، تأكد من تحديد main، ثم حدد Next.

  8. في شاشة Local Destination، حدد Browse لتحديد مجلد يمكن نسخ المستودع فيه، ثم حدد Next.

  9. في شاشة تحديد معالج لاستخدامه في استيراد المشاريع، تأكد من تحديد Import existing projects، ثم حدد Next.

  10. في شاشة Import Projects، قم بإلغاء تحديد مشروع DocumentDB، ثم حدد Finish. يحتوي مشروع DocumentDB على SDK Azure Cosmos DB Java، الذي سنقوم بإضافته كتبعية بدلاً من ذلك.

  11. في Project Explorer، انتقل إلى azure-cosmos-java-sample\src\com.microsoft.azure.cosmos.sample.dao\DocumentClientFactory.java واستبدل قيمي HOST و MASTER_KEY بمفتاح URI وPRIMARY لحساب Azure Cosmos DB الخاص بك، ثم احفظ الملف. لمزيد من المعلومات، راجع الخطوة 1. إنشاء حساب قاعدة بيانات Azure Cosmos DB.

  12. في Project Explorer، انقر بزر الماوس الأيمن فوق azure-cosmos-java-sample، وحدد Build Path، ثم حدد Configure Build Path.

  13. في شاشة Java Build Path، في الجزء الأيسر، حدد علامة التبويب Libraries، ثم حدد Add External JARs. انتقل إلى موقع ملف lombok.jar، وحدد Open، ثم حدد OK.

  14. استخدم الخطوة 12 لفتح نافذة Properties مرة أخرى، ثم في الجزء الأيمن حدد Targeted Runtimes.

  15. في شاشة Targeted Runtimes، حدد New، وحدد Apache Tomcat v7.0، ثم حدد OK.

  16. استخدم الخطوة 12 لفتح نافذة Properties مرة أخرى، ثم في الجزء الأيمن حدد Project Facets.

  17. في شاشة Project Facets، حدد Dynamic Web Module وJava، ثم حدد OK.

  18. في علامة التبويب Servers أسفل الشاشة، انقر بزر الماوس الأيمن فوق Tomcat v7.0 Server at localhost ثم حدد Add and Remove.

  19. في نافذة Add and Remove، انقل azure-cosmos-java-sample إلى مربع Configured، ثم حدد Finish.

  20. في علامة التبويب Servers، انقر بزر الماوس الأيمن فوق Tomcat v7.0 Server at localhost، ثم حدد Restart.

  21. في المتصفح انتقل إلى http://localhost:8080/azure-cosmos-java-sample/ وابدأ في إضافتها إلى قائمة المهام. لاحظ أنه إذا قمت بتغيير قيم المنفذ الافتراضي، فقم بتغيير 8080 إلى القيمة التي حددتها.

  22. لنشر مشروعك على موقع Azure على ويب، راجع الخطوة 6. نشر تطبيقك إلى مواقع ويب Azure.

الخطوات التالية

هل تحاول القيام بتخطيط السعة للترحيل إلى Azure Cosmos DB؟ يمكنك استخدام معلومات حول نظام مجموعة قاعدة البيانات الموجودة لديك لـ تخطيط السعة.